Package Exports
- actions-warden
- actions-warden/commands/audit
- actions-warden/commands/pin
- actions-warden/commands/report
- actions-warden/commands/upgrade
Readme
actions-warden
Audit, pin, and upgrade GitHub Actions workflows. Designed for safe, hands-off invocation by humans or LLMs.
- Audit - scan workflows for supply-chain and injection vulnerabilities
- Pin - rewrite tag refs (
@v3) to immutable commit SHAs - Upgrade - bump pinned actions to the newest permitted version
- Report - combined audit + dry-run plan, ideal for LLM context
Why
Tag references in uses: are mutable - anyone with write access to the action
repo (or a stolen maintainer token) can rewrite v3 to point at malicious code.
Pinning to a 40-character commit SHA closes that hole. actions-warden finds
the holes, plans the fix, and applies it - but never without your explicit
say-so. --dry-run is the default for every destructive command.
Install
npm install -g actions-warden
# or one-shot
npx actions-warden auditRequires Node.js 20 or newer.
Quick start
# Audit every workflow under .github/workflows
actions-warden audit
# Audit a specific file with remediation hints
actions-warden audit -w .github/workflows/release.yml --explain
# Plan a SHA-pinning pass (does NOT write)
actions-warden pin
# Actually write the pins
actions-warden pin --write
# Plan upgrades within the same major version
actions-warden upgrade --mode=minor
# Get one combined LLM-friendly report
actions-warden report --format=toonOutput formats
TOON (default - --format=toon)
Token-Oriented Object Notation. Each line is a labeled record with key=value
fields. Parses cleanly without a schema and ends with a machine-readable
STATUS: trailer.
SCAN: file=.github/workflows/release.yml
FINDING: id=18b82e86d7 type=unpinned-action sev=high action=actions/checkout@v3 line=15
FINDING: id=b50d0d45ab type=secrets-in-env sev=critical key=AWS_SECRET line=4
SUMMARY: files=1 findings=2 critical=1 high=1 medium=0 low=0
STATUS: FAILJSON (--format=json)
Structured payload for programmatic integrations.
Text (--format=text)
Plain human-readable lines - useful when piping through less.
Commands
audit
Scan workflows for security findings.
| flag | default | description |
|---|---|---|
-w, --workflow <pattern> |
discover under .github/workflows/ |
repeatable path or glob |
--severity <level> |
low (i.e. include all) |
minimum severity to report |
--explain |
false |
include plain-English remediation hint per finding |
--format <fmt> |
toon |
toon, json, or text |
--output <dest> |
stdout |
stdout or file |
--output-path <path> |
- | required when --output=file |
--cwd <dir> |
. |
working directory |
Exit codes: 0 if no findings, 1 if any finding reported, 2 on usage error.
pin
Resolve every tag/branch ref to a 40-char commit SHA. Preserves the original
tag as an inline # v3 comment so upgrade can find it later.
| flag | default | description |
|---|---|---|
-w, --workflow <pattern> |
discover | repeatable |
--write |
false |
apply changes (otherwise dry-run) |
--dry-run <bool> |
true |
explicit dry-run toggle |
--token <token> |
$GITHUB_TOKEN |
GitHub API token |
--fix <id> |
- | apply only the change with this id |
--format <fmt> |
toon |
upgrade
Bump pinned/tagged actions to the newest version allowed by --mode.
| flag | default | description |
|---|---|---|
--mode <m> |
minor |
major, minor, or patch |
--write |
false |
apply changes |
--fix <id> |
- | apply only this change id |
--token <token> |
$GITHUB_TOKEN |
report
Runs audit, plus dry-run pin and upgrade. Single combined output.
| flag | default | description |
|---|---|---|
--mode <m> |
minor |
upgrade scope |
--offline |
false |
skip network-dependent stages |
rules
Print the rule catalog.
Use it as a GitHub Action
actions-warden ships an action.yml at the repo root, so you can drop it
into any workflow.
permissions:
contents: read
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: <owner>/actions-warden@<commit-sha> # pin to a SHA
with:
command: audit
severity: high
explain: 'true'
token: ${{ github.token }}Inputs (all optional unless noted):
| input | default | applies to | description |
|---|---|---|---|
command |
audit |
all | audit, pin, upgrade, report, rules |
workflow |
discover | all | space-separated paths or globs |
severity |
- | audit/report | low / medium / high / critical |
format |
toon |
all | toon / json / text |
mode |
minor |
upgrade/report | major / minor / patch |
min-age |
7 |
upgrade/report | cooldown in days before accepting a new tag |
write |
false |
pin/upgrade | true to apply changes |
explain |
false |
audit | include remediation hints |
offline |
false |
report | skip network calls |
output-path |
- | all | also save the report to this file |
token |
- | all | GitHub token (pass ${{ github.token }}) |
working-directory |
$GITHUB_WORKSPACE |
all | directory to scan |
node-version |
20 |
- | Node.js version to install |
Outputs:
| output | description |
|---|---|
status |
OK or FAIL (mirrors the CLI's exit signal) |
findings |
number of audit findings (audit/report) |
report-path |
absolute path of the saved report, if output-path was set |
The action also writes a job summary block with the output of the command, making findings visible directly in the GitHub UI.
Important: the audit command exits non-zero when findings are reported,
which fails the job by default. To collect findings without failing the build,
set continue-on-error: true on the step.
Programmatic API
Each command is also exported as an async function, so an LLM agent or larger Node tool can invoke it without spawning a subprocess.
import { audit, pin, upgrade, report } from 'actions-warden';
const result = await audit({ cwd: '/path/to/repo', explain: true });
for (const finding of result.findings) {
// result.findings[i].id is stable; pass it to pin({ fix: id })
}Available functions: audit, pin, upgrade, report, listRules,
parseWorkflowFile, renderAudit, renderPin, renderUpgrade,
renderReport, format, redact.
Audit rules
| id | severity | catches |
|---|---|---|
unpinned-action |
high | uses: refs that aren't 40-char SHAs |
excessive-permissions |
medium | write-all and broad write scopes |
secrets-in-env |
critical | secrets at workflow/job env (leaks to every step) |
script-injection |
critical | github.event.* interpolated into run: |
pull-request-target-checkout |
critical | "pwn-request" pattern |
Run actions-warden rules for the live list.
LLM invocation safety
Every command is designed to be safe to invoke from an autonomous agent:
pinandupgradedefault to--dry-run=true. You cannot accidentally mutate files without explicitly passing--write.- Output is deterministic and idempotent - re-running on an unchanged repo produces identical bytes.
- Every finding and every planned change carries a stable
id. To apply a single fix without scope creep, pass--fix=<id>. - The CLI never prompts interactively. All decisions are flag-driven.
- Secrets that leak into log lines (tokens, AWS keys, PEM blocks) are passed through a redactor before output.
Authentication
export GITHUB_TOKEN=$(gh auth token) # or set GH_TOKEN
actions-warden pinThe --token flag takes precedence. Without a token the GitHub API allows 60
requests/hour, which is enough for small repos but will rate-limit on larger
audits.
Caching
GitHub API responses are cached in .actions-warden-cache/ (gitignore it).
TTL defaults to 1 hour. Delete the directory to force a refresh.
Exit codes
| code | meaning |
|---|---|
0 |
success, no findings |
1 |
findings reported, or errors during pin/upgrade |
2 |
invalid arguments |
Releasing
To cut a release:
Bump
versioninpackage.json(e.g.0.1.0→0.2.0).Commit and push to
main.Create and push the tag:
git tag v0.2.0 git push origin v0.2.0
The .github/workflows/release.yml workflow then:
- verifies
package.jsonversion matches the tag and runs the test suite, - creates a GitHub Release with auto-generated notes,
- force-updates the floating major tag (e.g.
v0) to point at the new commit.
Consumers can pin precisely (@v0.2.0), float on the major (@v0), or
pin to a commit SHA (recommended - and what actions-warden pin will
produce when run against their workflow).
Publishing to the GitHub Marketplace
The repo's action.yml already declares branding, so it is Marketplace-eligible.
After the first release tag is pushed, open the release on github.com and tick
"Publish this Action to the GitHub Marketplace" to list it. No automation
required - the release workflow above handles everything except that opt-in.
Publishing to npm
The release workflow includes a publish-npm job that publishes to npm with
provenance via OIDC trusted publishing - no long-lived NPM_TOKEN lives in
GitHub secrets.
npm does not support pre-publish trusted-publisher configuration — the Trusted Publisher panel only appears on packages that already exist on the registry. So the very first publish has to be done with a one-time automation token; after that, OIDC takes over.
Step 1 — bootstrap publish (one time, locally)
npm login # browser auth
npm publish --access public # no --provenance on the first publish;
# local publishes can't sign provenanceStep 2 — configure the trusted publisher on npmjs.com
Once the package exists, go to:
npmjs.com → Packages → actions-warden → Settings → Trusted publishing
Add a new GitHub Actions publisher with:
- Organization or user:
chiz0me - Repository:
actions-warden - Workflow filename:
release.yml - Environment: (leave blank)
Save. From this point on, no token is needed.
Step 3 — release future versions
Bump version in package.json, push, then:
git tag v0.2.0
git push origin v0.2.0The release workflow then:
- runs the full test suite and dependency-pin verification,
- publishes to npm with
--provenance --access public(the published package carries a verifiable link back to this exact commit and workflow run), - creates the GitHub Release with auto-generated notes,
- force-moves the floating major tag (
v0).
The package is published as actions-warden (unscoped, public). Consumers
install it with:
npm install -g actions-warden
# or
npx actions-warden auditRequirements: trusted publishing needs npm ≥ 11.5.1 and Node ≥ 24, so the
publish-npmjob uses Node 24 and upgrades npm to latest before publishing.
Security
See SECURITY.md for the disclosure policy.
License
MIT