Skip to content

OWASP Top 10 Security Controls

1. SQL Injection Prevention

  • ORM: SQLModel/SQLAlchemy with parameterized queries
  • Never concatenate strings in SQL queries
  • Validate inputs with Pydantic schemas
# ✅ CORRECT - parameterized query
result = await conn.execute(
    "SELECT * FROM shipments WHERE tenant_id = $1",
    [tenant_id]
)

# ❌ WRONG - string concatenation
result = await conn.execute(
    f"SELECT * FROM shipments WHERE tenant_id = {tenant_id}"
)

2. XSS Sanitization

  • Sanitize all user inputs
  • Output encoding in responses
  • No HTML/JavaScript in inputs
import html
def sanitize_input(text: str) -> str:
    return html.escape(text)

3. CSRF Protection

  • Double-submit cookie pattern for sensitive forms
  • Tokens in forms
  • Verify Origin/Referer headers

4. Rate Limiting

Per tenant_id and endpoint:

Tier Limit
STARTER 100/min
GROWTH 500/min
SCALE 1000/min
ENTERPRISE Custom

Public tracking: 60 req/min/IP

RATE_LIMITS = {
    "STARTER": 100,
    "GROWTH": 500,
    "SCALE": 1000,
    "ENTERPRISE": None,
}

Headers: X-RateLimit-Limit, X-RateLimit-Remaining


5. CORS Configuration

CORSPolicy(
    allow_origins=["https://saas-courier.com"],
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_credentials=True,
    same_site="strict",
)

6. Input Validation

  • Pydantic schemas for all inputs
  • Validation at presentation layer before domain
  • String sanitization
class ShipmentCreateSchema(BaseModel):
    sender_name: str = Field(max_length=255)
    recipient_name: str = Field(max_length=255)
    tracking_id: str = Field(pattern=r"^TRK-[A-Z0-9]{8}$")

7. JWT Authentication

ACCESS_TOKEN_EXPIRE_MINUTES = 15
REFRESH_TOKEN_EXPIRE_DAYS = 7

# Token contains:
# - sub: user_id
# - tenant_id: UUID
# - exp: expiration timestamp

8. Password Storage

  • bcrypt with automatic salt
  • Minimum complexity: 8 chars, upper, lower, number
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(password: str, hashed: str) -> bool:
    return pwd_context.verify(password, hashed)

9. Sensitive Data Protection

  • Environment variables for secrets
  • No secrets in logs
  • No passwords/tokens in responses
# .env - NEVER commit
SECRET_KEY=your-secret-key
DATABASE_URL=postgresql://...

10. Audit Logging

Log all actions with: - User ID - Action performed - Entity affected - Timestamp

Never log: - Passwords - Tokens - Personal data


Testing

Run security tests:

cd saas-backend && uv run pytest tests/security/ -v

Tenant isolation tests:

uv run pytest tests/security/test_tenant_isolation.py -v