SC013: SecretsDetection
Overview
| Property | Value |
|---|---|
| ID | SC013 |
| Name | SecretsDetection |
| Group | security |
| Severity | ERROR |
Description
Scans Python files and configuration files for hardcoded API keys, tokens, private keys, and database URLs with embedded passwords.
Hardcoded secrets are dangerous because:
- They can be accidentally committed to version control
- They may be exposed in public repositories
- They create security vulnerabilities in your application
- They make secret rotation difficult
What it checks
The check scans for multiple secret patterns:
| Pattern | Description |
|---|---|
| AWS Access Keys | AKIA followed by 16 alphanumeric characters |
| API Keys | api_key, api-key assignments with 20+ char values |
| Private Keys | RSA, DSA, EC, OPENSSH private key headers |
| Database URLs | mysql://, postgres://, mongodb:// with embedded passwords |
| Secret Keys | secret_key assignments with 10+ char values |
Files scanned
- Python files (
*.py) - Configuration files (
*.yaml,*.yml,*.json,*.toml,*.cfg,*.ini) - Environment example files (
.env.example)
Directories excluded
tests/,test/- Test directories.venv/- Virtual environments__pycache__/- Python cachefixtures/,test_fixtures/- Test fixture directories
Result states
- PASSED: No hardcoded secrets found
- FAILED: Secrets detected with file, line number, and type
How to fix
Use environment variables
# Bad: hardcoded API key
api_key = "sk-1234567890abcdef1234567890abcdef"
# Good: environment variable
import os
api_key = os.environ["API_KEY"]Use a secrets manager
# AWS Secrets Manager
import boto3
client = boto3.client("secretsmanager")
secret = client.get_secret_value(SecretId="my-api-key")
# HashiCorp Vault
import hvac
client = hvac.Client(url="https://vault.example.com")
secret = client.secrets.kv.v2.read_secret_version(path="my-secret")Use python-dotenv for local development
# .env file (add to .gitignore!)
# API_KEY=sk-1234567890abcdef1234567890abcdef
from dotenv import load_dotenv
load_dotenv()
api_key = os.environ["API_KEY"]Use pydantic-settings
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
api_key: str
database_url: str
class Config:
env_file = ".env"
settings = Settings()Remove hardcoded database URLs
# Bad: password in URL
DATABASE_URL = "postgres://user:secretpassword@localhost/db"
# Good: components from environment
import os
DB_USER = os.environ["DB_USER"]
DB_PASS = os.environ["DB_PASS"]
DB_HOST = os.environ["DB_HOST"]
DB_NAME = os.environ["DB_NAME"]
DATABASE_URL = f"postgres://{DB_USER}:{DB_PASS}@{DB_HOST}/{DB_NAME}"Protect private keys
# Bad: hardcoded private key
PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----"""
# Good: load from file (outside repo)
from pathlib import Path
PRIVATE_KEY = Path(os.environ["PRIVATE_KEY_PATH"]).read_text()Why ERROR severity?
This check is an ERROR because:
- Exposed secrets can lead to immediate security breaches
- Attackers actively scan public repositories for secrets
- Credential stuffing attacks are common
- Leaked secrets are expensive and time-consuming to rotate
Configuration
Skip this check
[tool.pycmdcheck]
skip = ["SC013"]CLI
pycmdcheck --skip SC013Skip entire security group
pycmdcheck --skip-group securityBest practices
- Never commit secrets: Add
.envfiles to.gitignore - Use .env.example: Provide a template without real values
- Rotate on exposure: If secrets are committed, rotate immediately
- Use pre-commit hooks: Add secret detection to pre-commit
- Audit git history: Use tools like
git-secretsortrufflehog
Add pre-commit hook
# .pre-commit-config.yaml
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets