ASVS 5.0 V15.3.1 requires that the application returns only the required subset of fields from a data object, rather than the whole object, because some fields should never be visible to the user. It is Level 1. The fix is to define an explicit response shape for each endpoint and map into it, instead of serializing your database model directly.
Why it matters
Returning a whole record is the easiest code to write and a common source of data exposure. The user table has a password hash, a reset token, an internal role, a billing note. The front end only displays a name and avatar, so nobody notices the rest travelling in the JSON until a researcher opens the network tab.
How to implement it
Use an allowlist. In FastAPI, a response model strips everything not declared:
from pydantic import BaseModel
from fastapi import FastAPI
app = FastAPI()
class UserPublic(BaseModel):
id: int
display_name: str
avatar_url: str | None = None
@app.get("/users/{user_id}", response_model=UserPublic)
def get_user(user_id: int):
return db.get_user(user_id) # extra fields on the record are dropped
In Node, build the object by hand or with a mapper:
function toPublicUser(u) {
return { id: u.id, displayName: u.displayName, avatarUrl: u.avatarUrl ?? null };
}
res.json(toPublicUser(user));
Different callers may need different shapes, such as the owner versus another user. Make those separate response models, not one model with fields blanked at runtime.
Click through it at your own pace, or open it full screen.
How to test it
- Call each endpoint and list every key in the response, including nested objects and arrays.
- Compare to the documented schema. Any extra key is a finding.
- Check every role. Request the same object as owner, as another user and as an anonymous caller.
- Write a contract test that fails when a new field appears that is not in the allowlist.
- Check error responses and list endpoints, which often use a different serializer.
Common mistakes
- Hiding fields in the front end with CSS or JavaScript.
- Using a denylist of sensitive fields, which breaks when someone adds a new column.
- Securing the detail endpoint but not the list or search endpoint.
- Returning ORM objects from GraphQL resolvers with a permissive type.
Quick check
Is deleting the password field before returning enough?
It fixes today's case only. An allowlist stays correct when new sensitive columns are added.
Which level does this belong to?
Level 1.