Skip to content

actions: new coronarium actions audit for SHA-pin static analysis#47

Merged
bokuweb merged 1 commit into
mainfrom
claude/actions-audit
May 10, 2026
Merged

actions: new coronarium actions audit for SHA-pin static analysis#47
bokuweb merged 1 commit into
mainfrom
claude/actions-audit

Conversation

@bokuweb

@bokuweb bokuweb commented May 10, 2026

Copy link
Copy Markdown
Owner

Summary

Closes harden-runner parity gap #10 from the roadmap.

New subcommand coronarium actions audit <workflow.yml...> walks
every uses: in jobs.<id>.steps[] and jobs.<id>.uses (reusable
workflow callers) and flags references that aren't pinned to a
40-char commit SHA. Pure offline static analysis — no GitHub API
calls.

Severity ladder:

sev when
error third-party action with mutable tag/branch (foo/bar@v1, foo/bar@main)
warn first-party (actions/*, github/*) mutable tag, or docker image without @sha256: digest
ok 40-char SHA pin, local action (./...), docker @sha256: reference

--strict escalates Warn → Error. Text + JSON output. Exits non-zero
when at least one error remains.

Smoke-tested against this repo's own workflows — it correctly
surfaces 17 errors (mostly dtolnay/rust-toolchain@stable|nightly
and the docker/* family on @v3/@v6) plus a long warn list of
actions/checkout@v4 etc.

Composite-action action.yml files (no top-level jobs: block) are
intentionally treated as out-of-scope — they return an empty
findings list rather than parse errors.

Tag→SHA auto-resolution via the GitHub API is deferred; it would
turn the tool into a network one and require auth.

Test plan

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace — 13 new unit tests in coronarium-core::actions (107 total in core, all green)
  • Manual run against .github/workflows/*.yml produced expected findings + exit code 1

🤖 Generated with Claude Code

Closes harden-runner gap #10. Walks one or more workflow YAMLs and
flags every `uses:` ref that isn't pinned to a 40-char commit SHA
— the supply-chain analogue of an unpinned dependency.

Severity ladder:

- Error: third-party with mutable tag/branch (`foo/bar@v1`,
  `foo/bar@main`) — exits 1.
- Warn:  first-party (`actions/*`, `github/*`) mutable ref, or a
  docker image without `@sha256:` digest. `--strict` escalates.
- Ok:    40-char SHA pin, local action (`./...`), docker `@sha256:`
  digest reference.

Walks both `jobs.<id>.steps[].uses` and `jobs.<id>.uses` (reusable
workflow callers). Composite-action `action.yml` (no top-level
`jobs:` block) is treated as out-of-scope and returns no findings.

Text + JSON output. Smoke-tested against this repo's own workflows
and surfaces real issues (dtolnay/rust-toolchain@stable, several
docker/* actions).

Tag→SHA auto-resolution via the GitHub API is intentionally
deferred — keeping the tool offline.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Signed-off-by: bokuweb <[email protected]>
@github-actions

Copy link
Copy Markdown

coronarium report

metric count
observed 249
denied 4
lost 0

events by kind

kind count
exec 6
open 64
connect 2

denied samples (first 10)

kind detail
exec /usr/bin/whoami (/usr/bin/whoami)
connect 1.1.1.1:443
connect 8.8.8.8:443
📊 Open the full HTML report locally
rm -rf /tmp/coronarium-25630917601 && gh run download 25630917601 -R bokuweb/sakimori -n coronarium-report -D /tmp/coronarium-25630917601 && (open /tmp/coronarium-25630917601/coronarium-report.html 2>/dev/null || xdg-open /tmp/coronarium-25630917601/coronarium-report.html 2>/dev/null || echo "open file:///tmp/coronarium-25630917601/coronarium-report.html")

Requires the gh CLI. The command downloads the workflow artifact and opens the self-contained HTML report in your browser.

@bokuweb bokuweb merged commit 8232ec1 into main May 10, 2026
7 checks passed
bokuweb added a commit that referenced this pull request May 11, 2026
…> points at (#57)

Closes the deferred follow-up from #47 (`actions audit`).

`sakimori actions audit` was offline-only: it flagged
`foo/bar@v1` as a mutable ref but left the user to manually look up
what to pin to. New `--resolve` flag (off by default) hits the
GitHub REST API and surfaces the resolved SHA on each finding so
the user can copy-paste the fix straight from the report.

Architecture:

- `actions::Resolver` trait keeps the offline path the default and
  lets tests substitute a deterministic fake.
- `actions::GithubResolver` is the concrete impl
  (`GET /repos/{owner}/{repo}/commits/{ref}` via ureq, reads
  `GITHUB_TOKEN` to raise the rate limit to 5000/hour).
- `actions::resolve_all(&mut findings, &dyn Resolver)` walks
  findings, caches per `(owner, repo, ref)` so a workflow that uses
  `actions/checkout@v4` ten times only hits the API once, and
  populates `Finding::resolved_sha` (or `Finding::resolve_error`
  when the lookup fails — one rate-limited action shouldn't kill
  the whole audit).

Output:

- **text**: indented `→ resolved: <sha>` line under the finding;
  `→ resolve failed: <reason>` on lookup error.
- **JSON**: new `resolved_sha` / `resolve_error` fields on
  `Finding` (both `skip_serializing_if = "Option::is_none"` so
  off-by-default consumers see no schema change).

5 new unit tests in `actions::tests` — caching, error isolation,
owner/repo extraction edge cases, percent-encoding for
`feature/x` style refs.

Signed-off-by: bokuweb <[email protected]>
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant