HIGH symlink attackfastapijwt tokens

Symlink Attack in Fastapi with Jwt Tokens

Symlink Attack in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability

A symlink attack in a FastAPI application that uses JWT tokens typically occurs when file upload or file-serving endpoints resolve user-controlled paths without canonicalization. An attacker can upload a file or provide a path that includes a symbolic link (e.g., ../../etc/passwd or a crafted path that traverses directories and points via symlink to a sensitive file). If the server resolves this path on disk and serves or processes the file, the JWT token associated with the request may be treated as an authorization signal, but the token does not prevent insecure file handling.

Specifically, consider an endpoint that accepts a filename or path to read user data. If the code joins this input to a base directory without resolving symlinks or normalizing paths, a malicious user can place a symlink inside an allowed directory that points outside it. When the server opens the file using the joined path, it follows the symlink and may expose or modify files the caller should not access. The presence of a valid JWT token identifying an authenticated user may lead developers to assume the request is safe, but the vulnerability is in path handling, not authentication. This can lead to information disclosure of arbitrary files (e.g., application configuration or other users’ data) or, in some configurations, write to sensitive locations if the endpoint also supports uploads.

In FastAPI, a common pattern is to use dependencies that validate JWT tokens and attach a user model to the request state. Authorization checks may verify scope or role claims but often do not validate or sanitize file paths. If a route uses user input directly to open a file — for example, using open(path, "rb") where path is built from request parameters — the token’s validity becomes a false guarantee. The server should instead resolve paths to their real absolute paths and ensure they remain within an intended directory, regardless of authentication state.

Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes

To mitigate path traversal and symlink risks in FastAPI while using JWT tokens, enforce path canonicalization and strict directory containment independently of authentication. Do not rely on JWT-based authorization alone to protect filesystem operations. Use os.path.realpath or Path.resolve() to resolve symlinks and normalize paths, and verify that the resolved path starts with the intended base directory.

Example secure FastAPI route with JWT authentication and safe file reading:

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
import os
from pathlib import Path

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
BASE_DIR = Path("/app/data").resolve()  # canonical base directory

def decode_token(token: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        return username
    except JWTError:
        raise credentials_exception

def get_current_user(token: str = Depends(oauth2_scheme)):
    username = decode_token(token)
    # You would fetch user details here
    return username

@app.get("/files/{filename}")
def read_file(filename: str, user: str = Depends(get_current_user)):
    # Build candidate path and resolve symlinks
    candidate = (BASE_DIR / filename).resolve()
    # Ensure the resolved path is within BASE_DIR
    try:
        candidate.relative_to(BASE_DIR)
    except ValueError:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Access denied"
        )
    if not candidate.is_file():
        raise HTTPException(status_code=404, detail="File not found")
    content = candidate.read_bytes()
    return {"filename": candidate.name, "size": len(content)}

Key points in this example:

  • BASE_DIR is resolved once at startup to an absolute path, eliminating relative ambiguities.
  • (BASE_DIR / filename).resolve() canonicalizes the path, removing any ".." components and following symlinks.
  • candidate.relative_to(BASE_DIR) ensures the resolved path remains inside the allowed directory; if not, access is denied.
  • JWT validation via the dependency still provides user context and authentication, but authorization now explicitly depends on safe path resolution rather than trusting token claims for filesystem safety.

Additionally, avoid serving user-uploaded files via dynamic routes that directly map to user input. If you must serve files, consider storing them with random names and maintaining a mapping in your database, and use X-Send-Type or similar mechanisms only after validating the target path.

Frequently Asked Questions

Can a valid JWT token prevent symlink attacks in FastAPI?
No. JWT tokens provide authentication and can carry authorization claims, but they do not protect against path traversal or symlink vulnerabilities. Secure file handling requires path canonicalization and containment checks independent of token validity.
Does resolving paths with .resolve() guarantee safety in FastAPI?
Using .resolve() is essential to eliminate symlinks and normalize paths, but you must also verify that the resolved path is within your intended directory (e.g., with relative_to). Both steps are required for robust protection.