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,142 @@
|
||||
# Secure Defaults
|
||||
|
||||
## Rule
|
||||
|
||||
Fail closed. Deny by default. Make the secure path the easy path.
|
||||
|
||||
**Source:** [OWASP Secure Design Principles](https://wiki.owasp.org/index.php/Security_by_Design_Principles)
|
||||
|
||||
## Fail Closed
|
||||
|
||||
### Correct Pattern
|
||||
|
||||
```python
|
||||
def check_access(user_id: str, resource_id: str) -> bool:
|
||||
"""Default deny — return False on any error."""
|
||||
try:
|
||||
permissions = get_permissions(user_id, resource_id)
|
||||
return "read" in permissions
|
||||
except Exception:
|
||||
# Log the error for debugging
|
||||
logging.exception("Permission check failed")
|
||||
# But deny access — fail closed
|
||||
return False
|
||||
|
||||
def process_request(request):
|
||||
"""Handle errors by denying, not allowing."""
|
||||
try:
|
||||
validate_request(request)
|
||||
return handle_request(request)
|
||||
except ValidationError as e:
|
||||
return {"error": str(e)}, 400
|
||||
except Exception:
|
||||
# Unknown error — don't leak info, don't allow access
|
||||
logging.exception("Unexpected error")
|
||||
return {"error": "Internal error"}, 500
|
||||
```
|
||||
|
||||
### Incorrect Pattern
|
||||
|
||||
```python
|
||||
# Wrong: fail open
|
||||
def check_access(user_id, resource_id):
|
||||
try:
|
||||
return has_permission(user_id, resource_id)
|
||||
except Exception:
|
||||
return True # "Let them in if something breaks"
|
||||
|
||||
# Wrong: exception = success
|
||||
try:
|
||||
verify_signature(token)
|
||||
except:
|
||||
pass # Signature verification bypassed!
|
||||
```
|
||||
|
||||
## Deny by Default
|
||||
|
||||
```python
|
||||
# Correct: explicit allowlist
|
||||
ALLOWED_ORIGINS = {"https://app.example.com", "https://admin.example.com"}
|
||||
|
||||
def check_cors(origin: str) -> bool:
|
||||
return origin in ALLOWED_ORIGINS
|
||||
|
||||
# Wrong: blocklist approach
|
||||
BLOCKED_ORIGINS = {"http://evil.com"}
|
||||
|
||||
def check_cors(origin: str) -> bool:
|
||||
return origin not in BLOCKED_ORIGINS # New attacks bypass this
|
||||
```
|
||||
|
||||
## Secure Configuration
|
||||
|
||||
```python
|
||||
# Correct: secure defaults, explicit opt-out
|
||||
class SecurityConfig:
|
||||
https_only: bool = True
|
||||
csrf_protection: bool = True
|
||||
content_security_policy: str = "default-src 'self'"
|
||||
cookie_secure: bool = True
|
||||
cookie_httponly: bool = True
|
||||
cookie_samesite: str = "Strict"
|
||||
|
||||
# Wrong: insecure defaults
|
||||
class Config:
|
||||
debug: bool = True # Should be False
|
||||
verify_ssl: bool = False # Should be True
|
||||
allow_all_origins: bool = True # Should be False
|
||||
```
|
||||
|
||||
## Least Privilege
|
||||
|
||||
```python
|
||||
# Correct: minimal permissions
|
||||
def create_db_connection():
|
||||
return connect(
|
||||
user="app_readonly", # Not root
|
||||
database="app_db",
|
||||
# Only needed permissions
|
||||
)
|
||||
|
||||
# Service accounts should have minimal scope
|
||||
SERVICE_ACCOUNT_PERMISSIONS = [
|
||||
"storage.objects.get",
|
||||
"storage.objects.list",
|
||||
# NOT: "storage.admin"
|
||||
]
|
||||
```
|
||||
|
||||
## Defense in Depth
|
||||
|
||||
```python
|
||||
class SecureEndpoint:
|
||||
"""Multiple layers of security."""
|
||||
|
||||
def handle(self, request):
|
||||
# Layer 1: Rate limiting
|
||||
if not self.rate_limiter.allow(request.ip):
|
||||
raise TooManyRequests()
|
||||
|
||||
# Layer 2: Authentication
|
||||
user = self.authenticate(request)
|
||||
if not user:
|
||||
raise Unauthorized()
|
||||
|
||||
# Layer 3: Authorization
|
||||
if not self.authorize(user, request.resource):
|
||||
raise Forbidden()
|
||||
|
||||
# Layer 4: Input validation
|
||||
data = self.validate(request.data)
|
||||
|
||||
# Layer 5: Business logic with validated data
|
||||
return self.process(user, data)
|
||||
```
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- Feature flags that disable security controls
|
||||
- Debug endpoints left enabled in production
|
||||
- Default passwords in documentation
|
||||
- Verbose error messages in production
|
||||
- Commented-out security checks
|
||||
Reference in New Issue
Block a user