Capture the API response behind a screen, list every field in it, and compare that list with what the screen displays. Any extra field is a candidate for excessive data exposure. Repeat as a lower role, an admin and an anonymous caller, then check nested objects, errors and client-side files.
The idea in plain words
Front ends often hide fields instead of removing them. The API sends the whole record and the UI picks what to show. Anyone who calls the API directly sees everything, including fields like internal flags, email addresses of other users, or tokens. The OWASP Web Security Testing Guide has a test procedure for this under API testing. The nine steps below follow it.
The steps
- Compare UI and API data. Open a page that shows user or object data, capture the request behind it in browser developer tools or an intercepting proxy, and look for fields the screen never displays.
- Cover many endpoints. User and account endpoints, list and search endpoints, related-object endpoints, error responses, and any admin or internal endpoints that are reachable from outside.
- Vary the privilege level. Request the same resource as a low-privilege user, an admin and an unauthenticated client. Also request another user's object and see whether the same rich data comes back.
- Review nested objects. If a response embeds customer, payment or shipping objects, check each one for fields the client does not need.
- Test for mass assignment. On endpoints that accept JSON or XML, add extra fields such as a role flag and see whether the server accepts them.
- Check GraphQL. If introspection is on, list the fields of each type, look for sensitive ones like password hashes, API keys and role flags, and query them directly.
- Inspect client-side assets. Publicly reachable source maps can reveal expected response fields, internal endpoints and hardcoded secrets.
- Trigger errors. Send malformed input, invalid authentication, and requests for nonexistent resources, then inspect bodies and headers for stack traces, database details or debug headers.
- Document and verify fixes. Record each sensitive field and its context, then confirm that the server filters by an explicit allow list such as response objects or serializers, not by hiding fields in the client.
A simple way to do the diff
Save the raw response and extract the field names. A command line JSON tool that lists keys works well for this. Put the field names from the response in one column and the labels visible on the screen in another. Anything in the first column that has no match in the second needs a reason to exist.
| Field in response | Shown in UI | Needed by the client | Action |
|---|---|---|---|
| display_name | Yes | Yes | Keep |
| No | No | Remove from this endpoint | |
| role | No | Yes, for menus | Keep, but check it cannot be set by the caller |
| internal_note | No | No | Remove |
What good looks like
Each endpoint returns a response object built from an explicit list of fields. The list differs by caller role where needed. Errors say little. Mass assignment is blocked by an allow list of writable fields.
Where it fits in a program
This test is cheap and finds real problems, but it relies on a person reading responses. Do it once per new endpoint family and again whenever a response object changes. If you already track API changes in pull requests, add "response fields changed" as a review prompt.
Worked example: a team directory page
Take a company directory page. The screen shows each person's name, job title, office and a profile photo. The team wants to know whether the API behind it shows more than that.
Capture. The tester opens the page, then opens browser developer tools and finds the request, GET /api/people?office=berlin. The response is a list. The first record looks like this in abbreviated form:
| Field | Value type | Shown on screen? |
|---|---|---|
| id | Number | No, used for links |
| displayName | Text | Yes |
| title | Text | Yes |
| office | Text | Yes |
| photoUrl | URL | Yes |
| workEmail | Text | No |
| personalEmail | Text | No |
| mobilePhone | Text | No |
| salaryBand | Text | No |
| managerId | Number | No |
| lastLogin | Date | No |
| isAdmin | Boolean | No |
Twelve fields come back and four are displayed. The tester marks the rest as questions.
Reasoning about each extra field. id and managerId may be needed for links and the org chart, so they stay but need a look. workEmail could be needed for a mail link, but nothing on the screen uses it. personalEmail, mobilePhone and salaryBand have no business appearing in a public directory list. lastLogin and isAdmin tell an attacker who is dormant and who has power, which is useful for targeting.
Vary the role. The tester repeats the request as a regular employee, as an HR user and with no login. The regular employee and anonymous responses are identical to the first. That is a second problem: the directory returns salary bands to everyone, and anonymous callers can reach it. The HR user gets the same fields, so the API does not vary by role at all.
Look at the details. Requesting GET /api/people/17 returns a larger object including a nested emergencyContact with a name and phone number. The list endpoint is lean compared with the detail endpoint, but both go further than the screens.
Mass assignment check. A profile update endpoint accepts a JSON body. The tester adds "isAdmin": true to an update of their own profile. The response is a 200 and the next read shows isAdmin as true. That is a serious finding, found because the diff step surfaced the field name.
Source map. The tester finds the front-end source map is publicly reachable. It lists an internal endpoint, /api/internal/people/export, never linked in the UI. A request to it as a regular user returns a file. Another finding.
Fixing it the right way
The team replaces the shared record object with explicit response objects: a public directory view with four fields, a detail view for logged-in staff with a few more, and an HR view with the sensitive ones, each selected by role on the server. Writable fields on the profile update become an allow list that excludes isAdmin. The source map is removed from production and the export route gets a role check.
Keeping the benefit
Add a test per response object that fails if a new field appears without an explicit decision. A snapshot of field names, reviewed in the pull request, does the job.
Try it yourself
Open the demo and look at how an example scan names the affected operation for each finding. Think about how you would label the directory findings above in the same way.
Click through it at your own pace, or open it full screen.
Quick check
Is hiding a field in the front end a fix?
No. The guide says to confirm the server filters responses by an explicit allow list, not by client-side hiding.
Do error responses count?
Yes. Stack traces, database details and debug headers in errors are part of the exposure the test covers.
How often should this diff test be repeated?
Whenever a response object changes, and once per quarter for stable endpoints. Add a review prompt to pull requests that touch serializers or response types.
Sources
Last reviewed: 19 October 2026.