no-pii-in-logs
Prevent personally identifiable information (PII) — emails, SSNs, credit cards, phone numbers — from reaching console / logger output.
Prevent personally identifiable information (PII) — emails, SSNs, credit cards, phone numbers — from reaching console.* or logger output. Logs are routinely shipped to third-party platforms (Datadog, Sentry, CloudWatch) and indexed by people who never had access authorization for that data.
This rule is part of eslint-plugin-secure-coding.
Quick Summary
| Aspect | Details |
|---|---|
| Severity | High (Security / Compliance — GDPR Art. 32, HIPAA §164.312) |
| Auto-Fix | 💡 Suggestions (redact, hash, or replace with stable user id) |
| Category | Secure Coding |
| CWE | CWE-359 |
| Best For | Any service that handles user data and ships logs off-host |
Why this matters
Application logs are the highest-volume, lowest-trust egress channel in most production services. By default they are:
- Shipped to vendors (Datadog, Sentry, Splunk, Loki, ELK) — broadening the trust boundary.
- Retained for weeks to months — long enough that a breach years from now still discloses today's data.
- Read by anyone with engineering oncall — not the access-controlled subset that touches the user record itself.
GDPR Article 32 (security of processing) and HIPAA §164.312 (technical safeguards) both treat log-leaked PII as a reportable incident. Most breach disclosures in 2024–2025 traced the data exposure to a logger, not a database.
What the rule detects
The rule flags PII patterns whether they enter the log call as a literal, a templated string, or a member access on an user/person/account identifier:
| Pattern | Detected as |
|---|---|
| Hard-coded email regex match | email |
user.email, account.ssn | inferred via identifier shape |
| 16-digit number passing Luhn | credit-card |
| ITU-formatted phone number | phone |
US SSN (\d{3}-\d{2}-\d{4}) | ssn |
Examples
❌ Incorrect
console.log('user signed in', user.email, user.phone);
logger.info(`payment from ${customer.creditCard}`);
console.error('lookup failed for SSN', record.ssn);
// PII concealed in JSON.stringify still gets logged
logger.debug(JSON.stringify(user));✅ Correct
import { redactPii, hashStable } from '@/lib/pii';
console.log('user signed in', { userId: user.id });
logger.info('payment authorised', { customerId: hashStable(customer.id) });
console.error('lookup failed', { ssnHash: hashStable(record.ssn) });
// Or use a structured logger that redacts known PII fields by allowlist
logger.debug({ user: redactPii(user) });Error Message Format
🔒 SECURE-CODING CWE-359 | PII detected in log argument | HIGH
Fix: Replace the PII payload with a stable opaque id (UUID, hash) or pass through a redacting logger before emission.Known False Negatives
- Custom logger wrappers (
logEvent,audit,track) are detected via theloggersrule option allowlist; loggers not on the allowlist are skipped. - PII reached via aliasing (
const e = user.email; log(e);) is tracked one assignment hop; deeper chains require type-aware enabling. - PII embedded inside an HTTP request body that incidentally surfaces in an error message (e.g.
JSON.stringify(req.body)in acatchblock) is not always reached without type info.
⚙️ Options
| Option | Type | Default | Description |
|---|---|---|---|
consoleMethods | string[] | ["log","error","warn","info"] | console methods treated as a log sink, matched as an exact method name. Replaces the built-in list. |
additionalConsoleMethods | string[] | [] | Extra console methods, on top of consoleMethods. |
piiTerms | string[] | ["email","ssn","password","credit card","phone"] | Field vocabulary, matched as WHOLE WORDS of an identifier — never as a substring. Multi-word terms such as "credit card" match consecutive segments. Replaces the built-in list. |
additionalPiiTerms | string[] | [] | Extra PII field terms, on top of piiTerms. |
piiLabels | string[] | ["email:","ssn:","password:","credit card:","creditcard:"] | String literals that are EXACTLY a field label announcing the next argument. Compared case-insensitively after trimming. Replaces the built-in list. |
additionalPiiLabels | string[] | [] | Extra field labels, on top of piiLabels. |
Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.