| name | excessive-data-exposure-anti-pattern |
| description | Security anti-pattern for excessive data exposure (CWE-200). Use when generating or reviewing API responses, database queries, or data serialization. Detects returning more data than necessary including internal fields, sensitive attributes, and related records. |
Excessive Data Exposure Anti-Pattern
Severity: High
Summary
Excessive Data Exposure occurs when APIs return more data than necessary for client functionality. This happens when endpoints serialize raw database objects or model classes without filtering sensitive fields. Attackers intercept API responses to access exposed PII, credentials, and internal system details, even when client-side UI hides this data.
The Anti-Pattern
Never serialize and return entire database objects or internal models. This exposes all object properties, including sensitive ones, assuming the client will filter what it needs.
BAD Code Example
from flask import jsonify
class User:
def __init__(self, id, username, email, password_hash, ssn, is_admin):
self.id = id
self.username = username
self.email = email
self.password_hash = password_hash
self.ssn = ssn
self.is_admin = is_admin
def to_dict(self):
return self.__dict__
@app.route("/api/users/<int:user_id>")
def get_user(user_id):
user = find_user_by_id(user_id)
if not user:
return jsonify({"error": "User not found"}), 404
return jsonify(user.to_dict())
GOOD Code Example
from flask import jsonify
class User:
pass
class UserPublicDTO:
def __init__(self, id, username):
self.id = id
self.username = username
@staticmethod
def from_model(user):
return UserPublicDTO(id=user.id, username=user.username)
@app.route("/api/users/<int:user_id>")
def get_user(user_id):
user = find_user_by_id(user_id)
if not user:
return jsonify({"error": "User not found"}), 404
user_dto = UserPublicDTO.from_model(user)
return jsonify(user_dto.__dict__)
Language-Specific Implementations
JavaScript/TypeScript (NestJS):
@Get(':id')
async getUser(@Param('id') id: string) {
const user = await this.userRepository.findOne(id);
return user;
}
import { Expose } from 'class-transformer';
export class UserPublicDto {
@Expose()
id: number;
@Expose()
username: string;
}
@Get(':id')
async getUser(@Param('id') id: string) {
const user = await this.userRepository.findOne(id);
return plainToClass(UserPublicDto, user, { excludeExtraneousValues: true });
}
Java (Spring Boot):
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userRepository.findById(id).orElseThrow();
}
public class Views {
public static class Public {}
public static class Internal extends Public {}
}
@Entity
public class User {
@JsonView(Views.Public.class)
private Long id;
@JsonView(Views.Public.class)
private String username;
private String passwordHash;
private String ssn;
}
@GetMapping("/users/{id}")
@JsonView(Views.Public.class)
public User getUser(@PathVariable Long id) {
return userRepository.findById(id).orElseThrow();
}
C# (ASP.NET Core):
[HttpGet("{id}")]
public ActionResult<User> GetUser(int id)
{
var user = _context.Users.Find(id);
return user;
}
public class UserPublicDto
{
public int Id { get; set; }
public string Username { get; set; }
}
[HttpGet("{id}")]
public ActionResult<UserPublicDto> GetUser(int id)
{
var user = _context.Users.Find(id);
if (user == null) return NotFound();
return new UserPublicDto
{
Id = user.Id,
Username = user.Username
};
}
Detection
- Review API responses: Use Burp Suite or OWASP ZAP to intercept API calls. Identify endpoints returning unused, internal, or sensitive fields (e.g.,
passwordHash, ssn, internalNotes).
- Analyze database queries: Grep for
SELECT * queries feeding API responses.
- Inspect serialization logic: Find generic
.toJSON() or serialize() methods dumping all object properties without filtering.
Prevention
Related Security Patterns & Anti-Patterns
References