DEV Community

Yuhe He
Yuhe He

Posted on

The CI Security Checklist I Run Before Every Repo Goes Public

Checklists fail when they become rituals: fifteen items, all ticked, nothing found. This one is shorter because every line in it has a scar behind it. It runs before a repo goes public, before a workflow change merges, and after any incident with secrets in play. Ten lines; the order is the point.

1. Enumerate every workflow trigger. on: is the attack surface. pull_request_target and workflow_run run in the base context with secrets — if a fork PR can influence what they execute, stop and fix before reading further. Everything below assumes this line passed.

2. Grep the org, not the repo, for secrets usage. Actions receive ${{ secrets.* }} from the repo/org context; org secrets are visible to every repo's workflows, including the one you just made public. A public repo inheriting an org secret is a public repo with a secret-shaped dependency.

3. Read one failing run's logs and artifacts as an attacker. Not the yml — the outputs. Command echo, exception payloads, uploaded debug files, bot comments. The recording surfaces (argv in logs, artifacts as downloadable logs, cache contents) are where real leaks live; the yml is where you wrote intent, artifacts are where the system wrote truth.

4. Check third-party actions by commit SHA, not tag. A mutable tag is a supply chain with a friendly writer. Pin the SHA; the diff between pins is the audit.

5. Ask what the cache stores. Caches are shared, restorable, and often include config directories. actions/cache with a broad path is a secret persistence layer.

6. Ask what the badge/status exposes. Public status checks on public repos show names, job logs (public repos!), and artifacts. "Public repo" plus "logs" plus "this workflow touches tokens" is the Venn diagram where GitHub's own docs put the warning box.

7. Test the empty-secret path. Delete a secret in a test repo; every workflow that behaves differently when a secret is absent is a probe surface — fork PRs can binary-search the difference. (If a workflow's branch label changes with the secret, you built a yes/no oracle for outsiders.)

8. Check who can comment and what comments trigger. issue_comment-triggered workflows run on comments — from contributors, from bots, from strangers on public repos. An automation that deploys on "LGTM" is a deployment permission granted to the comment section.

9. Verify secret rotation has an owner and a date. A secret without a rotation date is a future incident with a calendar. The audit does not rotate it; the audit names who will, by when, in the README's security section.

10. Write the answer to one question in plain language: "if this repo is public, what can a stranger influence?" Triggers, comments, PRs, cache keys, status names — the honest sentence, written down, is the checklist. The other nine lines feed it. If writing the sentence is hard, the repo is not ready to be public; the difficulty is the finding.

Line order matters: 1 gates the rest (base-context triggers make 7 and 8 lethal), 3 is the only item that finds leaks that already happened, and 10 is the only item that requires thinking. Most audits run 10 as the header ("repo is safe!") instead of the test. Running it last is what makes it an audit and not a ritual.

— 何雨禾 (heyuhe). The scar collection comes from solo pipelines where the attacker model was one person, and I was both the attacker and the audit.


The checklist as a fillable one-pager: GhostAction Event Brief — the 5-step self-audit ($5). Why “15 minutes” is the honest budget: GhostAction Is Harvesting CI Secrets Right Now.

Top comments (0)