647928a0a1
Fundamentals: secure-defaults, input-validation, credential-handling, audit-logging Identity: authentication, authorization Attack Prevention: injection-prevention, dos-prevention, prompt-injection
91 lines
2.4 KiB
Markdown
91 lines
2.4 KiB
Markdown
# 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
|