A security scan across public GitHub repositories identified 1,122 configuration files exposing active credentials. The culprit was a common convenience feature in terminal coding agents: the "Yes, and don't ask again" approval prompt.
When an agent proposes running a command containing credentials and you grant standing approval, the client records that exact string into .claude/settings.local.json under its permissions allow list. If that file ever gets tracked, every key inside it enters your commit history.
Here is the exact mechanism behind the leak and the three configuration steps required to prevent it.
The mechanics of the leak
When Claude Code asks for permission to execute a shell command, selecting "Yes, and don't ask again" creates an entry in your local settings file. If the proposed command contains inline tokens, database passwords, or private endpoints, the literal command string is saved to disk:
{
"permissions": {
"allow": [
"Bash(psql localhost:5432/main -U postgres)",
"Bash(curl -H \"Authorization: Bearer api-token-here\" https://api.internal/v1/sync)"
]
}
}
The underlying issue is Git synchronization. Claude Code automatically writes **/.claude/settings.local.json to your workstation's global Git excludes file (core.excludesFile). That protects your primary machine.
However, global git excludes do not travel with the repository. When you clone the repository onto a secondary laptop, a CI runner, or a teammate's machine where global excludes are unconfigured, Git treats .claude/settings.local.json as an untracked file. Running git add . or git add -A immediately stages the file along with your code.
Verifying your repository history
Before changing configurations, verify whether any agent settings files were staged or committed in your repository. Run this terminal check:
git log --all --full-history -- "*/.claude/settings.local.json" ".claude/settings.local.json"
If the command outputs commit hashes, the file has already been committed to your history. Here is a typical output from a compromised repository:
commit 7b1e4a8f90c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6
Author: Developer <developer at example.com>
Date: Wed Nov 11 14:22:10 2026 -05:00
feat: update dev configuration and test scripts
If your history contains commits with this file, rotating those credentials at the provider is mandatory. Deleting the file in a new commit leaves the secret intact in earlier revisions.
Step 1: Enforce repository-level Git ignore
Do not rely on workstation-level global excludes. Add the agent settings directory directly to your project .gitignore:
# Coding agent local settings
.claude/settings.local.json
.claude/worktrees/
If the file was already tracked in your working tree, unstage it without deleting your local copy:
git rm --cached .claude/settings.local.json
Output:
rm '.claude/settings.local.json'
Commit the .gitignore update so every collaborator and environment inherits the rule.
Step 2: Use environment variables instead of literal commands
Terminal agents allow command rules with environment variable substitution. Instead of approving commands with inline secrets, pass variables defined in your shell:
# Approves generalized variable command instead of literal secret
export db_password="secret_password"
psql "postgresql://app_user:$db_password/main"
In .claude/settings.json (the shared, committed team configuration), restrict standing permissions to generalized command patterns:
{
"permissions": {
"allow": [
"Bash(git status)",
"Bash(npm test)",
"Bash(pytest tests/)"
],
"deny": [
"Bash(curl *sk-ant*)",
"Bash(*BEGIN OPENSSH PRIVATE KEY*)"
]
}
}
Deny rules override allow rules. Blocking common credential patterns prevents the CLI from executing or recording high-risk strings without explicit per-command prompts.
Step 3: Add a pre-commit secret hook
Add a local hook in .git/hooks/pre-commit to prevent accidental staging of agent configuration files:
#!/usr/bin/env bash
set -euo pipefail
# Block staged agent local settings
if git diff --cached --name-only | grep -E '\.claude/settings\.local\.json$' > /dev/null; then
echo "Error: Attempting to commit .claude/settings.local.json" >&2
echo "This file contains local permissions and should never be committed." >&2
exit 1
fi
Make the script executable:
chmod +x .git/hooks/pre-commit
Testing the hook against a staged settings file confirms the block:
git add .claude/settings.local.json && git commit -m "test commit"
Output:
Error: Attempting to commit .claude/settings.local.json
This file contains local permissions and should never be committed.
Summary checklist
- Run
git logto confirm.claude/settings.local.jsonwas never pushed. - Commit
.claude/settings.local.jsonto the project root.gitignore. - Rotate any API tokens or database credentials that were saved with standing approvals.
- Define explicit deny patterns in
.claude/settings.jsonto block inline token patterns.
Top comments (0)