agentsclimarketplace

Fastapi security

Skill hlsitechio/claude-skills-security/appsec-stack-pack/fastapi-security

Defensive security audit skills for Claude — tech-stack-keyed and audit-domain-keyed packs for SaaS apps.

Install
npx -y skills add hlsitechio/claude-skills-security --skill fastapi-security

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Security audit for FastAPI applications including dependency injection for auth, Pydantic schemas for input/output, OAuth2 scopes, async endpoint patterns, CORS middleware, SQL injection via SQLAlchemy raw queries, Starlette middleware, and FastAPI-specific patterns. Use this skill whenever the user mentions FastAPI, Pydantic, Starlette, OAuth2PasswordBearer, Depends, APIRouter, fastapi-users, SQLAlchemy in FastAPI, or asks "audit my FastAPI app", "FastAPI security review", "Pydantic safe". Trigger when the codebase contains `fastapi` in `requirements.txt` / `pyproject.toml`.

SKILL.md

8.4 KB, as published. Nobody here has run it

FastAPI Security Audit

Audit FastAPI applications. FastAPI sits on Starlette + Pydantic — secure defaults are good, but custom code often bypasses them.

When this skill applies

  • Reviewing FastAPI endpoints, dependencies, Pydantic models
  • Auditing OAuth2 / JWT / API key auth flows
  • Reviewing CORS, middleware, exception handlers
  • Checking SQLAlchemy / SQLModel usage for SQL injection
  • Reviewing async patterns for race conditions

Workflow

Follow ../_shared/audit-workflow.md.

Phase 1: Stack detection

grep -E '^fastapi|"fastapi"' requirements.txt pyproject.toml 2>/dev/null
python -c "import fastapi; print(fastapi.__version__)" 2>/dev/null

Phase 2: Inventory

# Route definitions
grep -rn '@app\.\|@router\.' . --include='*.py' | head -50

# Dependencies (DI)
grep -rn 'Depends(' . --include='*.py' | head -30

# Pydantic models (schemas)
grep -rn 'class .*BaseModel\|class .*pydantic' . --include='*.py' | head

# CORS / middleware
grep -rn 'add_middleware\|CORSMiddleware\|TrustedHostMiddleware' . --include='*.py'

# Raw SQL
grep -rn 'text(\|execute(\|raw_connection' . --include='*.py'

Phase 3: Detection — the checks

Pydantic schemas — input validation

  • FAP-PYD-1 Endpoints accepting request bodies declare a Pydantic model — never request: dict or request: Any.
  • FAP-PYD-2 Field constraints set: Field(min_length=..., max_length=..., gt=..., lt=...).
  • FAP-PYD-3 model_config = ConfigDict(extra='forbid') (Pydantic v2) or class Config: extra = 'forbid' (v1) — rejects unknown fields, preventing mass assignment.
  • FAP-PYD-4 Email fields use EmailStr; URLs use HttpUrl; UUIDs use UUID4.
from pydantic import BaseModel, ConfigDict, EmailStr, Field

class CreateUserIn(BaseModel):
    model_config = ConfigDict(extra='forbid')
    
    email: EmailStr
    password: str = Field(min_length=8, max_length=128)
    display_name: str = Field(min_length=1, max_length=50)
    # role is intentionally NOT here; set by server

Pydantic schemas — output filtering

  • FAP-PYD-5 Response model declared on endpoints to filter output:
    @app.get("/users/{user_id}", response_model=UserOut)
    
    Without response_model, the endpoint returns whatever the function returns, including hidden fields.
  • FAP-PYD-6 Distinct input/output models (UserIn, UserOut, UserInternal) — don't reuse the same model for both directions; secrets leak.
  • FAP-PYD-7 response_model_exclude / response_model_exclude_unset not used to "hide" sensitive fields — explicit models are safer.

Authentication

  • FAP-AUTH-1 Every protected route declares an auth dependency:
    @app.get("/me")
    async def me(user: User = Depends(get_current_user)):
        return user
    
  • FAP-AUTH-2 Global dependency for app-wide auth NOT mixed with per-route auth (confusing; pick one model).
  • FAP-AUTH-3 OAuth2PasswordBearer configured with tokenUrl matching the actual login endpoint.
  • FAP-AUTH-4 JWT validation: see saas-security-pack/saas-code-security-review/references/jwt-validation.md. Algorithm explicit, secret from env, expiry checked.
  • FAP-AUTH-5 Password hashing: passlib[bcrypt] or Argon2; not plain SHA-256.
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")

def verify_password(plain, hashed):
    return pwd_context.verify(plain, hashed)

Authorization

  • FAP-AUTHZ-1 Per-resource authz checked in the dependency or route body. Auth dependency returns User; the route must then check ownership / role for the specific resource.
  • FAP-AUTHZ-2 OAuth2 scopes (if used) checked via Security(get_current_user, scopes=[...]). Verify the dependency actually checks security_scopes.scopes.
  • FAP-AUTHZ-3 Admin routes guarded by a separate dependency, not just "is authenticated".
def require_admin(user: User = Depends(get_current_user)) -> User:
    if not user.is_admin:
        raise HTTPException(status_code=403, detail="Forbidden")
    return user

@app.delete("/admin/users/{user_id}")
async def delete_user(user_id: UUID, _: User = Depends(require_admin)):
    ...

CORS

  • FAP-CORS-1 CORSMiddleware with specific allow_origins, not ["*"] for credentialed APIs.
  • FAP-CORS-2 allow_credentials=True ONLY with explicit origin list; with *, FastAPI/Starlette will block credentials, but ensure not bypassed via regex.
  • FAP-CORS-3 allow_methods and allow_headers not blanket * if not needed.

Trusted host

  • FAP-TH-1 TrustedHostMiddleware with allowed_hosts list — protects against Host header injection.

SQL injection (SQLAlchemy, SQLModel)

  • FAP-SQL-1 session.execute(text("SELECT ...")) with f-strings or % formatting = injection. Use bound params:
    # BAD
    session.execute(text(f"SELECT * FROM users WHERE id = {user_id}"))
    
    # GOOD
    session.execute(text("SELECT * FROM users WHERE id = :id"), {"id": user_id})
    
  • FAP-SQL-2 SQLAlchemy Core / ORM filter(User.id == user_id) is parameterized — safe.
  • FAP-SQL-3 query.filter(text(...)) patterns reviewed.

NoSQL injection

If using MongoDB / Beanie / Motor:

  • FAP-NOSQL-1 Don't pass dict directly from request to query: users.find_one(request.json()) — attacker controls operators ({"$gt": ""}).

Async patterns

  • FAP-ASYNC-1 Endpoints declared async def use async DB clients; sync_def endpoints called via FastAPI's thread pool. Don't mix sync I/O in async endpoints (blocks event loop).
  • FAP-ASYNC-2 Shared mutable state (e.g., a module-level dict) accessed without locking → race conditions. Use Redis / DB / asyncio.Lock for shared state.

Background tasks

  • FAP-BG-1 BackgroundTasks for fire-and-forget; errors caught and logged, not silently swallowed.
  • FAP-BG-2 For long-running or critical background work, use Celery / Arq / Dramatiq — not BackgroundTasks (which dies with the worker process).

File uploads

  • FAP-UP-1 UploadFile reads streamed; size limits enforced (FastAPI doesn't enforce by default — use Starlette's request.body size limits or check size after read).
  • FAP-UP-2 Content type validated by magic bytes via python-magic or similar, not just file.content_type (client-controlled).
  • FAP-UP-3 File saved with sanitized filename (UUID + safe extension).

Error handling

  • FAP-ERR-1 Global exception handler in production hides stack traces.
  • FAP-ERR-2 HTTPException used for client errors; don't bubble Python exceptions verbatim.
  • FAP-ERR-3 Validation errors (422) don't echo full schema paths if they reveal internals — Pydantic v2 errors are typically OK, but customize if exposing column names becomes a concern.

OpenAPI / docs

  • FAP-DOC-1 /docs and /redoc disabled in production OR gated by auth:
    app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
    
    Or:
    app = FastAPI(openapi_url="/api/v1/openapi.json")
    # Then protect with router-level dependency
    
  • FAP-DOC-2 OpenAPI schema doesn't include example values that are real credentials.

Session and cookies

If using Starlette's SessionMiddleware:

  • FAP-SES-1 secret_key from env; not the example placeholder.
  • FAP-SES-2 same_site='lax', https_only=True in production.

Dependencies

  • FAP-DEP-1 FastAPI, Pydantic, Starlette versions current. Pydantic v2 is significantly different from v1; verify migration was complete.
  • FAP-DEP-2 pip-audit clean.

Phase 4: Triage

Critical: endpoint accepting arbitrary dict body without Pydantic; raw SQL with string formatting; CORS * with credentials; docs reachable in prod with sensitive endpoints visible.

Phase 5: Report

Use ../_shared/findings-schema.md. Prefix IDs with FAP-.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.