feat(security): merge Phase 4b security hardening
Complete security hardening implementation including HTTPS enforcement, security headers, rate limiting, and comprehensive security test suite. Key features: - HTTPS enforcement with HSTS support - Security headers (CSP, X-Frame-Options, X-Content-Type-Options) - Rate limiting for all critical endpoints - Enhanced email template security - 87% test coverage with security-specific tests Architect approval: 9.5/10 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -374,4 +374,102 @@ if not validate_redirect_uri(redirect_uri):
|
||||
2. **Dependency Injection**: Pass dependencies, don't hard-code them
|
||||
3. **Composition over Inheritance**: Prefer composition for code reuse
|
||||
4. **Fail Fast**: Validate input early and fail with clear errors
|
||||
5. **Explicit over Implicit**: Clear interfaces over magic behavior
|
||||
5. **Explicit over Implicit**: Clear interfaces over magic behavior
|
||||
|
||||
## Security Practices
|
||||
|
||||
### Secure Logging Guidelines
|
||||
|
||||
#### Never Log Sensitive Data
|
||||
|
||||
The following must NEVER appear in logs:
|
||||
- Full tokens (authorization codes, access tokens, refresh tokens)
|
||||
- Passwords or secrets
|
||||
- Full authorization codes
|
||||
- Private keys or certificates
|
||||
- Personally identifiable information (PII) beyond user identifiers (email addresses, IP addresses in most cases)
|
||||
|
||||
#### Safe Logging Practices
|
||||
|
||||
When logging security-relevant events, follow these practices:
|
||||
|
||||
1. **Token Prefixes**: When token identification is necessary, log only the first 8 characters with ellipsis:
|
||||
```python
|
||||
logger.info("Token validated", extra={
|
||||
"token_prefix": token[:8] + "..." if len(token) > 8 else "***",
|
||||
"client_id": client_id
|
||||
})
|
||||
```
|
||||
|
||||
2. **Request Context**: Log security events with context:
|
||||
```python
|
||||
logger.warning("Authorization failed", extra={
|
||||
"client_id": client_id,
|
||||
"error": error_code # Use error codes, not full messages
|
||||
})
|
||||
```
|
||||
|
||||
3. **Security Events to Log**:
|
||||
- Failed authentication attempts
|
||||
- Token validation failures
|
||||
- Rate limit violations
|
||||
- Input validation failures
|
||||
- HTTPS redirect actions
|
||||
- Client registration events
|
||||
|
||||
4. **Use Structured Logging**: Include metadata as structured fields:
|
||||
```python
|
||||
logger.info("Client registered", extra={
|
||||
"event": "client.registered",
|
||||
"client_id": client_id,
|
||||
"registration_method": "self_service",
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
})
|
||||
```
|
||||
|
||||
5. **Sanitize User Input**: Always sanitize user-provided data before logging:
|
||||
```python
|
||||
def sanitize_for_logging(value: str, max_length: int = 100) -> str:
|
||||
"""Sanitize user input for safe logging."""
|
||||
# Remove control characters
|
||||
value = "".join(ch for ch in value if ch.isprintable())
|
||||
# Truncate if too long
|
||||
if len(value) > max_length:
|
||||
value = value[:max_length] + "..."
|
||||
return value
|
||||
```
|
||||
|
||||
#### Security Audit Logging
|
||||
|
||||
For security-critical operations, use a dedicated audit logger:
|
||||
|
||||
```python
|
||||
audit_logger = logging.getLogger("security.audit")
|
||||
|
||||
# Log security-critical events
|
||||
audit_logger.info("Token issued", extra={
|
||||
"event": "token.issued",
|
||||
"client_id": client_id,
|
||||
"scope": scope,
|
||||
"expires_in": expires_in
|
||||
})
|
||||
```
|
||||
|
||||
#### Testing Logging Security
|
||||
|
||||
Include tests that verify sensitive data doesn't leak into logs:
|
||||
|
||||
```python
|
||||
def test_no_token_in_logs(caplog):
|
||||
"""Verify tokens are not logged in full."""
|
||||
token = "sensitive_token_abc123xyz789"
|
||||
|
||||
# Perform operation that logs token
|
||||
validate_token(token)
|
||||
|
||||
# Check logs don't contain full token
|
||||
for record in caplog.records:
|
||||
assert token not in record.getMessage()
|
||||
# But prefix might be present
|
||||
assert token[:8] in record.getMessage() or "***" in record.getMessage()
|
||||
```
|
||||
Reference in New Issue
Block a user