How to stop Claude Code from pushing secrets to GitHub
Claude Code's permission rules and hooks are a good first guard against pushing API keys, but they run on the agent's machine. Set them up, then add a gate it can't reach.
Short answer: use three layers. Keep secrets out of Claude Code’s reach, add Claude Code’s own deny rules and a pre-push hook, and put a check on a separate machine that Claude Code can’t edit or go around. The first two take ten minutes and catch most mistakes. The third is what still holds when they don’t.
1. Keep secrets out of reach
Claude Code can only leak what it can read. Keep keys in environment variables
or a secret manager rather than in files in the project, and deny reads of the
files that do hold them. In .claude/settings.json:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}
That stops the most common accident: Claude Code reading .env to make a test
pass and copying a key into a config file.
2. Add a pre-push hook in Claude Code
Claude Code can run a script before every shell command it executes (a
PreToolUse hook). If the script exits with code 2, the command is blocked and
the script’s message goes back to Claude, which then fixes the problem. This
hook checks what’s about to be pushed for key-shaped strings:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": ".claude/hooks/check-push.sh" }]
}
]
}
}
#!/bin/sh
# .claude/hooks/check-push.sh - block git push when the outgoing commits
# contain something that looks like an API key.
input=$(cat) # the tool call as JSON; the command is inside it
case "$input" in
*"git push"*)
if git log @{u}.. -p 2>/dev/null |
grep -Eq 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|sk-[A-Za-z0-9_-]{20,}'; then
echo "Blocked: the commits you are pushing contain what looks like an API key. Remove it from the commits, then push again." >&2
exit 2
fi ;;
esac
exit 0
Make it executable (chmod +x). It compares against the
branch’s upstream, so a brand-new branch with no upstream isn’t checked: a
reason to keep the patterns simple and treat this as a first line, not the last.
You can also deny the risky push forms outright, e.g.
"Bash(git push --force:*)" and "Bash(git push --no-verify:*)" in the same
deny list.
3. Where those guards stop
Both layers run on your machine, as you, next to the agent:
- Claude Code can edit them.
.claude/settings.jsonand the hook script are files in the project. An agent working fast can change or remove them, unless you also deny edits to.claude/**, and even then a new clone or worktree starts without them. - They only cover Claude Code. A push from Codex, another agent, a script or your own terminal never passes Claude Code’s hooks.
- The key still reaches GitHub if the check misses it. Once pushed, deleting it doesn’t help: it has to be rotated.
That isn’t a flaw in Claude Code. Any check on the machine the agent controls has these limits, and git’s own pre-commit hooks have the same ones.
4. Add a gate Claude Code can’t reach
Put the final check on a separate machine, between your laptop and GitHub, and give only that machine the GitHub token. Your repo’s remote points at the gate; every push from Claude Code, any other agent or you is checked there before anything reaches GitHub. Nothing on your laptop can switch it off, and there’s no other route to GitHub because your laptop no longer holds a GitHub credential.
nimblegate is such a gate: self-hosted, one Docker container, deterministic checks for API keys from AWS, GitHub, OpenAI, Anthropic, Stripe and others, plus force-pushes and agent shortcuts like skipped tests. A rejected push tells Claude Code the rule, file and line, so it fixes the problem and pushes again:
remote: refs/heads/main: BLOCK [security/no-hardcoded-credentials] credentials detected (raw bytes redacted): config.js:3 - AWS access key
! [remote rejected] main -> main (pre-receive hook declined)
Setting it up takes about ten minutes: the quick start runs the gateway and blocks a fake key, and run your AI agent through it covers removing the direct route from your machine to GitHub.
Which layers do you need?
- Solo, low stakes: layers 1 and 2. They catch most accidents.
- Agents push on their own, or several agents share a repo: add layer 4. It covers every agent and every push, and doesn’t depend on anything the agent can edit.
Keep layers 1 and 2 even with the gate in place: they give Claude Code faster feedback, before it commits.
nimblegate is free for non-commercial use; commercial use needs a license. Questions: contact@nimblegate.com.
← All posts · nimblegate is a self-hosted push gateway for AI agents - how it works · live demo