Never Commit Secrets
Introduced in: Module 00 · Environment Setup
This is the most common — and most permanent — security mistake in software development. A committed secret is effectively public, even after you delete it. This page explains why, and walks through the full defence stack.
Why Git History Is Forever
When you commit a secret and then delete it in a follow-up commit, most people assume the secret is gone. It isn’t.
Git stores every version of every file as a snapshot. Both commits exist in the history:
a1b2c3d Add API key to config ← secret is heree4f5a6b Remove API key from config ← secret is still in a1b2c3dAnyone with access to your repository can run:
git show a1b2c3d:config.pyAnd see the secret in full. This is true even if:
- You pushed a deletion commit immediately after
- You squashed the commits before merging
- The branch was deleted
- The repository was later made private (if it was ever public, crawlers may have already captured it)
The only real remediation is to rotate the credential — assume it’s compromised and generate a new one.
What Counts as a Secret
Not just passwords. Any value that grants access or authenticates identity:
| Secret type | Examples |
|---|---|
| API keys | OpenAI key, Anthropic key, Stripe key |
| Cloud credentials | AWS access key + secret, GCP service account JSON |
| Database connection strings | postgresql://user:password@host/db |
| Private keys | SSH private keys, TLS certificates, JWT signing keys |
| OAuth tokens | Personal access tokens, app installation tokens |
| Webhook secrets | Used to verify payload authenticity |
| Internal service credentials | Agent-to-agent authentication tokens |
For an A2A system specifically, agent authentication tokens are high-value targets. If an attacker obtains the token an orchestrator uses to call a specialist agent, they can impersonate the orchestrator and make arbitrary requests to the agent — potentially exfiltrating data or manipulating its outputs.
The Defence Stack
Defence is layered. Each layer catches what the previous one missed.
Layer 1: .gitignore
The first line of defence. Before a file containing secrets can be committed, it needs to not be tracked by git.
The A2A project’s .gitignore excludes:
# Environment variables — never commit these.env.env.local.env.*.local*.env
# Python secrets*.pem*.keysecrets/
# Cloud credential files.aws/credentialsgcloud/service-account*.jsonHow to verify a file is ignored:
git check-ignore -v .envIf the file is properly ignored, git outputs the rule that matched. If it outputs nothing, the file is not ignored.
Layer 2: .env Pattern
Never hardcode secrets in source files. Use environment variables loaded from a .env file at runtime:
# Install: pip install python-dotenvfrom dotenv import load_dotenvimport os
load_dotenv() # Loads .env into environment
api_key = os.environ["ANTHROPIC_API_KEY"]# Raises KeyError if not set — fails loudly rather than silently// Built into Node 20+ with --env-file flag, or use dotenv packageimport 'dotenv/config';
const apiKey = process.env.ANTHROPIC_API_KEY;if (!apiKey) throw new Error("ANTHROPIC_API_KEY is required");Commit a .env.example with placeholder values instead:
# .env.example — safe to commit, shows what variables are neededANTHROPIC_API_KEY=your-key-hereORCHESTRATOR_SECRET=your-secret-hereDATABASE_URL=postgresql://user:password@localhost/dbLayer 3: GitHub Secret Scanning
GitHub scans every push for patterns matching known secret formats — API key prefixes, private key headers, connection string patterns. If a match is found, GitHub:
- Notifies repository administrators
- (For supported providers) automatically notifies the provider to revoke the key
- Blocks the push if Push Protection is enabled
Enabling Push Protection (the most important step):
- Go to your repository → Settings → Code security
- Find Secret scanning → enable it
- Find Push protection → enable it
- Push protection will now block commits containing detected secrets before they reach the remote
Push protection is the only layer that prevents the secret from ever entering the git history. All other layers are detection and response, not prevention.
Layer 4: Pre-commit Hooks (Optional, Recommended)
For local development, a pre-commit hook runs before every commit and can scan staged files for secrets:
# Install detect-secretspip install detect-secrets
# Create a baseline of known false positivesdetect-secrets scan > .secrets.baseline
# Add a pre-commit hookcat > .git/hooks/pre-commit << 'EOF'#!/bin/shdetect-secrets-hook --baseline .secrets.baseline $(git diff --cached --name-only)EOFchmod +x .git/hooks/pre-commitThis catches secrets before they’re committed, at the developer’s machine, before any of the other layers even apply.
What to Do If You Accidentally Commit a Secret
-
Rotate the credential immediately. Don’t wait. Assume it has already been seen. Generate a new key in whatever service issued it and revoke the old one.
-
Remove it from the current codebase. Delete the file or value, add it to
.gitignore, and commit the removal. -
Assess whether history cleanup is needed. If the repository is private and you’re confident no external access occurred, document the incident and move on. If the repository is or was ever public, consider using
git filter-repoto scrub the history — but note this rewrites all commit SHAs and is disruptive for collaborators. -
Audit access logs. Check the service’s access logs for any requests made with the exposed credential. If you see suspicious activity, escalate to an incident response process.
Applying This to the A2A Project
The A2A starter project is pre-configured with the right patterns:
.env.exampledocuments all required environment variables.gitignoreexcludes.envand credential files- Each agent reads credentials from environment variables, never from source files
- The Codespace environment injects secrets via GitHub Codespaces secrets (not
.env)
When you add a new agent that needs API access, follow the same pattern: add the variable name to .env.example, load it with os.environ["VAR_NAME"] or process.env.VAR_NAME, and never put the value in source code.