ENG-58Recommended
Block pushes that contain secrets
Turn on the code host's secret push protection for every repository, so a push with a key is refused before it lands. Every bypass needs a reason and alerts an owner.
How to do it
A scanner in the build finds a key after it is already in the history, as Keep secrets out of code explains. Push protection stops it on the server, for every client and every agent. GitHub calls it push protection, and GitLab calls it secret push protection. Turn it on at the organisation level, so new repositories get it too. Let only named people approve a bypass, and send each bypass to the security owner. Add patterns for your own token formats, such as a shop_live_ prefix. Keep a pre-commit scanner as well, because it catches the key before it is even committed. Treat any key that reached a branch as leaked and replace it, as Keep secrets in a secrets store says.
Examples
Shell example
# GitHub: list repositories without push protection
gh api --paginate orgs/example/repos --jq '.[] | select(.security_and_analysis.secret_scanning_push_protection.status != "enabled") | .full_name' \
| grep . && { echo "FAIL: push protection off"; exit 1; } || echo "PASS"
Python example
# The same check with the GitHub REST API
import os, sys, requests
headers = {"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}"}
repos = requests.get("https://api.github.com/orgs/example/repos?per_page=100", headers=headers, timeout=10).json()
unprotected = [r["full_name"] for r in repos
if r.get("security_and_analysis", {}).get("secret_scanning_push_protection", {}).get("status") != "enabled"]
if unprotected:
sys.exit(f"FAIL: push protection off in {', '.join(unprotected)}")
JavaScript example
// The same check in Node.js 22
const response = await fetch('https://api.github.com/orgs/example/repos?per_page=100', {
headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` },
});
const unprotected = (await response.json())
.filter(repo => repo.security_and_analysis?.secret_scanning_push_protection?.status !== 'enabled')
.map(repo => repo.full_name);
if (unprotected.length) throw new Error(`Push protection off in ${unprotected.join(', ')}`);
How to check
A script lists the repositories with push protection switched off, and the list is empty. Push a commit holding a test key in a known format, such as a sample from the provider's docs. The code host refuses the push.
Sources
Related rules
- APS-60 Keep secrets out of code in Application security
- ENG-26 Keep secrets in a secrets store in Engineering
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/engineering/ci-and-supply-chain/secret-push-protection">Agent Wranglers engineering playbook</a>. In code you write, add a comment next to it with the same link.