Metadata-Version: 2.4
Name: actionsec
Version: 0.1.0
Summary: Scan GitHub Actions workflows for security issues — unpinned actions, script injection, broad permissions, pull_request_target footguns. Zero config, zero dependencies.
Author: yyfjj
License: MIT
Project-URL: Homepage, https://github.com/jjdoor/actionsec-py
Project-URL: Repository, https://github.com/jjdoor/actionsec-py
Project-URL: Issues, https://github.com/jjdoor/actionsec-py/issues
Keywords: github-actions,workflow,security,supply-chain,ci,sast,devsecops,cli
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Utilities
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# actionsec

**Your CI pipeline runs with a write token and your secrets. `actionsec` checks
it isn't a liability.** It scans your GitHub Actions workflows for the handful of
mistakes that actually get repos compromised — unpinned third-party actions,
script injection from untrusted input, over-broad token permissions, and the
`pull_request_target` footgun. Zero config, **zero dependencies**.

```bash
pip install actionsec
actionsec
```

```
.github/workflows/ci.yml
  ✗ critical  L12   pull-request-target-checkout  `pull_request_target` checks out PR head code — untrusted code runs with a write token and secrets
  ✗ critical  L16   script-injection              untrusted `github.event.pull_request.title` in a run step — pass it through an env: var instead
  ✗ high      L5    broad-permissions             `permissions: write-all` gives the token full read/write — scope it down
  ✗ high      L13   unpinned-action               `some-marketplace/deploy-action@main` is a mutable branch — pin to a full commit SHA
  ✗ medium    L10   unpinned-action               `actions/checkout@v4` is a mutable tag — pin to a full commit SHA

✗ 5 issue(s) in 1 of 1 file(s) — 2 critical, 2 high, 1 medium
```

## Why

The 2025 supply-chain attacks (reviewdog, tj-actions/changed-files) all rode in
through the same door: a workflow that trusted a mutable action tag, so when the
upstream tag was repointed at malicious code, every consumer ran it — with a
token that could push commits and read secrets. **[71% of repos never pin actions
to a SHA.](https://arxiv.org/html/2502.06662v1)**

`actionlint` validates workflow *syntax*; `zizmor` does deep dataflow analysis but
wants installation and config. `actionsec` fills the gap between them: a five-second,
zero-config pass over the highest-impact security checks, small enough to drop in a
pre-commit hook or a one-line CI step.

## The checks

| Check | Severity | What it catches |
|-------|----------|-----------------|
| **unpinned-action** | high / medium | `uses:` a mutable tag or branch (`@v4`, `@main`) instead of a 40-char commit SHA. Third-party = high, GitHub-owned `actions/*` = medium. |
| **script-injection** | critical | `${{ github.event.*.title \| .body \| .head_ref ... }}` interpolated into a `run:` step, where GitHub substitutes it into the shell before it runs. |
| **broad-permissions** | high | `permissions: write-all` — the token gets full read/write across the repo. |
| **missing-permissions** | low | no `permissions:` block at all, so the workflow inherits the repo default scope. |
| **pull-request-target-checkout** | critical | `pull_request_target` + a checkout of the PR head — untrusted code runs with a privileged token and secrets. |

It is **not** a YAML validator (use [actionlint](https://github.com/rhysd/actionlint))
and **not** a deep dataflow analyzer (use [zizmor](https://github.com/woodruffw/zizmor)).
It's a fast, line-based first pass — which is also why it's zero-dependency: it
never needs to parse YAML into a tree.

## Usage

```bash
actionsec                       # scan ./.github/workflows
actionsec path/to/repo          # scan another repo's workflows
actionsec ci.yml release.yml    # scan specific files
actionsec --min-severity high   # only high + critical (good for a hard CI gate)
actionsec --format json         # machine-readable
```

Try it on the bundled example after cloning:

```bash
python -m actionsec examples    # → flags the deliberately-vulnerable demo workflow
```

## In CI

`actionsec` exits non-zero when it finds issues, so it gates a pipeline directly.
A common setup is to fail only on the serious stuff:

```yaml
# .github/workflows/security.yml
- run: npx actionsec --min-severity high
```

| Exit code | Meaning |
|-----------|---------|
| `0` | no issues at or above the threshold |
| `1` | issues found |
| `2` | error (no workflows found, unreadable file) |

## Options

```
--min-severity <sev>   low | medium | high | critical (default: low)
--format text|json     output format (default: text)
-v, --version
-h, --help
```

## Also available for Node

```bash
npx actionsec
```

Same checks, same severities, same exit codes — [actionsec on npm](https://github.com/jjdoor/actionsec).

> Note: actionsec reads workflow files as text, so it works on any repo regardless
> of which language you install it from.

## License

MIT
