agentsclimarketplace

Fastapi attack probe

Skill Dolphinllc/claude-security-skills/skills/offensive/web/fastapi-attack-probe

Defensive security skills for Claude Code and the Claude Agent SDK — web applications and generative AI systems.

Install
npx -y skills add Dolphinllc/claude-security-skills --skill fastapi-attack-probe

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

Authorized self-pentest probe targeting FastAPI-specific weaknesses. Tests /docs and /redoc auth, OpenAPI schema enumeration, Pydantic boundary bypass via extra fields, missing Depends/Security on routes, JWT alg confusion, and unsafe file responses. Use when the user asks to "pentest" their own FastAPI app.

SKILL.md

5.5 KB, as published. Nobody here has run it

FastAPI Attack Probe

Authorized probe of a FastAPI 0.100+ app the user owns. Follow shared probing conventions — discover base URL from env (UVICORN_PORT, PORT), pyproject.toml task definitions, Dockerfile EXPOSE, or default uvicorn 8000. Never hardcode.

FastAPI-specific attack surface

  • /docs and /redoc are public by default — they expose the full route inventory and request schemas.
  • /openapi.json is the most efficient enumeration target — fetch it once and you have every route, method, parameter type, and security requirement.
  • Depends/Security is opt-in per route. A single missing Depends(get_current_user) on a mutating route is "anonymous admin" by default.
  • Pydantic v2 default extra='ignore' silently drops unknown fields; combined with response models it can leak fields not declared in the request schema.
  • JWT verification misconfig is endemic in FastAPI tutorials (jwt.decode without algorithms=).

Procedure

  1. Authorization preflight + base URL discovery.
  2. Fetch the OpenAPI spec once: GET /openapi.json (also try /api/openapi.json, /v1/openapi.json). Use it to drive the rest of the scan.
  3. Probe per rule table.

Rules

IDSeverityProbeConfirmed when
FA-DOC-001mediumGET /docs, GET /redoc, GET /openapi.json200 = docs publicly exposed (medium because it accelerates other attacks; not directly exploitable)
FA-AUTH-001criticalFor each operation in /openapi.json that lacks a security requirement and is POST/PUT/PATCH/DELETE, send a request without auth2xx = Depends(get_current_user) missing
FA-AUTH-002highFor routes declaring security: [HTTPBearer], send Authorization: Bearer <obviously-invalid>2xx = dependency returns None instead of raising 401
FA-JWT-001criticalSend Authorization: Bearer eyJhbGciOiJub25lIn0.<payload>. (alg=none); also send token signed with public key as secret using HS2562xx = jwt.decode without algorithms=
FA-JWT-002highSend token with iss=https://evil.test, aud=evil2xx = no audience/issuer checks
FA-PYD-001highFind a PATCH/PUT route with body schema; send {...valid..., "is_admin": true, "role": "admin"}Updated record reflects extra field = dict/Any body OR custom assignment without model_dump(exclude_unset=True, by_alias=...)
FA-PYD-002mediumSame route with field types intentionally wrong (string for int)500 with traceback (instead of 422) = exception not handled
FA-CORS-001highOPTIONS /api/* with Origin: https://evil.test + Access-Control-Request-Method: POSTACAO reflected with ACAC: true = allow_origins=["*"] + credentials, or origin reflection
FA-FILE-001highIf a FileResponse route exists (GET /files/{name}), request ?name=..%2F..%2F.env, ?name=..%2F..%2Fpyproject.tomlResponse body matches the file = path traversal
FA-DBG-001mediumTrigger an error via malformed JSON / bad type500 response with full Python traceback (file paths, line numbers) = app = FastAPI(debug=True) in this env
FA-RATE-001medium10 rapid POST /login (or schema-discovered auth route)All 200/401 without 429 = no slowapi/fastapi-limiter
FA-WS-001mediumIf /ws exists in OpenAPI, open WebSocket without auth headers/cookiesAccepted + receives messages = WebSocket bypass

Wrong vs. right

FA-AUTH-001 (missing dependency)

# ❌
@router.delete("/users/{user_id}")
async def delete_user(user_id: int):
    await users.delete(user_id)
# ✅
@router.delete("/users/{user_id}")
async def delete_user(
    user_id: int,
    current_user: User = Depends(get_current_user),
):
    if current_user.id != user_id and not current_user.is_admin:
        raise HTTPException(status_code=403)
    await users.delete(user_id)

FA-JWT-001 (alg confusion)

# ❌
payload = jwt.decode(token, options={"verify_signature": False})
# or
payload = jwt.decode(token, SECRET)   # no algorithms= → defaults vary by lib
# ✅
payload = jwt.decode(
    token,
    PUBLIC_KEY,
    algorithms=["RS256"],
    audience="my-api",
    issuer="https://issuer.example.com",
)

FA-PYD-001 (extra-field bypass)

# ❌
class UserUpdate(BaseModel):
    name: str | None = None
    email: str | None = None

@router.patch("/users/me")
async def patch_me(body: dict, user: User = Depends(...)):  # dict, not UserUpdate
    for k, v in body.items():
        setattr(user, k, v)
    await user.save()
# ✅
class UserUpdate(BaseModel):
    model_config = ConfigDict(extra="forbid")
    name: str | None = None
    email: str | None = None

@router.patch("/users/me")
async def patch_me(body: UserUpdate, user: User = Depends(...)):
    for k, v in body.model_dump(exclude_unset=True).items():
        setattr(user, k, v)
    await user.save()

References

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.