GitHub Actions Review Prompt
Paste into Claude Code, run from the repo root.
You are a senior DevOps / Platform Engineering expert specializing in GitHub Actions, CI/CD cost optimization, and supply-chain security. Assume an adversarial, critical review posture. Cite specific files and line numbers for every finding. Do not summarize or hedge where you can be specific.
SCOPE
Review every workflow file under .github/workflows/ plus all related automation:
- Composite actions under
.github/actions/ - Reusable workflows referenced via
uses:(local paths and remote repos) - Scripts invoked by workflows: shell scripts, Makefile targets, Python/Node helpers
.github/dependabot.ymlandCODEOWNERSif present
If a workflow calls a script, read the script before judging the workflow. If a workflow uses a third-party action you are not certain about, fetch its action.yml or README before making recommendations that depend on its behavior.
PHASE 1 — RECONNAISSANCE
Do this first — do not start fixing the first workflow you open. Report a brief summary before doing any analysis.
- List every workflow with its triggers, runner(s), top-level purpose, and whether it has a
permissions:block andconcurrency:block. - Detect runner strategy per workflow: GitHub-hosted (
ubuntu-latestetc.), Blacksmith (blacksmith-*), or self-hosted. Flag mixed usage within a single workflow. - Detect the project stack (Node/TS, Go, Kotlin/Android, Godot, Python, etc.) — this determines appropriate caching, setup actions, and matrix recommendations.
- Detect Blacksmith adoption globally. If yes, check usage of
useblacksmith/cache,useblacksmith/setup-node,useblacksmith/setup-go, etc. If no, recommendations should targetactions/*equivalents and note where Blacksmith migration is highest-value.
PHASE 2 — ANALYSIS RUBRIC
For each finding, record: file path, line range, severity (Critical/High/Medium/Low/Nit), and one of:
- SAFE — functionality-preserving (no change to job outputs, artifacts, or observable behavior)
- JUDGMENT — changes behavior or adds surface area
- SECURITY — security/bug finding
Cost & Performance
- Runner right-sizing. Is the job CPU- or memory-bound? Is a 2-vCPU runner enough? If Blacksmith: recommend a specific SKU (
blacksmith-2vcpu-ubuntu-2404vsblacksmith-8vcpu-...). If GitHub-hosted: flag where Blacksmith migration would cut cost/time and estimate the delta. - Dependency caching. Present? Correct cache key? Sensible
restore-keysfallback? Expected hit rate? Preferuseblacksmith/cachewhen Blacksmith is in use (sticky caches, no network hop). actions/checkoutdepth. Isfetch-depth: 0used when only HEAD is needed? Flag. Note cases where full history is required (release tooling,git log-based logic).- Concurrency groups. Missing
concurrency: { group: ..., cancel-in-progress: true }on PR workflows is a direct cost leak. BUT: do not recommendcancel-in-progress: trueon deploy workflows targetingmain/prod — interrupted deploys can leave infra in a broken state. Recommendcancel-in-progress: falsewith a unique group there. - Path filters. Workflows triggered on changes that don't affect them (e.g., docs-only change triggering a full integration test suite).
- Matrix strategy. Redundant combinations,
fail-fast: falsemissing where warranted, or present where it causes wasted runs. - Parallelization. Sequential jobs that could run in parallel via
needs:restructuring. timeout-minutes. Missing on any job is a runaway-cost risk. Recommend a sensible default per job type.- Artifact management. Oversized uploads, missing compression, excessive retention.
- Service containers vs installed services. Over-provisioning (e.g., running a full Postgres container for a unit test suite that mocks the DB).
Correctness & Robustness
- Triggers.
on:correctness — duplicatepush+pull_requestcausing double runs, missingpull_request.typesfilters, wrong branch filters. if:conditions. String-comparison bugs, missing quoting around contexts, incorrect use ofsuccess()/failure()/always().- Job dependencies. Missing
needs:causing race conditions; over-constrained graphs serializing needlessly. - Env vs secrets vs vars. Misuse, scope errors.
- Shell safety. Multi-line bash blocks without
set -euo pipefail; shell interpolation bugs in called scripts. - Deprecated workflow commands. Flag deprecated workflow commands in
run:blocks —::set-output::and::save-state::(use$GITHUB_OUTPUT/$GITHUB_STATE), and::set-env::/::add-path::(removed for script-injection safety; use$GITHUB_ENV/$GITHUB_PATH). - Cache key correctness. Two jobs writing the same cache key with different contents (poisoning each other).
Security — expert-level critical review
Anchor every finding to the GitHub Actions Secure Use Reference. The rubric below is grouped to align with that reference where it speaks, plus items it doesn't cover (persist-credentials, cache poisoning, secrets-in-if:, concurrency); cite the doc plus the specific line/file for every finding. Where relevant, also map the finding to an OWASP Top 10 CI/CD risk or an OpenSSF Scorecard check.
Action pinning & third-party supply chain
- Action pinning. Third-party actions MUST be pinned to a full commit SHA with a comment noting the version tag (
uses: org/action@<40-char-sha> # v1.2.3). Per the GitHub doc, SHA pinning is "currently the only way to use an action as an immutable release" — tag pinning is bypassable by repo compromise (tags can be moved or deleted). Any unpinned third-party action is at minimum a High finding. The tj-actions/changed-files compromise (March 2025) is the canonical case for why this matters. - Verify the SHA. Confirm the pinned SHA is from the action's canonical repository, not a fork.
- Official vs third-party. Official
actions/*may use major tags, but SHA is preferred even for those. Verified-creator actions are still third-party — pin them to a SHA. - Reusable workflows. Same supply-chain rules apply — pin remote
uses: org/repo/.github/workflows/foo.yml@<sha>. - Provenance signals. Unknown publishers, unverified authors, archived repos, recent maintainer changes, low star/install counts on a security-sensitive action — flag as concerns even when otherwise pinned.
- Deprecated action runtime. Flag third-party actions whose
action.ymldeclares a deprecated Node runtime (using: 'node12'or'node16') — GitHub forced node20-over-node16 by default on 2024-06-03, and these will eventually fail. Treatnode20as nearing end-of-life (EOL ~April 2026, with node24 migration underway) and recommend upgrading to a Node24-capable version. - Dependabot for actions.
.github/dependabot.ymlshould includepackage-ecosystem: github-actions. Note: Dependabot only opens version-update PRs for semver-pinned actions — SHA-pinned actions don't generate Dependabot alerts. Mitigation is the SHA-with-version-comment pattern (Dependabot updates the comment along with the SHA). - Dependency review on PRs. PRs touching
.github/should runactions/dependency-review-actionto flag vulnerable transitive action versions before merge.
Secret hygiene
- Plaintext secrets in workflow files. Hardcoded credentials or tokens in YAML are Critical. Use repo / org / environment secrets.
- Structured data as a single secret is broken. JSON, XML, YAML blobs encapsulating multiple values cause log redaction to fail (redaction is exact-string match). Per the GitHub doc, split into individual secrets per sensitive value. Flag any secret whose name suggests a blob (
*_JSON,*_CONFIG,*_BUNDLE). - Generated/derived secrets must be re-masked. When a workflow derives a value from a secret (signed JWT, base64-re-encoded key, OAuth code exchange, decrypted blob), register the derived value with
echo "::add-mask::$VALUE"before the next step uses it. Otherwise it leaks in logs. echoof secrets (even temporarily, even in a debugging step left behind) is a Critical finding. Same for unredacted secrets in step outputs, error messages, or environment dumps.- Secrets in
if:expressions. Secrets transformed through expression functions (fromJSON,format, string concatenation) can defeat the runner's exact-string log redaction and leak in logs/UI. Flag anyif:referencingsecrets.*. - Exposure response. If unredacted secrets appeared in any prior run's logs, the secret is compromised — delete the log and rotate. Note this as the documented response procedure, not a maybe.
- Environment-scoped secrets with required reviewers. Sensitive secrets (production deploy keys, signing keys, third-party API keys with destructive scope) should live in environment-scoped secrets with required reviewers and deployment-branch restrictions, not as repo or org secrets. Flag prod-deploy workflows that use repo secrets directly.
Workflow code injection
- Script injection via
${{ ... }}interpolation. Any${{ github.event.* }},${{ github.head_ref }},${{ inputs.* }},${{ github.event.pull_request.title }},${{ github.event.comment.body }},${{ github.event.issue.title }}(or any other user-controllable context value) interpolated directly into arun:block is a High finding (Critical if the workflow has elevated permissions or runs onpull_request_target). Fix per the GitHub doc: pass via anenv:mapping and reference"$VAR"(double-quoted), withset -euo pipefailupstream. Better still, use a JavaScript or composite action that takes the value as an argument. pull_request_target+ checkout of PR head. Classic RCE vector —pull_request_targetruns with write permissions and access to repo secrets, and a malicious PR can inject code that runs in that context. Critical. The defaultpull_requestevent from a fork runs with a read-onlyGITHUB_TOKENand no access to secrets by design — which is precisely why people reach forpull_request_target. The secure pattern is to keep untrusted-code jobs onpull_requestand isolate any secret-requiring step behind an environment with required reviewers.workflow_runtrigger. Verify it does not execute untrusted PR code with elevated privileges from the triggering workflow's context.persist-credentials: trueonactions/checkoutwhen the job does not push — leaves theGITHUB_TOKENon disk and reachable from any subsequent step or composite action. Set tofalseunless explicitly needed forgit push.- Cache poisoning. PR-writable caches (
actions/cachekeyed on PR-controllable values) consumed by trusted workflows can be poisoned by an attacker PR. Scope cache keys to the trigger; never let a PR run write a cache key that amainrun or a privileged workflow reads.
Permissions, OIDC, and access surface
permissions:block. Every workflow should declare minimum permissions explicitly at the workflow or job level. A workflow without one is a Medium finding (org defaults vary but historically default to write-all). Recommendpermissions: contents: readas the workflow-level default and elevate per-job only where needed.GITHUB_TOKENscope vs need. Common over-grants:contents: writeon read-only test jobs,pull-requests: writeon jobs that don't comment,id-token: writeon a job with no OIDC/cloud-auth step. Tighten per-job.- OIDC for cloud authentication. Long-lived cloud credentials (AWS access keys, GCP service-account JSON, Azure SPN secrets) stored as repo secrets is a Medium finding when the provider supports OIDC — recommend the official OIDC integrations (
aws-actions/configure-aws-credentials,google-github-actions/auth,azure/loginwith federated credentials) withpermissions: id-token: writescoped to the deploy job. OIDC eliminates the standing credential entirely. - Environments + required reviewers. Production deployment workflows should target an environment (
environment: production) with required reviewers and deployment-branch restrictions configured in repo Settings. Workflows that deploy to prod without environment-gated approval are at minimum Medium. - CODEOWNERS for
.github/workflows/. The.github/workflows/directory should be owned inCODEOWNERSby the platform/security team and protected by branch-protection-required-review. Without this, anyone with repo write access can modify CI to exfiltrate secrets. Medium finding if missing on a repo with non-trivial secrets. - Disable Actions creating/approving PRs. Org or repo setting "Allow GitHub Actions to create and approve pull requests" should be off unless an automation explicitly requires it. Self-approving CI bots are a known privilege-escalation vector.
- Audit log for Actions changes. Recommend periodic review of
org.update_actions_secret,repo.actions_enabled, and runner registration events. Note as a recommendation if not already covered by the org's security tooling.
Runner risks
- GitHub-hosted runners are ephemeral and isolated — the default safe choice. Per-image SBOMs are published at
actions/runner-images/releasesfor supply-chain review. - Self-hosted runners are persistent unless explicitly ephemeral. Per the GitHub doc: never use self-hosted runners on public repositories — any user can open a PR that compromises the runner host. Internal/private repos still require care because forks and PRs from anyone with read access execute on the runner. Flag any self-hosted runner on a public repo as Critical.
- JIT (Just-In-Time) ephemeral runners. If self-hosted is required, prefer the JIT REST API pattern (
POST /repos/{owner}/{repo}/actions/runners/generate-jitconfig) that creates a single-job, self-destroying runner. Note the caveat: the host/VM backing successive JIT runners can retain state unless each ephemeral runner gets a freshly provisioned environment. - Runner groups. Multi-repo / org-level self-hosted runners must be partitioned into runner groups with explicit repo allowlists. A runner group accessible to many repos is a cross-tenant compromise vector.
- Runner host hygiene. Self-hosted runners with cloud-instance metadata access (AWS IMDSv2, GCP metadata service) are a credential-exfiltration risk if any workflow runs untrusted code. Lock down with IAM, IMDSv2-only, and
harden-runneregress policies.
Detection & tooling
Recommend concrete additions to CI when gaps are found:
zizmor(woodruffw/zizmor) — static analyzer specifically for GitHub Actions security. Catches script injection, dangerous triggers, permissions over-grants, and many of the items above. Recommend as a CI step or pre-commit hook.actionlint(rhysd/actionlint) — syntactic linter with shellcheck integration forrun:blocks. Complement to zizmor (correctness, not security).step-security/harden-runner— egress filtering and audit for what each workflow contacts at runtime. Recommended for repos with elevated permissions or sensitive secrets.actions/dependency-review-action— PR-time dependency vulnerability gate.- OpenSSF Scorecard (scorecard.dev / scorecard-action) — automated check suite covering pinned-actions, token-permissions, dangerous-workflow, and several other items in this rubric. Recommend especially for repos that publish actions or have high supply-chain exposure.
- Repository security advisories. For repos that publish actions consumed by others: enable private vulnerability reporting and use repository security advisories.
Maintainability
- DRY: repeated setup steps across workflows → candidates for composite action or reusable workflow.
- Naming, step comments, workflow-level documentation.
PHASE 3 — REPORT
Produce a single structured report with these sections:
Section A — Safe Optimizations (functionality-preserving)
Numbered list. For each: file path, line range, before snippet, after snippet, one-line rationale, expected impact (time saved / cost saved / risk reduced). These are the ONLY changes you may offer to apply automatically.
Section B — Additional Improvements (judgment calls)
Things that add value but change behavior, add new workflows, or require team decisions. Do NOT offer to implement these automatically.
At the end of Section B, produce a ready-to-paste follow-up prompt I can use in a fresh Claude Code session to have you implement the full list (or an easily-editable subset).
Section C — Bugs & Security Findings
Ordered by severity. For each: file + lines, description, concrete exploit or failure scenario, recommended fix. Do NOT offer to implement these automatically.
At the end of Section C, produce a ready-to-paste follow-up prompt to address the findings.
Section D — Summary
- Top 3 quick wins
- Estimated CI time saved per PR run (rough)
- Estimated monthly cost delta (rough — note assumptions)
- Single most important security finding, if any
PHASE 4 — IMPLEMENTATION
After presenting the report, ASK whether to proceed with Section A changes. If approved, apply them as a single coherent commit's worth of edits, then output:
- A suggested commit message (conventional-commits style)
- A suggested PR description summarizing the changes and expected impact
Do nothing from Section B or C without an explicit new request.
CONSTRAINTS
- Do not modify any file before I approve Section A.
- Do not delete or disable workflows.
- Do not invent third-party action capabilities — fetch the action if uncertain.
- Quote specific lines when making findings; avoid generic advice.
- Read scripts referenced by workflows before judging the workflow.
- Where Blacksmith-native patterns differ from generic GitHub Actions patterns, prefer Blacksmith-native when Blacksmith is already in use.
- Do not blanket-apply
cancel-in-progress: trueto deploy workflows on protected branches.