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.
| Pattern | Risk when the model changes |
|---|---|
| Return the ORM or input object | New fields leak automatically |
| Output model per route | New fields stay private until added |
| Shared output model across routes | One route may expose more than another needs |
Review checklist
- Find routes with no response_model and no return annotation.
- Find routes where the output model is the same class as the input model.
- Look for fields named password, hash, token, secret, key or internal in any output model.
- 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.
- 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.