🛡️ Sentinel: [Security Enhancement] Add security middleware to API template (#154)

* feat: add security middleware to REST API template

Adds `TrustedHostMiddleware` and `CORSMiddleware` to the FastAPI template to ensure basic security protections are in place. Includes comments guiding users on how to configure these for production.

- Added TrustedHostMiddleware for Host header validation
- Added CORSMiddleware for Cross-Origin Resource Sharing
- Added TODOs for production configuration

* feat: add security middleware to REST API template

Adds `TrustedHostMiddleware` and `CORSMiddleware` to the FastAPI template to ensure basic security protections are in place. Includes comments guiding users on how to configure these for production.

- Added TrustedHostMiddleware for Host header validation
- Added CORSMiddleware for Cross-Origin Resource Sharing
- Configured safe defaults (allow_credentials=False) for the template
- Added TODOs for production configuration

* feat: secure API template and fix Pydantic deprecations

Enhances `rest-api-template.py` with standard security middleware and updates Pydantic usage to V2 standards.

- Added `TrustedHostMiddleware` and `CORSMiddleware` with safe defaults
- Updated Pydantic models to use `ConfigDict` and `model_dump()` to resolve deprecation warnings
- Documented security learnings in sentinel journal

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
google-labs-jules[bot]
2025-12-22 09:51:51 -05:00
committed by GitHub
parent a86384334b
commit 12f3ff4555
2 changed files with 27 additions and 6 deletions

4
.jules/sentinel.md Normal file
View File

@@ -0,0 +1,4 @@
## 2024-05-23 - Default Security Middleware in FastAPI Templates
**Vulnerability:** Missing default security headers and CORS configuration in API templates.
**Learning:** Developers often copy templates directly into production. If templates lack security headers by default, new services will be insecure.
**Prevention:** Always include `TrustedHostMiddleware` and `CORSMiddleware` in API templates with strict comments on how to configure them for production. Use safe defaults where possible, but permissive defaults with warnings (like `*`) are acceptable for templates if clearly marked with TODOs.

View File

@@ -4,8 +4,10 @@ Includes pagination, filtering, error handling, and best practices.
""" """
from fastapi import FastAPI, HTTPException, Query, Path, Depends, status from fastapi import FastAPI, HTTPException, Query, Path, Depends, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field, EmailStr from pydantic import BaseModel, Field, EmailStr, ConfigDict
from typing import Optional, List, Any from typing import Optional, List, Any
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
@@ -16,6 +18,22 @@ app = FastAPI(
docs_url="/api/docs" docs_url="/api/docs"
) )
# Security Middleware
# Trusted Host: Prevents HTTP Host Header attacks
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["*"] # TODO: Configure this in production, e.g. ["api.example.com"]
)
# CORS: Configures Cross-Origin Resource Sharing
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # TODO: Update this with specific origins in production
allow_credentials=False, # TODO: Set to True if you need cookies/auth headers, but restrict origins
allow_methods=["*"],
allow_headers=["*"],
)
# Models # Models
class UserStatus(str, Enum): class UserStatus(str, Enum):
ACTIVE = "active" ACTIVE = "active"
@@ -40,8 +58,7 @@ class User(UserBase):
created_at: datetime created_at: datetime
updated_at: datetime updated_at: datetime
class Config: model_config = ConfigDict(from_attributes=True)
from_attributes = True
# Pagination # Pagination
class PaginationParams(BaseModel): class PaginationParams(BaseModel):
@@ -74,7 +91,7 @@ async def http_exception_handler(request, exc):
error=exc.__class__.__name__, error=exc.__class__.__name__,
message=exc.detail if isinstance(exc.detail, str) else exc.detail.get("message", "Error"), message=exc.detail if isinstance(exc.detail, str) else exc.detail.get("message", "Error"),
details=exc.detail.get("details") if isinstance(exc.detail, dict) else None details=exc.detail.get("details") if isinstance(exc.detail, dict) else None
).dict() ).model_dump()
) )
# Endpoints # Endpoints
@@ -96,7 +113,7 @@ async def list_users(
status=UserStatus.ACTIVE, status=UserStatus.ACTIVE,
created_at=datetime.now(), created_at=datetime.now(),
updated_at=datetime.now() updated_at=datetime.now()
).dict() ).model_dump()
for i in range((page-1)*page_size, min(page*page_size, total)) for i in range((page-1)*page_size, min(page*page_size, total))
] ]
@@ -147,7 +164,7 @@ async def update_user(user_id: str, update: UserUpdate):
existing = await get_user(user_id) existing = await get_user(user_id)
# Apply updates # Apply updates
update_data = update.dict(exclude_unset=True) update_data = update.model_dump(exclude_unset=True)
for field, value in update_data.items(): for field, value in update_data.items():
setattr(existing, field, value) setattr(existing, field, value)