Safeguard
Security Guides•

How does a FastAPI response_model stop password and field leaks?

Return one model for input and another for output. FastAPI filters the response to the output model's fields, so passwords and internal fields stay private.

Safeguard Security Team
4 min read

Declare a separate output model without sensitive fields and FastAPI filters every response to that model. If you return the same model you accepted, any field in it, including a plaintext password, goes back to the client. Split input and output models, and set the output one on each route.

The leak in the official example

FastAPI's response model documentation demonstrates the problem directly. It defines a UserIn model with a username, a password, an email and an optional full name, then uses the same model as both input and output. The page comments that you should not do this in production, and explains the result: whenever a browser creates a user with a password, the API returns the same password in the response.

The docs add a sharper point. It might not matter when the same user is sending the password, but if the same model is reused on another path operation, you could be sending users' passwords to every client. Its warning says to never store a plain password or send it in a response unless you know the caveats.

The fix: an output model

Create a second model that lists only what clients may see:

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str | None = None

class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: str | None = None

@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
    return user

FastAPI validates and filters the returned data through UserOut, so the password field is dropped even though the function returned the full object. The documentation also shows a return type annotation form that achieves the same filtering when the types are compatible.

Why this is a design rule, not a patch

Database models and internal objects tend to grow fields: password hashes, tokens, flags such as is_admin, internal notes. If a route returns a raw object, each new field becomes public the moment someone adds it. An explicit output model flips the default. A field is private until you add it to the output.

PatternRisk when the model changes
Return the ORM or input objectNew fields leak automatically
Output model per routeNew fields stay private until added
Shared output model across routesOne route may expose more than another needs

Review checklist

  1. Find routes with no response_model and no return annotation.
  2. Find routes where the output model is the same class as the input model.
  3. Look for fields named password, hash, token, secret, key or internal in any output model.
  4. Test the response with a user who should not see another user's data. A response model hides fields, but it does not check ownership.
  5. Add a test that asserts the password key is absent from the JSON.

Try it yourself

Click through it at your own pace, or open it full screen. Look for findings about excessive data in responses.

Quick check

Does response_model check who may see the data?

No. It filters fields, not rows. You still need authorization checks that confirm the caller may see that particular record.

Is it enough to hash the password before returning?

Still not a good idea. Do not return password material at all. Keep it out of the output model.

Safeguard's SAST and API security scanning can help surface routes that return more than intended. You can try a free scan.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.