Real incident: a debug log statement printed the entire request body to logs. For months, every signup, profile update, and payment-method-add wrote email, phone, address, and last-4 of card to Datadog. Discovered during a SOC2 audit. Mesrai catches the shape on every PR.
The vulnerable diff
// lib/log-middleware.ts (the buggy middleware)
export function requestLogger(req, res, next) {
// BUG: req.body contains PII on user-facing endpoints
log.info({ method: req.method, path: req.path, body: req.body }, "request received");
next();
}
// During incident investigation:
// Datadog Logs query: `service:api source:nodejs` →
// 3.2M log entries with raw PII (email, phone, address, card.last4)
// over the past 6 months.What is wrong
Observability platforms (Datadog, Splunk, ELK) are designed for fast search and retention — they are NOT designed as a PII store. Sending raw user PII into logs has several problems: log retention typically exceeds your data retention policy; the platform's access controls may be broader than your DB's; logs are often shipped to multiple regions; logs are common targets in supply-chain attacks. The fix is to redact at log time — strip sensitive keys from request bodies before logging, or omit body logging entirely on user-facing endpoints.
The attack
Audit finding:
During SOC2 readiness assessment:
- Auditor queries Datadog logs for "email" field.
- 3.2M entries. PII present in plain text.
- Audit finding: control failure on data minimization (CC6.7).
- Remediation: redaction policy + log retention reduction + incident
notification to affected users.
- Cost: ~$80K in remediation + audit re-test fees.Catastrophic.
Mesrai's review comment
mesraipilot · Bot · reviewed 1 min ago
[mesrai] [code-review] [Security] [PII-Leak] [critical]
Logging `req.body` writes user PII to your log aggregator. This is
a compliance failure (GDPR Art. 5.1.c data minimization, SOC2 CC6.7,
DPDP Act § 8) and an exposure risk.
Two-layer fix:
1. Don't log raw bodies on user-facing endpoints:
log.info({
method: req.method,
path: req.path,
userId: req.user?.id,
statusCode: res.statusCode,
}, "request");
2. Add a serializer that redacts sensitive keys for any body
logging you do want:
// pino example
const SENSITIVE = ["password","token","authorization","email",
"phone","ssn","aadhaar","card","cvv","accountNumber"];
const logger = pino({
redact: { paths: SENSITIVE, censor: "[REDACTED]" },
});
For existing log entries: bulk-delete in Datadog via the API,
shorten retention to 7 days going forward, and notify affected
users if your DPA requires it.The fix
// lib/log-middleware.ts — fixed
export function requestLogger(req, res, next) {
log.info({
method: req.method,
path: req.path,
userId: req.user?.id,
}, "request");
res.on("finish", () => {
log.info({
method: req.method, path: req.path, status: res.statusCode,
durationMs: Date.now() - req.startTime,
}, "response");
});
next();
}
// lib/logger.ts (the redacting logger if used elsewhere)
import pino from "pino";
const SENSITIVE = [
"password","token","authorization","email","phone","ssn","aadhaar",
"card.number","card.cvv","cvv","accountNumber","ifsc",
"*.password","*.email","*.phone", // wildcards for nested
];
export const log = pino({ redact: { paths: SENSITIVE, censor: "[REDACTED]" } });Minimal request log: method, path, userId only. Response log adds status + duration. Logger has a redaction policy so any incidental body logging strips sensitive fields. PII never reaches Datadog.
Why human review missed it
PII-in-logs is one of the most common SOC2/GDPR audit findings. Mesrai catches every log statement that prints `req.body`, `req.query`, or other potentially-PII fields without redaction.
Related rules + further reading
Mesrai rule pack: security/no-pii-in-logs — flags log statements with req.body, req.query, password, email, token, etc.
OWASP Logging Cheat Sheet: PII handling.
GDPR Art. 5.1.c — data minimization. DPDP § 8.
Takeaway
Logs are not a PII store. Redact at log time. Audit your existing logs. Mesrai catches the pattern at PR time.