| name | prompt-security-hardening |
| description | Use when writing skills, CLAUDE.md files, agent prompts, or any directives that involve shell commands, environment variables, API credentials, file creation, or git operations - prevents secrets leakage into LLM context, unsafe shell patterns, and credential exposure |
| user-invocable | false |
Prompt Security Hardening
Your context window is sent to an API provider. Every secret that enters your context is a secret leaked to a third party. This skill defines the security boundaries you operate within.
1. Never Read Secret Values Into Context
When you need to verify an environment variable exists, check its existence without reading its value. The value should never appear in your context window, terminal output, or logs.
if [ -z "${STRIPE_SECRET_KEY+x}" ]; then
echo "STRIPE_SECRET_KEY is not set"
else
echo "STRIPE_SECRET_KEY is set"
fi
[[ -v STRIPE_SECRET_KEY ]] && echo "set" || echo "not set"
[[ -v DATABASE_URL ]] && echo "DATABASE_URL is set" || echo "DATABASE_URL is not set"
echo $STRIPE_SECRET_KEY
printenv STRIPE_SECRET_KEY
echo "Key is: ${STRIPE_SECRET_KEY}"
echo "Preview: ${STRIPE_SECRET_KEY:0:8}..."
echo "Length: ${#STRIPE_SECRET_KEY}"
set | grep STRIPE_SECRET_KEY
export | grep STRIPE_SECRET_KEY
env | grep STRIPE_SECRET_KEY
env | grep -q '^VAR='
Partial values and lengths are also leaks. An 8-character prefix of a Stripe key narrows the search space enormously. The length of a secret confirms its format. Reveal nothing.
Grepping shell config files (~/.zshrc, ~/.bashrc, ~/.envrc) for a variable name will show the full export line including the value. Check for the variable name's presence without showing the line content:
grep -qc 'ANTHROPIC_API_KEY' ~/.zshrc && echo "found in .zshrc" || echo "not in .zshrc"
grep 'ANTHROPIC_API_KEY' ~/.zshrc
grep -n 'ANTHROPIC_API_KEY' ~/.zshrc
2. Never Hardcode Secrets in Generated Code or Directives
When writing skills, agents, or CLAUDE.md files that include code examples, use environment variable references. When generating code for users, always reference environment variables or secret managers.
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
stripe.api_key = "sk_live_..."
stripe.api_key = "sk_test_..."
environment:
DATABASE_URL: ${DATABASE_URL}
environment:
DATABASE_URL: postgresql://admin:password123@db:5432/myapp
Placeholder values like changeme, your-api-key-here, replace-me, or postgres://user:password@localhost/db are not acceptable. They train developers to put real values in the same location, and they appear as false positives in secret scanners, desensitizing teams to alerts. Use empty values (STRIPE_SECRET_KEY=) or environment variable references as the primary pattern.
For .env.example or template files that get committed:
STRIPE_SECRET_KEY=
DATABASE_URL=
JWT_SECRET=
STRIPE_SECRET_KEY=sk_test_your_key_here
DATABASE_URL=postgres://user:password@localhost:5432/myapp
JWT_SECRET=change-this-to-something-secure
3. Set Restrictive File Permissions on Sensitive Files
When creating files that contain or will contain secrets (.env, .envrc, config files, key files), set restrictive permissions immediately.
touch .env && chmod 600 .env
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 /etc/myapp/secrets.conf
Default file creation mode (typically 644) makes files world-readable. SSH will refuse to use a key with open permissions, but .env files and config files have no such guardrail.
4. Verify .gitignore Before Creating Secret-Bearing Files
Before creating .env, .envrc, or any file that will contain secrets, verify the gitignore rules will exclude it. If they won't, add the rule first.
git check-ignore -v .env || echo ".env" >> .gitignore
touch .env && chmod 600 .env
git check-ignore -v .envrc || echo ".envrc" >> .gitignore
This applies to any file that will hold credentials: .env, .envrc, secrets.conf, credentials.json, key files, MCP configuration with embedded tokens.
5. Keep Secrets Out of URLs and Process-Visible Arguments
Tokens in URLs get logged in server access logs, proxy logs, and browser history. Tokens in command-line arguments are visible to other users via ps aux.
curl -H "Authorization: Bearer ${API_TOKEN}" https://api.example.com/data
curl "https://api.example.com/data?api_key=${API_TOKEN}"
For git operations, avoid embedding tokens in clone URLs:
git clone "https://${GITHUB_TOKEN}@github.com/org/repo.git"
GIT_ASKPASS=$(mktemp) && chmod 700 "$GIT_ASKPASS"
printf '#!/bin/sh\necho "${GITHUB_TOKEN}"' > "$GIT_ASKPASS"
GIT_ASKPASS="$GIT_ASKPASS" git clone https://github.com/org/repo.git
rm "$GIT_ASKPASS"
git config --global credential.helper store
echo "https://oauth2:${GITHUB_TOKEN}@github.com" | git credential-store store
git clone https://github.com/org/repo.git
When a token must be passed as an argument and there is no header/stdin alternative, use process substitution to limit exposure:
curl -H @<(echo "Authorization: Bearer ${API_TOKEN}") https://api.example.com/data
6. Sanitize External Input in Shell Commands
When constructing shell commands from file contents, tool results, or user-provided values, always quote variables and validate input.
FILENAME=$(some_tool_output)
cat $FILENAME
cat "$FILENAME"
USER_INPUT="$1"
find . -name $USER_INPUT
USER_INPUT="$1"
if [[ ! "$USER_INPUT" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "Invalid input" >&2
exit 1
fi
find . -name "$USER_INPUT"
For SQL in shell scripts, use parameterized queries:
psql -c "SELECT * FROM users WHERE name = '$USERNAME'"
psql --variable="username=$USERNAME" -c "SELECT * FROM users WHERE name = :'username'"
7. Guard Against Context Contamination From Files
When you read a file, its contents enter your context window and are sent to the API provider. Before reading any file, evaluate whether it might contain secrets.
Files likely to contain secrets — read with extreme caution or avoid entirely:
.env, .envrc, *.env.*
credentials.json, secrets.*, *-key.pem
- MCP configuration files with
env blocks
- Docker
.env files
~/.aws/credentials, ~/.netrc, ~/.npmrc with tokens
When debugging configuration issues, check file existence and structure without reading secret values:
wc -l .env
grep -c '=' .env
grep '^[A-Z_]*=' .env | cut -d= -f1
stat .env
Applying This Skill to Directives
When writing skills, CLAUDE.md files, or agent prompts:
- Code examples in directives must use environment variable references, not placeholder secrets
- Shell examples that check configuration must use existence checks, not value reads
- Workflow steps involving credentials must specify the safe pattern explicitly — if you leave it to default behavior, the unsafe pattern will be used inconsistently
- File creation steps must include permission setting and gitignore verification
- Never instruct an agent to read a secrets file to verify its contents — instruct it to verify structure or key names only
Quick Reference
| Need | Safe Pattern | Dangerous Pattern |
|---|
| Check env var exists | [ -z "${VAR+x}" ] or [[ -v VAR ]] | echo $VAR, printenv VAR |
| Use credential in code | os.environ["KEY"] | key = "sk_live_..." |
| Create secret file | touch f && chmod 600 f | echo "secret" > f (644) |
| Pre-commit safety | git check-ignore -v .env | Create .env and hope |
| API authentication | -H "Authorization: Bearer $TOKEN" | ?api_key=$TOKEN in URL |
| Git clone with token | Credential helper or GIT_ASKPASS | https://token@github.com |
| Verify file config | grep '^KEY=' f | cut -d= -f1 | cat f or source f |
| Shell variable use | "$VAR" (quoted) | $VAR (unquoted) |