Your AI coding agent is asked to help debug a failing API integration. You paste the full error log into the chat — and the log contains the Authorization header from the failing request, which includes a live bearer token. The token is now in the model’s context and potentially in your API provider’s logs, your application’s chat history database, and any monitoring systems that record prompt payloads. The token needs to be rotated immediately, but first you need to know exactly where it went. Accidental secret inclusion is the most common credential-exposure vector in AI-assisted development: it usually happens through log pastes, config file reads, environment variable printing, or automated pipelines that include more context than intended. This article covers how to detect what was exposed, where to look for it, and the preventive controls that stop secrets from entering the model context in the first place.
Common causes
1. Pasting error logs that contain authorization headers
HTTP error logs commonly include full request and response headers. Authorization: Bearer sk-..., X-API-Key: ..., or Cookie: session=... appear inline. When a developer pastes these logs to get debugging help, every secret in the log is now in the prompt.
How to spot it: Before pasting any log output into an AI chat, run it through a quick grep:
grep -iE "(authorization|x-api-key|bearer|password|secret|token|cookie)" /path/to/error.log
If any line returns a hit, redact it before pasting.
2. Agent reads .env or config files as part of project context
An AI coding assistant is given broad file-system access to “understand the project.” It reads .env, config/production.yaml, or terraform.tfvars and those files contain production secrets.
How to spot it: Check which files the agent accessed during a session. Any file whose name suggests credentials should not have been in scope.
3. System prompt template includes a secret via string interpolation
A developer builds the system prompt dynamically and accidentally includes a secret in the template:
const systemPrompt = `You are a support agent for our API. The internal admin key is ${ADMIN_KEY}. Use this for escalations.`;
How to spot it: Grep all files where system prompts are constructed for process.env., os.environ, or any variable name containing key, secret, token, password.
4. Tool call arguments include full credentials
An agent calls a tool like make_http_request and the orchestration layer injects the API key into the headers argument as a string, which then appears in the model’s conversation for the next turn.
How to spot it: Log all tool call arguments and run secret-pattern scans over them. Check whether headers or authentication arguments are passed as plain strings vs. opaque credential handles.
5. CI/CD pipeline passes environment variables into prompt templates
An automated pipeline builds prompts using CI environment variables. The pipeline is configured in a way that dumps all environment variables for debugging and those dumps include secrets.
How to spot it: Review every echo $VARIABLE or printenv statement in your CI scripts. Check whether any output from these commands is consumed by a prompt-building step.
6. Chat history persistence captures the secret permanently
Even if you delete the message from the UI, the secret may have been persisted to a database, included in a training feedback export, or cached in your API provider’s systems. You cannot assume deletion is complete.
How to spot it: Check your chat history storage: database tables, object storage buckets, log shipping destinations. Confirm whether the message body is stored and who has read access.
Shortest path to fix
Step 1: Rotate the exposed credential immediately
# This step cannot wait for investigation.
# Example: rotate an OpenAI key
# 1. Visit https://platform.openai.com/api-keys
# 2. Revoke the exposed key
# 3. Issue a new key
# 4. Update all dependents
# Example: rotate a GitHub token
# 1. Visit https://github.com/settings/tokens
# 2. Delete the exposed token
# 3. Generate a replacement
# 4. Re-authorize all services that used the old token
# Example: rotate an AWS key pair
aws iam delete-access-key --access-key-id AKIA_EXPOSED_KEY_ID
aws iam create-access-key --user-name svc-account-name
Step 2: Identify everywhere the secret traveled
# Search your application logs for the exposed key value
grep -r "EXPOSED_KEY_VALUE" /var/log/app/ /var/log/nginx/
# Search your database for the value (run this in a read-only replica)
# SELECT COUNT(*) FROM chat_messages WHERE body LIKE '%EXPOSED_KEY_VALUE%';
# Search your monitoring/SIEM for the value
Step 3: Add a pre-send secret scanner to your prompt builder
const SECRET_PATTERNS = [
/sk-[A-Za-z0-9]{20,}/g,
/ghp_[A-Za-z0-9]{36}/g,
/AKIA[A-Z0-9]{16}/g,
/xoxb-[0-9]{11,}-[A-Za-z0-9-]+/g,
/-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----/g,
/password\s*[:=]\s*\S+/gi,
/api[_-]?key\s*[:=]\s*\S+/gi,
];
function containsSecret(text: string): boolean {
return SECRET_PATTERNS.some((re) => re.test(text));
}
function buildPromptSafe(userMessage: string, context: string): string {
if (containsSecret(context)) {
throw new Error("Context contains a potential secret — redact before including in prompt.");
}
if (containsSecret(userMessage)) {
logger.warn({ event: "user_message_contains_secret" });
// Either block or redact depending on policy
}
return `${systemPrompt}\n\nContext:\n${context}\n\nUser: ${userMessage}`;
}
Step 4: Block secret-pattern file paths from agent access
const BLOCKED_FILE_PATTERNS = [
/\.env(\.\w+)?$/,
/secrets\.(yml|yaml|json)$/i,
/credentials\.json$/i,
/terraform\.tfvars(\.json)?$/i,
/\.netrc$/,
/.*\.pem$/,
/.*_rsa(\.pub)?$/,
/.*\.p12$/,
];
function canReadFile(path: string): boolean {
return !BLOCKED_FILE_PATTERNS.some((re) => re.test(path));
}
Step 5: Redact secrets from the prompt before it is stored
function redactSecretsFromPrompt(prompt: string): string {
let redacted = prompt;
for (const pattern of SECRET_PATTERNS) {
redacted = redacted.replace(pattern, "[REDACTED_SECRET]");
}
return redacted;
}
// Store only the redacted version in your chat history database
const safePromptForStorage = redactSecretsFromPrompt(fullPrompt);
await db.chatMessages.create({ body: safePromptForStorage, sessionId });
Step 6: Set up pre-commit hooks to prevent secrets in code that generates prompts
# Install gitleaks as a pre-commit hook
brew install gitleaks
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
gitleaks protect --staged --redact --config .gitleaks.toml
if [ $? -ne 0 ]; then
echo "gitleaks: secrets detected in staged files — commit blocked"
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
Prevention
- Treat every prompt payload as a potential log entry that could be retained for years — never include a live secret in any prompt.
- Apply a secret-pattern scanner to all prompt content before sending to the model API.
- Block AI agent file access to any file whose name or path pattern suggests it contains credentials.
- Store prompt and response payloads with secrets redacted in your persistence layer.
- Run
gitleaksortruffleHogin pre-commit hooks and CI pipelines to catch hardcoded secrets before they reach code that builds prompts. - Never interpolate raw environment variables into system prompt templates; if a tool needs a credential, pass it via a server-side tool handler, not through the model.
- Document a runbook for rotating each secret type in your system so any engineer can rotate in under 10 minutes when a leak is detected.
- Conduct quarterly audits of what data flows into your prompt-building pipeline to ensure no new secret sources have been added.
FAQ
Q: If I delete the message from the chat UI, is the secret gone? A: Likely not. Deletion from the UI usually only removes the user-facing record. The underlying database row, API provider retention, log aggregation, and monitoring captures may all retain the value. Assume the secret is permanently compromised and rotate it.
Q: My model provider says they do not train on API usage data. Does that mean my prompt is safe? A: The no-training policy applies to training datasets, not to operational logging for abuse detection, debugging, or retention requirements. Always assume prompts may be retained for some period and design accordingly.
Q: Should I use a secrets manager to inject secrets into my application? A: Yes. AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, and similar tools provide runtime secret injection via API rather than environment variables or config files. The secret is never persisted in code, and access is auditable.
Q: Is it safe to give an AI assistant read access to my codebase if I have .env in .gitignore?
A: Not by default. Many AI coding tools (Claude Code, Cursor, Cline) read files from the working directory including files not tracked by git. You must explicitly configure which files the tool may access, or apply path blocklists.
Related
- Agent Leaks an API Key in Its Output
- Injection Bypasses the System Prompt
- Data Exfiltration via Image URL
- User Input Treated as System Instruction
- AI Agent Overwrote .env / Environment Variables
- Claude Code Commits Secret to Repository
- Prompt Injection via User-Pasted Content
- Prompt Injection Embedded Inside a PDF