npx skills add ...
npx skills add bitwarden/ai-plugins --skill detecting-secrets
npx skills add bitwarden/ai-plugins --skill detecting-secrets
This skill should be used when the user asks to "find hardcoded secrets", "audit for credential leaks", "check for API keys in code", "review secret scanning alerts", "rotate a leaked secret", or needs to detect hardcoded credentials, review secret handling patterns, or remediate exposed secrets.
Look for these categories of hardcoded secrets in code:
| Type | Example Patterns |
|---|---|
| API Keys | AKIA[0-9A-Z]{16} (AWS), AIza[0-9A-Za-z_-]{35} (Google), strings assigned to variables named *apiKey*, *api_key* |
| Connection Strings | Server=...;Password=..., mongodb://user:pass@host, postgres://user:pass@host |
| Private Keys | -----BEGIN RSA PRIVATE KEY-----, -----BEGIN OPENSSH PRIVATE KEY----- |
| Tokens | ghp_[A-Za-z0-9]{36} (GitHub PAT), xoxb- (Slack bot), sk- (OpenAI) |
| Passwords | Values assigned to variables named *password*, *passwd*, *secret*, *credential* |
| Certificates | PFX/P12 files with embedded passwords, PEM files with private keys |
https://user:pass@host)Distinguish real secrets from false positives. Not every pattern match indicates an actual secret — consider context:
Decision criteria:
**/test/**, **/tests/**, **/*.Test/**)?$2b$, argon2 $argon2id$) are NOT secrets — they're properly storedSearch these locations when auditing for secrets:
| Location | What to Look For |
|---|---|
appsettings.json / appsettings.Development.json | Connection strings, API keys, service credentials |
.env / .env.local | Environment variable definitions with real values |
web.config / app.config | Machine keys, connection strings |
docker-compose.yml / Dockerfile | ENV directives with credentials, build args with secrets |
CI/CD files (.github/workflows/*.yml) | Inline secrets instead of ${{ secrets.* }} references |
| Test seed scripts / migration files | Database passwords, service account credentials |
| Comments and TODO notes | "Temporary" credentials left in comments |
| Default parameter values | function connect(password = "admin123") |
| Constants files | Centralized credential definitions |
Push protection prevents commits containing detected secrets from being pushed. When someone bypasses push protection, the alert is flagged — review these with extra scrutiny.
When a secret is found in code, follow this sequence:
Assume any committed secret is compromised. Even if the repo is private, the secret may have been cached, logged, or accessed by CI/CD systems.
Replace the hardcoded secret with a secure reference:
If the secret was committed to a public repo or a repo that will become public:
Warning: Rewriting git history is destructive and affects all collaborators. Only do this when the secret was exposed in a public or soon-to-be-public repository.
.gitignore for files that should never be committed (.env, *.pfx, appsettings.Development.json)Bitwarden uses Azure Key Vault for secrets management, provisioned by the BRE team:
| Instead Of | Use |
|---|---|
| Hardcoded connection strings | Azure Key Vault secrets |
| API keys in config files | Environment variables set at deployment |
| Certificates in source | Azure Key Vault certificates |
| Shared team credentials in code | Managed identities (Azure) |
| Secrets in CI/CD workflow files | GitHub Actions secrets (${{ secrets.NAME }}) |
For local development, use user-secrets or .env files that are .gitignored — never commit them.
git log -p -S "secret-pattern" to search history.// NOT a real secret — documented example
{
"apiKey": "YOUR_API_KEY_HERE"
}
// REAL secret — actual value in config
{
"apiKey": "sk-proj-abc123def456ghi789jkl012mno345pqr678stu901vwx"
}# List all secret scanning alerts
gh api /repos/{owner}/{repo}/secret-scanning/alerts --jq '.[] | {number, state, secret_type, secret_type_display_name, created_at, push_protection_bypassed}'
# Get details for a specific alert
gh api /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}
# List alerts that bypassed push protection
gh api "/repos/{owner}/{repo}/secret-scanning/alerts?state=open" --jq '.[] | select(.push_protection_bypassed == true)'// WRONG — hardcoded secret
var connectionString = "Server=prod.db;Password=s3cr3t!";
// CORRECT — environment variable
var connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
// CORRECT — Azure Key Vault (Bitwarden's approach)
var connectionString = await keyVaultClient.GetSecretAsync("db-connection-string");# Using git filter-repo (preferred over filter-branch)
git filter-repo --path-glob '*.json' --replace-text expressions.txt
# expressions.txt format:
# literal:the-secret-value==>REDACTED