# Checklist for Reviewing AI-Generated GitHub Actions

> A human review checklist for GitHub Actions workflows written by AI coding agents — secret exposure, supply chain attacks, permissions, and deployment safety.

**Type:** Checklist  
**Tools:** Cursor, Claude Code, Codex, Windsurf  
**Stack:** TypeScript  
**Updated:** 2026-06-08

---

GitHub Actions workflows have direct access to secrets and deployment environments. AI generates workflows that pin to mutable tags, expose secrets in logs, and grant excessive permissions to third-party actions.

## Correctness

```txt
[ ] Workflow triggers are scoped correctly — pull_request vs pull_request_target behave differently for fork PRs
[ ] pull_request_target does not check out the PR's code without explicit trust validation
[ ] on: push branches filter is set — avoid triggering on every branch push
[ ] Job dependencies are declared with needs: — parallel jobs that must be sequential are chained
[ ] Conditional steps use the correct expression syntax — ${{ }} not $() or %VAR%
[ ] Matrix strategy fail-fast is set intentionally — false if you want all matrix jobs to complete
[ ] Artifact retention days are set — default is 90 days, which may retain sensitive files too long
[ ] Cache keys are specific enough to invalidate on dependency changes (include lockfile hash)
[ ] Upload-artifact and download-artifact steps use matching names and paths
[ ] Environment variables are set at the correct scope (job vs step) — step-level env does not persist
[ ] Working directory is set explicitly for monorepos — default is repo root
[ ] Checkout depth is set for git history-dependent commands (e.g. conventional commits, changelogs)
```

## Security

```txt
[ ] Third-party actions are pinned to a full SHA commit hash, not a mutable tag like @v3 or @main
[ ] actions/checkout, actions/setup-node, and other GitHub-owned actions are pinned to SHA
[ ] GITHUB_TOKEN permissions are declared with the minimum required scope (contents: read, id-token: write, etc.)
[ ] permissions: at the workflow level is set to read-all or {} to restrict defaults before granting per-job
[ ] Secrets are not echoed in run: steps — no echo ${{ secrets.MY_SECRET }}
[ ] Secrets are not set as environment variables at the job level if only one step needs them
[ ] User-controlled input (GitHub event data) is not interpolated into run: shell commands — use env: instead
[ ] Expression injection is prevented — do not use ${{ github.event.pull_request.title }} directly in run: steps
[ ] Deployment jobs require environment approval for production — use environment: with protection rules
[ ] Self-hosted runners are not used for open-source repos with pull_request triggers
[ ] OpenID Connect (OIDC) is used for cloud auth (AWS, GCP, Azure) instead of long-lived credentials
[ ] No AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY stored as secrets — prefer OIDC role assumption
[ ] workflow_dispatch inputs are validated — do not pass them directly to shell commands
```

## Deployment

```txt
[ ] Production deployments run only on the main or release branch — not on every PR
[ ] Deployment workflow has a manual approval gate via environment protection rules
[ ] Rollback step or procedure is documented in the workflow or linked
[ ] Database migration steps run before the new application version is deployed
[ ] Health check is polled after deployment — workflow fails if the service does not come up
[ ] Concurrency group is set to prevent simultaneous deployments: concurrency: deploy-${{ github.ref }}
[ ] cancel-in-progress is set to false for deployments — do not cancel a deployment mid-flight
[ ] Release assets and changelogs are generated from signed commits or tags, not PR body text
[ ] Semantic versioning is automated with a verified tool — not a hand-rolled shell script
```

## Performance

```txt
[ ] Node modules are cached using actions/cache with the lockfile as the cache key
[ ] Docker layer caching is enabled — use cache-from and cache-to in docker/build-push-action
[ ] Large test suites are parallelized with matrix strategy across available runners
[ ] Setup steps (language runtime, tools) use cached versions where available
[ ] Artifact uploads are scoped to only the files needed by downstream jobs
[ ] Workflow does not install global tools that could be provided by a pre-built action
```

## AI-Specific Risks

```txt
[ ] AI has not used deprecated set-output syntax — use GITHUB_OUTPUT environment file instead
[ ] AI has not used deprecated add-path or save-state commands — use GITHUB_PATH and GITHUB_STATE
[ ] AI has not pinned to a mutable :latest Docker image in a container: block
[ ] AI has not mixed GitHub-hosted and self-hosted runner syntax for the same job
[ ] AI has not used actions/cache@v2 — v3 or v4 is required for current runner images
[ ] AI has not generated a workflow that runs on pull_request_target and checks out PR code — classic supply chain vulnerability
[ ] AI-generated workflow secrets are referenced by the correct name — check for typos that silently pass empty strings
[ ] AI has not hardcoded environment-specific URLs or bucket names that differ between staging and production
```

## Fix Prompt

```txt title="Fix Prompt"
Review this GitHub Actions workflow against the checklist above. Pin all
third-party actions to their full SHA, add minimum-required permissions, replace
any secret interpolation in run: steps with env: bindings, add a concurrency
group to prevent parallel deployments, and add an environment approval gate for
the production deploy step. Return the corrected workflow YAML only.
```