PII Leak in Response (Java)

ID

pii_leak_in_response_java

Severity

high / critical (composite with unauthenticated_endpoint)

Remediation Complexity

medium

Remediation Risk

high

Remediation Effort

medium

Family

API3:2023 - Broken Object Property Level Authorization

CWE

CWE-359

Resource

data_exposure

Language

Spring MVC, JAX-RS

Description

Reports endpoints whose response model carries fields tagged PII (Personally Identifiable Information), PCI (payment-card data), or PHI (protected health information) by the sensitivity classifier. Distinct from excessive_data_exposure in scope:

  • excessive_data_exposure — over-fetching, any sensitivity-tagged field the client didn’t ask for (including credentials).

  • pii_leak_in_response — sensitive-data sinks. Any time PII / PCI / PHI reaches the wire, regardless of whether the client asked for it.

CREDENTIAL and CRYPTO_MATERIAL tags are intentionally excluded — login and OAuth endpoints legitimately return tokens / cookies, and treating those as PII leaks would drown the signal.

The detector pairs with unauthenticated_endpoint via the FlawCorrelator: when the same endpoint also has no authentication requirement, a composite CRITICAL pii_leak_in_unauthenticated_endpoint finding is emitted.

Rationale

PII / PCI / PHI in a response is not automatically a defect — a user fetching their own profile needs to see their email and phone. The risk is when the response carries those fields beyond the operation’s intended scope:

  • Cross-user leak/users/search?name=alice returns full PII for every match. The endpoint’s purpose is "find a username"; returning email / phone / DOB / address is over-disclosure.

  • Anonymous / partner endpoint leak — a B2B partner endpoint that returns a list of customers with full PII (or worse, PCI / PHI), where the partner’s contract entitles them to aggregate numbers only.

  • Unauthenticated leak — the worst case. Any unauthenticated PII / PCI / PHI sink is a critical breach — under GDPR Art-32 ("appropriate technical measures") and PCI-DSS 3.4 ("render PAN unreadable"), there’s no defensible "well, it’s TLS-encrypted" argument.

The regulatory side compounds the technical one: a single unauthenticated PII endpoint discovered by a journalist or competitor is a public disclosure event. Treat PII / PCI / PHI in responses as a default-deny shape and justify each exposure case by case.

A susceptible Spring search endpoint:

@GetMapping("/users/search")
public List<User> search(@RequestParam String q) {
  return userRepository.findByUsernameContaining(q);
}

@Entity
class User {
  @Id Long id;
  String username;
  String email;          // PII
  String phone;          // PII
  String dateOfBirth;    // PII
  String address;        // PII
  // ...
}

The search returns full User entities — including email, phone, dateOfBirth, and address — even though the caller only asked for a username lookup.

Remediation

Three approaches, in order of robustness:

  • Remove the field from the response DTO if the operation doesn’t genuinely require it. The fastest fix; defaults to least disclosure.

  • Mask or redact at the response boundary. Show j***@example.com instead of john.doe@example.com, last-four-digits of a phone number, the first letter of a name. Pair with full unmasked access from a separate, audited "details" endpoint.

  • Tighten authorization on the endpoint. If the field is necessary, confirm the caller is entitled to it — pair this finding with a BOLA / BFLA review. An admin reading any user’s email is acceptable; a logged-in non-admin reading any user’s email is not.

Cross-cutting:

  • If the sensitivity tag is wrong for your project — e.g., an email-newsletter app where email is intentionally public — override the tag rather than suppressing the finding.

  • Pair this detector with unauthenticated_endpoint review. The composite CRITICAL finding is the actionable one.

A revised search returns the minimum needed shape:

public record UserSummary(Long id, String username) {}

@GetMapping("/users/search")
public List<UserSummary> search(@RequestParam String q) {
  return userRepository.findByUsernameContaining(q).stream()
      .map(u -> new UserSummary(u.getId(), u.getUsername()))
      .toList();
}

A separate, properly authorised /users/{id} endpoint can return the full record when the caller is entitled to see it.

Configuration

The detector accepts minConfidence (default high) and inherits the sensitivity classifier’s global configuration. The set of tags considered "PII" (PII / PCI / PHI) is fixed; CREDENTIAL and CRYPTO_MATERIAL are intentionally excluded from this detector’s scope.

References