Scan GitHub Actions workflows for security issues — unpinned actions, script injection, broad permissions, pull_request_target footguns. Zero config, zero dependencies.
Project description
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.
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.
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) and not a deep dataflow analyzer (use 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
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:
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:
# .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
npx actionsec
Same checks, same severities, same exit codes — actionsec on npm.
Note: actionsec reads workflow files as text, so it works on any repo regardless of which language you install it from.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file actionsec-0.1.0.tar.gz.
File metadata
- Download URL: actionsec-0.1.0.tar.gz
- Upload date:
- Size: 12.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62a6582817f60ab524ff675727600edbc94bc1e77ceda2543b946970b6bdbe76
|
|
| MD5 |
82277e1544e6fa00225fd4dcf2802bba
|
|
| BLAKE2b-256 |
511a8b9cb4991c4219f287cb25d06ef4ec2f804af706c5c1f01e610c5d3a7982
|
File details
Details for the file actionsec-0.1.0-py3-none-any.whl.
File metadata
- Download URL: actionsec-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1af39fd0ab91e56bbd9df89c272416ea5656e423c93279fa32159a4cb1c01ab2
|
|
| MD5 |
f401fc5aca24b8df46d717ebe4326888
|
|
| BLAKE2b-256 |
fbc3397c435f43a70f2bf472c579eabfafd46451b30efa1fee961dc7a0abb920
|