spec-flow
Version:
Spec-Driven Development workflow toolkit for Claude Code - Build high-quality features faster with repeatable AI workflows
548 lines (399 loc) • 13.2 kB
Markdown
# Security Guidelines: Preventing Secret Exposure in Documentation
**Version**: 1.0.0
**Last Updated**: 2025-10-28
**Audience**: Workflow contributors, command authors, agent developers
---
## Overview
This document provides security guidelines for preventing secret exposure in Spec-Flow documentation, reports, and artifacts. Following these guidelines ensures that sensitive information never makes it into version control or public documentation.
## What Constitutes a Secret?
### High-Risk Secrets (Never expose)
1. **API Keys**
- `OPENAI_API_KEY=sk-...`
- `api_key=abc123xyz`
- Any key for external services (Stripe, SendGrid, etc.)
2. **Authentication Tokens**
- Bearer tokens
- JWT tokens (`eyJ...`)
- OAuth tokens
- Session tokens
3. **Passwords**
- Database passwords
- Admin passwords
- Service account passwords
- `SECRET_KEY` values
4. **Database Connection Strings**
- `postgresql://user:password@host/db`
- `mysql://admin:secret@db.example.com`
- Any URL with embedded credentials
5. **Deployment Tokens**
- `VERCEL_TOKEN`
- `RAILWAY_TOKEN`
- `GITHUB_TOKEN`
- `DOPPLER_TOKEN`
6. **Private Keys**
- SSH private keys
- TLS/SSL private keys
- PGP private keys
- Signing keys
7. **Cloud Provider Credentials**
- AWS access keys (`AKIA...`)
- AWS secret keys
- GCP service account keys
- Azure credentials
### Medium-Risk Information (Context-dependent)
1. **Deploy IDs**
- Vercel deployment IDs (publicly accessible but can be sensitive)
- Railway deployment IDs
- Internal deployment identifiers
2. **URLs with Query Params**
- `https://api.com/endpoint?key=abc123` (key might be sensitive)
- URLs with embedded tokens or session IDs
3. **Environment-Specific URLs**
- Internal API endpoints
- Staging/development URLs (if not public)
### Safe Information (OK to document)
1. **Environment Variable Names**
- `DATABASE_URL` (name only, not value)
- `OPENAI_API_KEY` (name only)
- `NEXT_PUBLIC_API_URL` (name only)
2. **Public URLs**
- `https://example.com`
- `https://api.example.com/docs`
- Production URLs (without embedded secrets)
3. **Code Structure**
- File paths (`src/services/auth.ts`)
- Function names (`authenticate()`)
- Line numbers (`:42`)
4. **Metrics**
- Performance scores (Lighthouse: 95)
- Test coverage (89%)
- Response times (145ms)
---
## Sanitization Strategies
### 1. Use Placeholders
Replace actual values with clear placeholders:
```markdown
❌ BAD:
DATABASE_URL=postgresql://admin:MyP@ssw0rd@db.railway.app/prod
✅ GOOD:
DATABASE_URL=***REDACTED***
or
DATABASE_URL=[from environment - see Doppler]
```
### 2. Extract Domains Only
For URLs, show domain without credentials:
```markdown
❌ BAD:
API_URL: https://user:token123@api.internal.com/v1
✅ GOOD:
API_URL domain: api.internal.com (full URL in Doppler)
or
API_URL: https://***:***@api.internal.com/v1
```
### 3. Use Environment Variables
Reference env vars by name, not value:
```bash
❌ BAD:
export VERCEL_TOKEN=abc123xyz789
✅ GOOD:
export VERCEL_TOKEN=$(doppler secrets get VERCEL_TOKEN --plain)
# or
# VERCEL_TOKEN should be set in environment
```
### 4. Sanitize Command Output
Before writing to reports, sanitize output:
```bash
# Source sanitization utility
source .spec-flow/scripts/bash/sanitize-secrets.sh
# Generate report
REPORT_CONTENT=$(generate_report)
# Sanitize before writing
CLEAN_CONTENT=$(sanitize_secrets <<< "$REPORT_CONTENT")
# Write to file
echo "$CLEAN_CONTENT" > "$FEATURE_DIR/report.md"
```
---
## Using Sanitization Tools
### Bash Sanitization
**Script**: `.spec-flow/scripts/bash/sanitize-secrets.sh`
**Usage**:
```bash
# Sanitize stdin
echo "API_KEY=abc123" | .spec-flow/scripts/bash/sanitize-secrets.sh
# Sanitize file
.spec-flow/scripts/bash/sanitize-secrets.sh < input.md > output.md
# Source in scripts
source .spec-flow/scripts/bash/sanitize-secrets.sh
CLEAN=$(sanitize_secrets <<< "$DIRTY_CONTENT")
```
**Patterns detected**:
- API keys (`api_key`, `apikey`, `api-key`)
- Tokens (`token`, `bearer`, `auth_token`)
- Passwords (`password`, `pwd`, `passwd`)
- Database URLs (`postgresql://`, `mysql://`, `mongodb://`)
- URLs with credentials (`https://user:pass@host`)
- JWT tokens (`eyJ...`)
- AWS keys (`AKIA...`)
- GitHub tokens (`ghp_`, `gho_`)
- Deployment tokens (`vercel_token`, `railway_token`)
- Private keys (`-----BEGIN PRIVATE KEY-----`)
### PowerShell Sanitization
**Script**: `.spec-flow/scripts/powershell/Sanitize-Secrets.ps1`
**Usage**:
```powershell
# Sanitize string
Sanitize-Secrets -InputText "API_KEY=abc123"
# Sanitize file
Sanitize-Secrets -Path "report.md"
# Pipeline
Get-Content "input.md" | Sanitize-Secrets | Set-Content "output.md"
# In scripts
$cleanContent = $dirtyContent | Sanitize-Secrets
```
---
## Secret Detection Testing
### Automated Scanning
**Script**: `.spec-flow/scripts/bash/test-secret-detection.sh`
**Usage**:
```bash
# Scan all reports
bash .spec-flow/scripts/bash/test-secret-detection.sh
# Scan specific feature
bash .spec-flow/scripts/bash/test-secret-detection.sh specs/001-feature
# Pre-commit hook (recommended)
# Add to .git/hooks/pre-commit:
bash .spec-flow/scripts/bash/test-secret-detection.sh
```
**Exit codes**:
- `0` - No secrets detected (safe)
- `1` - Secrets detected (blocked)
- `2` - Error running scan
**Output**:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Secret Detection Scanner
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Found 15 files to scan
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
❌ Secrets detected in: specs/001-feature/ship-report.md
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Pattern: TOKEN
Line 45: vercel_token=[***REDACTED***]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Scan Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Files scanned: 15
Files with secrets: 1
Total secrets found: 1
❌ SECRETS DETECTED!
Action required:
1. Review the files listed above
2. Replace exposed secrets with placeholders
3. Re-run scan to verify fixes
```
---
## Guidelines for Command Authors
### When Writing Commands
1. **Never echo secret values**:
```bash
❌ echo "Token: $VERCEL_TOKEN"
✅ echo "Token: [set in environment]"
```
2. **Sanitize before writing to files**:
```bash
❌ echo "$OUTPUT" > report.md
✅ echo "$(sanitize_secrets <<< "$OUTPUT")" > report.md
```
3. **Extract domains from URLs**:
```bash
❌ echo "API URL: $FULL_URL"
✅ URL_DOMAIN=$(echo "$FULL_URL" | sed -E 's|https?://([^/?]+).*|\1|')
✅ echo "API domain: $URL_DOMAIN"
```
4. **Use redirection carefully**:
```bash
# Avoid redirecting sensitive command output
❌ doppler secrets get DATABASE_URL --plain >> report.md
# Check presence, not value
✅ if doppler secrets get DATABASE_URL --plain >/dev/null 2>&1; then
✅ echo "✅ DATABASE_URL configured"
✅ fi
```
### When Creating Reports
1. **Use templates with placeholders**:
```markdown
❌ Deployed to: https://abc123.vercel.app?token=xyz789
✅ Deployed to: [deployment-url]
```
2. **Reference configuration locations**:
```markdown
❌ OPENAI_API_KEY=sk-abc123xyz789
✅ OPENAI_API_KEY=[configured in Doppler: cfipros/production_api]
```
3. **Document commands, not values**:
```markdown
❌ export DATABASE_URL=postgresql://user:pass@host/db
✅ export DATABASE_URL=$(doppler secrets get DATABASE_URL --plain)
```
---
## Guidelines for Agent Developers
### Security Section Template
Add this section to all agent briefs that create reports:
```markdown
## SECURITY: SECRET SANITIZATION
**CRITICAL**: Before writing ANY content to report files or summaries:
**Never expose:**
- Environment variable VALUES (API keys, tokens, passwords)
- Database URLs with embedded credentials
- Deployment tokens (VERCEL_TOKEN, RAILWAY_TOKEN, GITHUB_TOKEN)
- URLs with secrets in query params
- Private keys or certificates
**Safe to include:**
- Environment variable NAMES
- URL domains without credentials
- Public deployment URLs (without embedded tokens)
- Status indicators (✅/❌)
**Use placeholders:**
- Replace actual values with `***REDACTED***`
- Use `[VARIABLE from environment]` for env vars
- Extract domains only: `https://user:pass@api.com` → `https://***:***@api.com`
**When in doubt:** Redact the value. Better to be overly cautious than expose secrets.
```
### Agent Checklist
Before finalizing an agent brief:
- [ ] Added SECURITY section
- [ ] Reviewed all examples for hardcoded secrets
- [ ] Verified report templates use placeholders
- [ ] Documented safe alternatives to exposing values
- [ ] Tested agent output with secret detection script
---
## Common Mistakes to Avoid
### 1. Echoing Doppler Output
```bash
❌ WRONG:
API_KEY=$(doppler secrets get API_KEY --plain)
echo "API Key: $API_KEY" # Exposes secret!
✅ RIGHT:
if doppler secrets get API_KEY --plain >/dev/null 2>&1; then
echo "✅ API_KEY configured"
else
echo "❌ API_KEY missing"
fi
```
### 2. Including Secrets in Error Messages
```bash
❌ WRONG:
if ! verify_connection "$DATABASE_URL"; then
echo "Failed to connect: $DATABASE_URL" # Exposes password!
fi
✅ RIGHT:
if ! verify_connection "$DATABASE_URL"; then
DB_HOST=$(echo "$DATABASE_URL" | sed -E 's|.*@([^/]+).*|\1|')
echo "Failed to connect to: $DB_HOST"
fi
```
### 3. Hardcoding URLs in Templates
```markdown
❌ WRONG (ship-report-template.md):
Deployed to: https://app.cfipros.com
✅ RIGHT:
Deployed to: [production-app-url]
```
### 4. Logging Full curl Commands
```bash
❌ WRONG:
echo "Running: curl -H 'Authorization: Bearer $TOKEN' $URL" # Exposes token!
✅ RIGHT:
echo "Running: curl -H 'Authorization: Bearer [REDACTED]' $URL"
```
---
## Testing Your Changes
### Before Committing
1. **Run secret detection**:
```bash
bash .spec-flow/scripts/bash/test-secret-detection.sh
```
2. **Review reports manually**:
```bash
grep -r "api_key\|token\|password" specs/*/artifacts/
```
3. **Test sanitization**:
```bash
# Create test input with fake secrets
echo "API_KEY=abc123" | .spec-flow/scripts/bash/sanitize-secrets.sh
# Verify output: API_KEY=***REDACTED***
```
### Pre-Commit Hook (Recommended)
Create `.git/hooks/pre-commit`:
```bash
#!/bin/bash
set -e
echo "Running secret detection scan..."
if ! bash .spec-flow/scripts/bash/test-secret-detection.sh; then
echo ""
echo "❌ Commit blocked: Secrets detected in files"
echo "Fix issues and try again"
exit 1
fi
echo "✅ No secrets detected - safe to commit"
```
Make it executable:
```bash
chmod +x .git/hooks/pre-commit
```
---
## Incident Response
### If Secrets Are Exposed
**Immediate actions**:
1. **Rotate compromised secrets**:
```bash
# Revoke old token
# Generate new token
# Update in Doppler
doppler secrets set EXPOSED_TOKEN=new_value
```
2. **Remove from git history**:
```bash
# Use git-filter-repo or BFG Repo-Cleaner
git filter-repo --path-glob '**/report.md' --invert-paths
```
3. **Force push (if not public)**:
```bash
git push --force origin main
```
4. **Document in security log**:
- What was exposed
- How it was exposed
- When it was discovered
- Actions taken
- Prevention measures added
**Prevention**:
- Run secret detection scan before every commit
- Review all new commands and agents for secret exposure
- Test report generation with sanitization
- Update this document with lessons learned
---
## Resources
**Scripts**:
- Bash sanitization: `.spec-flow/scripts/bash/sanitize-secrets.sh`
- PowerShell sanitization: `.spec-flow/scripts/powershell/Sanitize-Secrets.ps1`
- Secret detection: `.spec-flow/scripts/bash/test-secret-detection.sh`
**Templates**:
- Report templates: `.spec-flow/templates/*-template.md`
- Agent briefs: `.claude/agents/phase/*.md`
**Documentation**:
- Architecture: `docs/architecture.md`
- Commands: `docs/commands.md`
- Contributing: `CONTRIBUTING.md`
**External Resources**:
- [OWASP Secrets Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html)
- [GitHub Secret Scanning](https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning)
- [Doppler Best Practices](https://docs.doppler.com/docs/best-practices)
---
## Questions?
For security concerns or questions about this guide:
1. Review this document thoroughly
2. Check existing command/agent implementations
3. Test with sanitization tools before committing
4. Open an issue: https://github.com/anthropics/claude-code/issues
**Remember**: When in doubt, redact. It's better to be overly cautious with secrets than to expose sensitive information.