Initial commit: 9 security patterns for code review
Fundamentals: secure-defaults, input-validation, credential-handling, audit-logging Identity: authentication, authorization Attack Prevention: injection-prevention, dos-prevention, prompt-injection
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# Credential Handling
|
||||
|
||||
## Rule
|
||||
|
||||
Never hardcode secrets. Load from environment or secret manager at runtime.
|
||||
|
||||
**Source:** [CWE-798: Use of Hard-coded Credentials](https://cwe.mitre.org/data/definitions/798.html)
|
||||
|
||||
## Correct Pattern
|
||||
|
||||
```python
|
||||
import os
|
||||
from functools import lru_cache
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_api_key() -> str:
|
||||
"""Load API key from environment. Fail fast if missing."""
|
||||
key = os.environ.get("API_KEY")
|
||||
if not key:
|
||||
raise RuntimeError("API_KEY environment variable not set")
|
||||
return key
|
||||
|
||||
# For cloud environments, use secret manager
|
||||
def get_secret(name: str) -> str:
|
||||
"""Load secret from cloud secret manager."""
|
||||
from google.cloud import secretmanager
|
||||
client = secretmanager.SecretManagerServiceClient()
|
||||
response = client.access_secret_version(name=name)
|
||||
return response.payload.data.decode("UTF-8")
|
||||
```
|
||||
|
||||
## Incorrect Pattern
|
||||
|
||||
```python
|
||||
# Wrong: hardcoded secret
|
||||
API_KEY = "sk-1234567890abcdef"
|
||||
|
||||
# Wrong: secret in config file checked into git
|
||||
config = {"api_key": "sk-1234567890abcdef"}
|
||||
|
||||
# Wrong: secret in default argument
|
||||
def call_api(key="sk-1234567890abcdef"):
|
||||
...
|
||||
|
||||
# Wrong: secret in error message
|
||||
def validate_key(key):
|
||||
if key != expected_key:
|
||||
raise ValueError(f"Invalid key: {key}") # Leaks the key!
|
||||
|
||||
# Wrong: secret in log
|
||||
logging.info(f"Using API key: {api_key}")
|
||||
```
|
||||
|
||||
## Secret Detection
|
||||
|
||||
Block these patterns in CI:
|
||||
|
||||
```python
|
||||
import re
|
||||
|
||||
SECRET_PATTERNS = [
|
||||
r'(?i)(api[_-]?key|apikey)\s*[=:]\s*["\'][^"\']+["\']',
|
||||
r'(?i)(secret|password|passwd|pwd)\s*[=:]\s*["\'][^"\']+["\']',
|
||||
r'(?i)bearer\s+[a-zA-Z0-9_-]+',
|
||||
r'sk-[a-zA-Z0-9]{32,}', # OpenAI-style keys
|
||||
r'ghp_[a-zA-Z0-9]{36}', # GitHub PAT
|
||||
]
|
||||
|
||||
def scan_for_secrets(content: str) -> list[str]:
|
||||
findings = []
|
||||
for pattern in SECRET_PATTERNS:
|
||||
if re.search(pattern, content):
|
||||
findings.append(f"Potential secret: {pattern}")
|
||||
return findings
|
||||
```
|
||||
|
||||
## Environment Separation
|
||||
|
||||
| Environment | Source | Notes |
|
||||
|-------------|--------|-------|
|
||||
| Development | `.env` file (gitignored) | Never commit |
|
||||
| CI | CI secrets / vault | Injected at runtime |
|
||||
| Production | Secret manager | Rotated automatically |
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- Secrets in Docker build args leak to image history
|
||||
- Environment variables visible in `/proc` on Linux
|
||||
- Secrets in URLs get logged by proxies/load balancers
|
||||
- Clipboard managers may capture pasted secrets
|
||||
Reference in New Issue
Block a user