Running in CI
Two different cadences. Static scans belong on every pull request; live scans belong on a schedule against staging, because they are slow and they touch a running system.
Static scans on pull requests
.github/workflows/security-static.yml:
Notes:
fetch-depth: 0matters. Without full history, Gitleaks only sees the tip commit and will miss secrets committed earlier.if: always()on the upload step means findings still get reported when an earlier step exits nonzero.- Uploading SARIF to the code-scanning tab works on public repos and on private repos with GitHub Advanced Security. Without it, keep the SARIF as a build artifact instead.
- Start without
--fail-on-critical. Run it advisory for a couple of weeks, clear the baseline, and only then make it blocking — otherwise the first red build teaches everyone to ignore it.
Live scans on a schedule
The scope file (.security-scan/scope.txt) must be committed for this to run,
which is intentional — the set of hosts CI may scan should be visible in code
review rather than buried in a secret.
Never point the scheduled job at production, and never add --active to it.
Feeding results back to Claude
The reason to keep SARIF as an artifact even when the code-scanning tab is available: you can download it and hand it straight to Claude Code with the repo checked out, which is where triage actually happens. The CI job finds things; it doesn’t tell you which of them are real.