Files
security-patterns/secure-defaults.md
T
Rodin 647928a0a1 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
2026-05-10 22:45:03 -07:00

3.5 KiB

Secure Defaults

Rule

Fail closed. Deny by default. Make the secure path the easy path.

Source: OWASP Secure Design Principles

Fail Closed

Correct Pattern

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

# 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

# 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

# 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

# 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

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