From 64a4bc6f3561583a247e1fbbf8a5553720e1aefc Mon Sep 17 00:00:00 2001 From: Victor Solano Date: Tue, 8 Sep 2026 15:36:51 +0200 Subject: [PATCH 1/3] fix: avoid redacting structured numeric values as phones --- lib/utils/pii.test.ts | 91 +++++++++++++++++++++++-------------------- lib/utils/pii.ts | 14 ++++--- 2 files changed, 57 insertions(+), 48 deletions(-) diff --git a/lib/utils/pii.test.ts b/lib/utils/pii.test.ts index 96c8b7d..7894d48 100644 --- a/lib/utils/pii.test.ts +++ b/lib/utils/pii.test.ts @@ -22,6 +22,11 @@ describe("redactPii", () => { "Call me at [PHONE REDACTED]", ); }); + it("redacts domestic phone numbers with a leading zero", () => { + expect(redactPhones("Call me at 07700 900123")).toBe( + "Call me at [PHONE REDACTED]", + ); + }); it("leaves a short extension-only digit run alone", () => { expect(redactPhones("ext 12")).toBe("ext 12"); @@ -33,11 +38,9 @@ describe("redactPii", () => { expect(redactPhones("call 555-1212 ext 12")).toBe("call [PHONE REDACTED]"); }); - it("documents that a long non-phone digit run is over-redacted", () => { - // Known over-match: an order ID with enough digits is treated as a phone - // number. Conservative redaction is intentional in the utility. + it("leaves a long non-phone digit run alone", () => { expect(redactPhones("Order 12345678901234567890")).toBe( - "Order [PHONE REDACTED]", + "Order 12345678901234567890", ); }); @@ -58,7 +61,7 @@ describe("redactPii", () => { ); }); - // over-redacts scenarios + // non-phone structured values it("over-redacts emails with invalid local-part separators", () => { expect( @@ -106,92 +109,94 @@ describe("redactPii", () => { ).toBe("Values: [EMAIL REDACTED] [EMAIL REDACTED] [EMAIL REDACTED]"); }); - it("over-redacts a plain number as a phone number", () => { + it("leaves a plain number alone", () => { expect(redactPhones("Reference number: 1234567")).toBe( - "Reference number: [PHONE REDACTED]", + "Reference number: 1234567", ); }); - it("over-redacts a long numeric identifier as a phone number", () => { + it("leaves a long numeric identifier alone", () => { expect(redactPhones("Order ID: 001234567890")).toBe( - "Order ID: [PHONE REDACTED]", + "Order ID: 001234567890", ); }); - it("over-redacts a version number as a phone number", () => { + it("leaves a version number alone", () => { expect(redactPhones("Build version: 1.234.567")).toBe( - "Build version: [PHONE REDACTED]", + "Build version: 1.234.567", ); }); - it("over-redacts an IP address as a phone number", () => { + it("leaves an IP address alone", () => { expect(redactPhones("Server IP: 192.168.1.1")).toBe( - "Server IP: [PHONE REDACTED]", + "Server IP: 192.168.1.1", ); }); - it("over-redacts a date-like value as a phone number", () => { - expect(redactPhones("Reference date: 2026-08-06")).toBe( - "Reference date: [PHONE REDACTED]", - ); + it("leaves an ISO date and timestamp alone", () => { + const input = "Created at: 2026-01-14T13:50:34.240Z"; + + expect(redactPhones(input)).toBe(input); }); - it("over-redacts a timestamp as a phone number", () => { + it("leaves a compact timestamp alone", () => { expect(redactPhones("Created at: 20260806144500")).toBe( - "Created at: [PHONE REDACTED]", + "Created at: 20260806144500", ); }); - it("over-redacts a postal code as a phone number", () => { - expect(redactPhones("PIN code: 11000123")).toBe( - "PIN code: [PHONE REDACTED]", - ); + it("leaves a postal code alone", () => { + expect(redactPhones("PIN code: 11000123")).toBe("PIN code: 11000123"); }); - it("over-redacts a currency amount as a phone number", () => { + it("leaves a currency amount alone", () => { expect(redactPhones("Total amount: 123.456.789")).toBe( - "Total amount: [PHONE REDACTED]", + "Total amount: 123.456.789", ); }); - it("over-redacts coordinates as a phone number", () => { + it("leaves coordinates alone", () => { expect(redactPhones("Location: 28.6139 77.2090")).toBe( - "Location: [PHONE REDACTED]", + "Location: 28.6139 77.2090", ); }); - it("over-redacts an all-zero placeholder as a phone number", () => { + it("leaves an all-zero placeholder alone", () => { expect(redactPhones("Placeholder: 000-000-0000")).toBe( - "Placeholder: [PHONE REDACTED]", + "Placeholder: 000-000-0000", ); }); - it("over-redacts a parenthesized value as a phone number", () => { + it("leaves a parenthesized value alone", () => { expect(redactPhones("Value: (2026) 1234567")).toBe( - "Value: [PHONE REDACTED]", + "Value: (2026) 1234567", ); }); - it("over-redacts an extension-like value as a phone number", () => { + it("leaves an extension-like value alone", () => { expect(redactPhones("Extension: ext-1234568")).toBe( - "Extension: ext-[PHONE REDACTED]", + "Extension: ext-1234568", ); }); - it("over-redacts a number embedded in an identifier", () => { + it("leaves a number embedded in an identifier alone", () => { expect(redactPhones("Serial number: ABC-1234567-XYZ")).toBe( - "Serial number: ABC-[PHONE REDACTED]-XYZ", + "Serial number: ABC-1234567-XYZ", ); }); - it("over-redacts multiple non-phone values in one string", () => { - expect( - redactPhones( - "Date 2026-08-06, IP 192.168.1.1, version 1.234.567, order 001234567890", - ), - ).toBe( - "Date [PHONE REDACTED], IP [PHONE REDACTED], version [PHONE REDACTED], order [PHONE REDACTED]", - ); + it("leaves multiple non-phone values alone", () => { + const input = + "Date 2026-08-06, IP 192.168.1.1, version 1.234.567, order 001234567890"; + + expect(redactPhones(input)).toBe(input); + }); + + it("leaves CVE identifiers, token counts, and commit ranges alone", () => { + const input = + "CVE-2024-12345 used 8 240 000 000 tokens; commits abc1234..def5678"; + + expect(redactPhones(input)).toBe(input); }); it("over-redacts an email embedded in a larger token", () => { diff --git a/lib/utils/pii.ts b/lib/utils/pii.ts index 5330a75..d3a0697 100644 --- a/lib/utils/pii.ts +++ b/lib/utils/pii.ts @@ -4,19 +4,22 @@ * Applied to all user-submitted text before insertion into the database. * Redacts email addresses and phone numbers with placeholder tokens. * - * These patterns are intentionally conservative — they may over-match in - * edge cases, but that's preferable to leaking PII. + * These patterns favor common phone and email shapes and avoid redacting + * structured identifiers such as dates and version numbers. */ /** Matches common email formats */ const EMAIL_PATTERN = /[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g; /** - * Matches phone numbers in common formats: - * +1-555-555-5555, (555) 555-5555, 555.555.5555, +44 7700 900123, etc. + * Matches phone numbers with a recognizable phone structure: + * +1-555-555-5555, (555) 555-1212, 555.555.5555, +44 7700 900123, etc. + * + * Requiring a separator between phone-sized groups avoids treating arbitrary + * digit runs, dates, versions, and identifiers as phone numbers. */ const PHONE_PATTERN = - /(? { const digits = match.replace(/\D/g, ""); if (digits.length < MIN_PHONE_DIGITS) return match; + if (/^0+$/.test(digits)) return match; return "[PHONE REDACTED]"; }); } From 7dd3801c37f6fccae02389fa4728a95f0ce282dc Mon Sep 17 00:00:00 2001 From: Victor Solano Date: Tue, 8 Sep 2026 16:31:32 +0200 Subject: [PATCH 2/3] Fix compact international phone redaction --- lib/utils/pii.test.ts | 5 +++++ lib/utils/pii.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/utils/pii.test.ts b/lib/utils/pii.test.ts index 7894d48..39dbae8 100644 --- a/lib/utils/pii.test.ts +++ b/lib/utils/pii.test.ts @@ -22,6 +22,11 @@ describe("redactPii", () => { "Call me at [PHONE REDACTED]", ); }); + it("redacts compact international phone numbers", () => { + expect(redactPhones("Call me at +447700900123")).toBe( + "Call me at [PHONE REDACTED]", + ); + }); it("redacts domestic phone numbers with a leading zero", () => { expect(redactPhones("Call me at 07700 900123")).toBe( "Call me at [PHONE REDACTED]", diff --git a/lib/utils/pii.ts b/lib/utils/pii.ts index d3a0697..3c7d7e6 100644 --- a/lib/utils/pii.ts +++ b/lib/utils/pii.ts @@ -19,7 +19,7 @@ const EMAIL_PATTERN = /[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g; * digit runs, dates, versions, and identifiers as phone numbers. */ const PHONE_PATTERN = - /(? Date: Tue, 8 Sep 2026 20:57:55 +0530 Subject: [PATCH 3/3] fix: prettier formatting --- lib/utils/pii.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/utils/pii.test.ts b/lib/utils/pii.test.ts index 39dbae8..ff25f8c 100644 --- a/lib/utils/pii.test.ts +++ b/lib/utils/pii.test.ts @@ -173,9 +173,7 @@ describe("redactPii", () => { }); it("leaves a parenthesized value alone", () => { - expect(redactPhones("Value: (2026) 1234567")).toBe( - "Value: (2026) 1234567", - ); + expect(redactPhones("Value: (2026) 1234567")).toBe("Value: (2026) 1234567"); }); it("leaves an extension-like value alone", () => {