Back to skill

Security audit

FastAPI Production Engineering

Security checks for vulnerabilities and agentic risk

Overview

This FastAPI guidance skill is mostly transparent, but some production templates teach unsafe upload and WebSocket patterns and use an unpinned build tool install.

Use this skill as a checklist or starting reference, but do not copy the WebSocket, file upload, Docker, or CI snippets verbatim. Require authenticated WebSocket handshakes with authorization checks, generate server-side upload filenames with containment and content validation, and pin build tools such as uv before using the deployment templates.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (3)

T08 · Insecure Dependencies

Warning
Location
SKILL.md:713
Finding
Unpinned Build Tool Installation Creates Supply-Chain Risk## Vulnerability Details **File Location**: `SKILL.md:713` and `SKILL.md:816` **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Medium ### Vulnerable Code Docker build template at `SKILL.md:713`: ```dockerfile RUN pip install --no-cache-dir uv ``` GitHub Actions template at `SKILL.md:816`: ```yaml - run: pip install uv && uv sync ``` ### Technical Analysis Both templates install `uv` without an exact version constraint or package hash. Consequently, the installed package is determined by the package index at build time rather than by a reviewed and reproducible dependency definition. This allows build behavior to change without any corresponding source-code change. If a future release is defective or malicious, or if the package index or publisher account is compromised, Docker builds and CI jobs based on these templates will install and execute the affected package. The risk is amplified in CI because installation commands execute automatically and commonly have access to repository content, build artifacts, and workflow-scoped credentials. Although `uv sync --frozen` protects application dependency resolution when a valid lockfile is present, it does not protect the preceding unpinned installation of the `uv` executable itself. ### Attack Path 1. A project adopts the supplied Dockerfile or GitHub Actions template. 2. A malicious or compromised release of `uv` becomes the version resolved by `pip install uv`. 3. A developer starts a Docker build, or the CI workflow runs after a push or pull request. 4. `pip` downloads and installs the unreviewed release. 5. Package installation or subsequent execution of `uv sync` runs attacker-controlled behavior in the build or CI environment. 6. Depending on environment permissions, that behavior may read source code, alter build artifacts, access available workflow credentials, or compromise the resulting container image. ...[truncated 674 chars]
Remediation
## Remediation Suggestions 1. Pin `uv` to a reviewed exact version in both templates, for example: ```dockerfile RUN pip install --no-cache-dir "uv==<reviewed-version>" ``` 2. Where supported, download from a controlled artifact repository and verify an expected cryptographic checksum. 3. Use pip hash checking with a locked bootstrap requirements file rather than resolving the installer dynamically. 4. Keep `uv.lock` under version control and continue using `uv sync --frozen`. 5. Run automated dependency updates through reviewed pull requests rather than automatically consuming the latest release. 6. Restrict CI token permissions and avoid exposing deployment secrets to dependency-installation jobs. 7. Apply the same pinned version consistently at both `SKILL.md:713` and `SKILL.md:816`.

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:918
Finding
Client-Controlled Upload Filename Enables Path Traversal## Vulnerability Details **File Location**: `SKILL.md:918-935` **Vulnerability Type**: Path traversal through an untrusted upload filename **Risk Level**: High ### Vulnerable Code ```python from fastapi import UploadFile, File @router.post("/upload") async def upload_file( file: UploadFile = File(..., description="File to upload"), user: User = Depends(get_current_user), ): # Validate if file.size and file.size > 10 * 1024 * 1024: # 10MB raise ValidationError("File too large (max 10MB)") allowed_types = {"image/jpeg", "image/png", "application/pdf"} if file.content_type not in allowed_types: raise ValidationError(f"File type not allowed: {file.content_type}") # Save contents = await file.read() path = f"uploads/{user.id}/{file.filename}" # Save to S3/local storage... return {"filename": file.filename, "size": len(contents)} ``` ### Technical Analysis `UploadFile.filename` is supplied by the client and is inserted directly into a storage path. The code does not remove path separators, reject traversal components, generate a server-controlled object name, or verify that the resolved destination remains inside the intended upload directory. If the placeholder is completed with a normal filesystem write, filenames containing components such as `../` can resolve outside `uploads/{user.id}`. Absolute paths or platform-specific separators may create similar problems, depending on the operating system and path-handling API. The MIME allowlist does not mitigate path traversal. The `content_type` value is also client-controlled and does not prove that the content is a valid image or PDF. In addition, checking `file.size` before reading does not reliably impose a hard streaming limit when the size is absent or inaccurate. ### Attack Path 1. An attacker authenticates as an ordinary user because the endpoint requires `get_curren ...[truncated 1564 chars]
Remediation
## Remediation Suggestions 1. Do not use the client filename as the storage key. Generate a random server-side identifier, such as a UUID, and map it to separately stored display metadata. 2. If retaining part of the original filename is necessary, strip all directory components and enforce a strict allowlist of characters and extensions. 3. Build paths with `pathlib.Path`, resolve the candidate destination, and verify that it remains under a fixed resolved upload root before opening it. 4. Open new files with exclusive creation semantics where possible to prevent unintended overwrites. 5. Store uploads outside application source, static roots, and executable directories. 6. Verify actual file signatures and decoded content rather than trusting the multipart `content_type`. 7. Enforce the byte limit while streaming the body and abort as soon as the maximum is exceeded. 8. Apply restrictive service-account and storage permissions so uploaded content cannot modify application or system files. 9. For object storage, generate server-controlled keys and configure bucket policies to prevent execution and public access by default.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
SKILL.md:889
Finding
WebSocket Endpoint Permits User Identity Spoofing## Vulnerability Details **File Location**: `SKILL.md:889-914` **Vulnerability Type**: Missing WebSocket authentication and object-level authorization **Risk Level**: High ### Vulnerable Code ```python from fastapi import WebSocket, WebSocketDisconnect class ConnectionManager: def __init__(self): self.connections: dict[str, WebSocket] = {} async def connect(self, user_id: str, ws: WebSocket): await ws.accept() self.connections[user_id] = ws def disconnect(self, user_id: str): self.connections.pop(user_id, None) async def send(self, user_id: str, message: dict): if ws := self.connections.get(user_id): await ws.send_json(message) manager = ConnectionManager() @router.websocket("/ws/{user_id}") async def websocket_endpoint(websocket: WebSocket, user_id: str): await manager.connect(user_id, websocket) try: while True: data = await websocket.receive_json() # Process message except WebSocketDisconnect: manager.disconnect(user_id) ``` ### Technical Analysis The endpoint treats the path parameter `user_id` as the connection's authenticated identity. It does not validate a session or token, derive the identity from a trusted principal, or check whether the caller is authorized to use the requested identifier. `ConnectionManager.connect()` then stores the socket directly under that caller-controlled identifier. Assigning to `self.connections[user_id]` also silently replaces any existing connection for the same user. Messages sent through `send(user_id, message)` are delivered to whichever socket most recently claimed that identifier. WebSocket connections do not automatically inherit the authentication dependency used by unrelated HTTP routes. Authentication and authorization must be explicitly performed during the WebSocket handshake. The example also lacks an ...[truncated 1629 chars]
Remediation
## Remediation Suggestions 1. Authenticate the connection before calling `websocket.accept()`. 2. Validate a short-lived WebSocket credential using an approved transport, such as an authorization-capable handshake mechanism or a secure, single-use ticket. 3. Derive `user_id` exclusively from the validated principal; do not trust a path parameter as identity. 4. Perform object-level authorization before joining any user-specific channel. 5. Validate the `Origin` header against an environment-specific allowlist for browser clients. 6. Define an explicit policy for duplicate sessions rather than silently replacing an existing connection. 7. Associate connection records with unique connection IDs and validated principal objects, not only user-controlled strings. 8. Authorize every inbound message according to the authenticated principal and validate its schema before processing. 9. Add tests proving that unauthenticated connections, identity mismatches, unauthorized channels, and untrusted origins are rejected.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (5)

Credential Access

High
Category
Privilege Escalation
Content
raise ValueError(f"environment must be one of {allowed}")
        return v
    
    model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}

@lru_cache
def get_settings() -> Settings:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The Quick Start section uses broad natural-language trigger phrases such as 'audit my FastAPI project' and 'set up a new FastAPI project' that could be matched too loosely by an agent platform and cause unintended invocation. In a skill-dispatch system, overly generic triggers can activate this skill in contexts where the user did not explicitly request it, leading to confusing behavior, context hijacking, or incorrect automation selection.

Internal Network Request

Medium
Category
Server-Side Request Forgery
Content
EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD ["python", "-c", "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"]

CMD ["uvicorn", "src.app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
```
Confidence
70% confidence
Finding
Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The file upload example validates size and MIME type but then reads and saves user-supplied content using a path derived from the original filename, without warning about storage safety. This can normalize insecure handling patterns, including unsafe persistence of untrusted files, path manipulation risks, and missing malware/content scanning or filename sanitization.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The skill exposes many generic natural-language triggers such as 'set up a new FastAPI project' and 'review my API security' without clear scoping, confirmation, or exclusion conditions. In an agent setting, this can cause overbroad activation and unintended execution of powerful code-generation or security-review behaviors on the wrong target or with insufficient user intent verification.

Static analysis

No suspicious patterns detected.