Excessive Data Exposure (C#)
ID |
excessive_data_exposure_csharp |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
high |
Remediation Effort |
medium |
Family |
API3:2023 - Broken Object Property Level Authorization |
CWE |
CWE-213, CWE-200 |
Resource |
data_exposure |
Language |
ASP.NET Core (Controllers + Minimal APIs) |
Description
Walks each endpoint’s response model and classifies every field into one of three confidence tiers, based on whether the field is sensitivity-tagged (PII / PCI / PHI / credentials by the sensitivity classifier) and whether the same field name appears in the request:
-
HIGH — sensitive AND not referenced in the request. The caller never asked for the field; returning it is over-fetch.
-
MEDIUM — sensitive AND referenced in the request. Possibly a legitimate field-by-field update — surfaced for review.
-
LOW — not sensitivity-tagged AND not referenced in the request. Mild signal of over-fetching.
Only the HIGH tier fires by default. The detector is per-language because the framework idioms differ — JSON-annotation models in Java / C#, dataclass / Pydantic models in Python, plain-object / class-transformer in JS/TS, struct tags in Go, Eloquent / Symfony serializer groups in PHP.
Rationale
Excessive Data Exposure is the read side of OWASP API3:2023 — the API returns more than the client needs, because the implementation returned a persistence model directly and the persistence model has more fields than the contract. The risk is twofold:
-
Direct data leak — the extra fields contain credentials (
passwordHash,apiToken), PII (email,phone,ssn), or PCI (cardLastFour,accountNumber). Anyone reading the response sees them. -
Object-property authorization bypass — even if the endpoint is authorised to return the object, individual fields on that object should be scoped. A "GET my profile" endpoint authorised for the user should not expose the user’s
isAdminflag,lastLoginIp, orinternalNotes. API3 separates these from API1 (object-level) for exactly this reason.
The pattern is endemic in single-page-app backends, where the same User / Order / Patient entity is reused across all read endpoints, with the client deciding which fields to show. The "client filters out the bad fields" defence does not work — anyone can read the network response.
A susceptible ASP.NET Core controller might look like this:
[ApiController]
[Route("users")]
public class UsersController : ControllerBase
{
[HttpGet("{id:int}")]
public async Task<ActionResult<User>> Get(int id)
=> await _db.Users.FindAsync(id) is { } u ? Ok(u) : NotFound();
}
public class User
{
public int Id { get; set; }
public string Username { get; set; } = "";
public string Email { get; set; } = ""; // PII
public string PasswordHash { get; set; } = ""; // CREDENTIAL
public string Ssn { get; set; } = ""; // PII
public bool IsAdmin { get; set; } // PRIVILEGE
}
The handler returns the EF Core entity, so every property is serialised into the response.
Remediation
The fix is to return a dedicated response model per operation, narrow to the fields the operation actually needs to expose. Specific per-language patterns are in the language pages.
Beyond per-route DTOs:
-
If the sensitivity tag is wrong for your project — for example, an email-newsletter app whose
User.emailfield is intentionally public — exclude the field via per-project sensitivity overrides rather than suppressing the finding. -
Pair this detector with
pii_leak_in_response(focuses on PII / PCI / PHI specifically, with stricter severity). -
Server-side response filtering is the only effective control. Anything that depends on the client hiding fields is not a remediation.
Here is a revised handler returning a dedicated ViewModel:
public record PublicUser(int Id, string Username);
[ApiController]
[Route("users")]
public class UsersController : ControllerBase
{
[HttpGet("{id:int}")]
public async Task<ActionResult<PublicUser>> Get(int id)
=> await _db.Users
.Where(u => u.Id == id)
.Select(u => new PublicUser(u.Id, u.Username))
.FirstOrDefaultAsync() is { } p ? Ok(p) : NotFound();
}
The projection (Select(…)) shapes the response and removes the sensitive columns from the SQL query itself — defence in depth.
Other C# idioms:
-
[JsonIgnore]on each sensitive property — fragile (every new sensitive field must be annotated). -
JsonSerializerOptionsdefaults that hide properties matching a regex. -
AutoMapper profiles that map
UsertoPublicUserconsistently.
Configuration
The detector accepts:
-
minConfidence— the minimum confidence tier that fires. Defaulthigh. Set tomediumto include "sensitive AND referenced in request" findings. Set tolowonly for benchmark / audit runs (very noisy).
The set of sensitivity tags (PII / PCI / PHI / credentials) is configured globally on the sensitivity classifier, not per-detector.
References
-
OWASP API Security Top 10 (2023) - API3:2023 - Broken Object Property Level Authorization.
-
CWE-213 : Exposure of Sensitive Information Due to Incompatible Policies.
-
CWE-200 : Exposure of Sensitive Information to an Unauthorized Actor.
-
OWASP Cheat Sheets Series: REST Security Cheat Sheet — section on minimum information disclosure.
-
Microsoft docs — Choose between controller-based APIs and Minimal APIs in ASP.NET Core.
-
Microsoft docs — Controller action return types.