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.
Use it from Claude Code
This repo is also a Claude Code plugin. Once installed, Claude will invoke
actions-warden automatically whenever a prompt asks to audit, pin, or
upgrade GitHub Actions workflows.
Option A - via the plugin marketplace (recommended):
/plugin marketplace add chiz0me/claude-plugins
/plugin install actions-warden@chiz0meOption B - drop the skill in directly (no marketplace):
mkdir -p ~/.claude/skills/actions-warden
curl -fsSL https://raw.githubusercontent.com/chiz0me/actions-warden/main/skills/actions-warden/SKILL.md \
-o ~/.claude/skills/actions-warden/SKILL.mdAfter installation, prompts like "audit my workflows", "pin my actions to
SHAs", or "check this workflow for script injection" will route through the
skill, which runs the CLI via npx actions-warden and explains the TOON
output back to you.
Plugin layout (per the Claude Code plugin spec):
.claude-plugin/plugin.json # plugin manifest
skills/actions-warden/SKILL.md # the skill itselfProgrammatic 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.
Marketplace sync
When a vX.Y.Z tag is pushed, the sync-marketplace job in release.yml
updates the actions-warden plugin entry in
chiz0me/claude-plugins/.claude-plugin/marketplace.json
to match. No-op when the marketplace is already on the target version.
One-time setup (cross-repo write requires a token the default
GITHUB_TOKEN cannot provide):
- Create a fine-grained personal access token at
https://github.com/settings/personal-access-tokens/new with:
- Repository access: Only select repositories →
chiz0me/claude-plugins - Permissions: Contents: Read and write
- Repository access: Only select repositories →
- In
chiz0me/actions-wardenrepo settings, add it as an Actions secret namedMARKETPLACE_SYNC_TOKEN.
After that, every release tag also writes a sync: bump actions-warden to vX.Y.Z
commit to the marketplace repo.
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