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 cache
  • fixtures/, 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 SC013

Skip entire security group

pycmdcheck --skip-group security

Best practices

  1. Never commit secrets: Add .env files to .gitignore
  2. Use .env.example: Provide a template without real values
  3. Rotate on exposure: If secrets are committed, rotate immediately
  4. Use pre-commit hooks: Add secret detection to pre-commit
  5. Audit git history: Use tools like git-secrets or trufflehog

Add pre-commit hook

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets