fix(redact): stop the E.164 phone pattern flagging compact timestamps

Bare 14-digit runs like 20260727202423 (YYYYMMDDHHMMSS backup/log stamps)
matched the phone regex and produced MEDIUM PII findings. Reject a
separator-free 14-digit span whose fields parse as a plausible date-time;
real numbers carry a + or spacing, so phone coverage is unchanged.

Contributed by @abkrim (PR #2428).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-08-14 19:23:58 -07:00
parent 85954e604a
commit a03f571147
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
2 changed files with 36 additions and 2 deletions

View File

@ -138,6 +138,36 @@ function looksLikeWallet(span: string): boolean {
return span.length >= 26 && span.length <= 62;
}
// Compact log/backup stamps (`20260727202423` = YYYYMMDDHHMMSS) are bare digit
// runs that the phone regex happily eats. Only a SEPARATOR-FREE 14-digit span
// qualifies: E.164 tops out at 15 digits and real numbers carry a + or spacing,
// so rejecting this shape costs no phone coverage.
function looksLikeCompactTimestamp(span: string): boolean {
if (!/^\d{14}$/.test(span)) {
return false;
}
const n = (from: number, to: number) => Number(span.slice(from, to));
const [year, month, day, hour, minute, second] = [
n(0, 4),
n(4, 6),
n(6, 8),
n(8, 10),
n(10, 12),
n(12, 14),
];
return (
year >= 1900 &&
year <= 2999 &&
month >= 1 &&
month <= 12 &&
day >= 1 &&
day <= 31 &&
hour <= 23 &&
minute <= 59 &&
second <= 59
);
}
// ── Placeholder suppression (per-matched-span, NOT per-line) ─────────────────
/**
@ -498,7 +528,10 @@ export const PATTERNS: RedactPattern[] = [
autoRedactable: true,
redactToken: "<REDACTED-PHONE>",
// A digit-only UUID's hyphen groups read as national phone formatting.
validate: (span, match) => !insideUuid(match) && span.replace(/\D/g, "").length >= 10,
validate: (span, match) =>
!insideUuid(match) &&
span.replace(/\D/g, "").length >= 10 &&
!looksLikeCompactTimestamp(span),
},
{
id: "pii.ssn",

View File

@ -203,8 +203,9 @@ describe("PII patterns", () => {
scan("bob@acme.co", { repoVisibility: "private", repoPublicEmails: ["bob@acme.co"] }).findings,
).toHaveLength(0);
});
test("phone E.164", () => {
test("phone E.164 flags, skips compact timestamps", () => {
expect(ids("call +14155550123 now")).toContain("pii.phone.e164");
expect(ids("backup stamp 20260727202423 ran late")).not.toContain("pii.phone.e164");
});
test("ssn flags valid, skips 000 octet", () => {
expect(ids("ssn 123-45-6789")).toContain("pii.ssn");