Agent Wranglers

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

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.