fix: stop flagging ordinary company names as privacy services

A weak word ("private", "protection") plus a generic context word ("services",
"contact") counted as a privacy service, so "Tata Consultancy Services Private
Limited" (the standard Indian company form), "Allied Fire Protection Services",
and "Contact Lens Protection Ltd" got privacyService/redacted/privacyEnabled.
"privado" and "for privacy" matched as substrings ("Banco Privado Atlántico",
"Center for Privacy and Technology").

Keep only domain-specific context words, ignore legal forms like "Private
Limited", "Pvt. Ltd.", and "Pte. Ltd.", and match Spanish services by phrase.
No change across the saved WHOIS corpus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Opus 5.5 committed 2026-10-03 15:16:53 -04:00
1 parent bca20bc5f0
commit 2fc9f4dfc8
2 files changed
+32 -10

No files matched your search

+18 -1
View File
@@ -13,13 +13,30 @@ test("isPrivacyName flags redaction notices and privacy services", () => {
"Identity Protection Service",
"Withheld for Privacy ehf",
"Datos Privados",
"Contacto Privado",
"Proxy Protection LLC",
"Whois Privacy Services Pty Ltd",
]) {
expect(isPrivacyName(v), v).toBe(true);
}
});
test("isPrivacyName does not flag ordinary names with ambiguous words", () => {
for (const v of ["Private Equity Partners LLC", "Protection One", "Jane Private", "Acme Inc"]) {
for (const v of [
"Private Equity Partners LLC",
"Protection One",
"Jane Private",
"Acme Inc",
"Tata Consultancy Services Private Limited",
"Endurance Domains Technology Pvt. Ltd.",
"Example Holdings Pte. Ltd.",
"Allied Fire Protection Services Inc",
"Private Client Services Ltd",
"Contact Lens Protection Ltd",
"Banco Privado Atlantico",
"Hospital Privado de Córdoba S.A.",
"Center for Privacy and Technology",
]) {
expect(isPrivacyName(v), v).toBe(false);
}
});
+14 -9
View File
@@ -19,8 +19,10 @@ const REDACTION_PHRASES = [
* redaction notices these can be real registrant text worth showing, so they aren't placeholders.
*/
const PRIVACY_SERVICE_PHRASES = [
"privado", // Spanish
"datos privados", // Spanish
// Spanish; not "privado" alone, which names real banks, hospitals, and schools
"datos privados",
"contacto privado",
"registro privado",
"registration private",
"private registration",
"whois privacy",
@@ -31,7 +33,6 @@ const PRIVACY_SERVICE_PHRASES = [
"contact privacy",
"domains by proxy",
"proxy service",
"for privacy",
];
/**
@@ -41,7 +42,9 @@ export const PRIVACY_STRONG_KEYWORDS = [...REDACTION_PHRASES, ...PRIVACY_SERVICE
/**
* Words too ambiguous to trust alone ("Private Equity LLC", "Protection One"). They only count
* when at least two distinct terms from WEAK + CONTEXT appear as whole words.
* when at least two distinct terms from WEAK + CONTEXT appear as whole words. Context words are
* domain-specific: generic ones like "services" or "contact" turn up in ordinary company names
* ("Allied Fire Protection Services", "Contact Lens Protection Ltd").
*/
const PRIVACY_WEAK_WORDS = ["privacy", "private", "protect", "protected", "protection"];
const PRIVACY_CONTEXT_WORDS = [
@@ -51,13 +54,14 @@ const PRIVACY_CONTEXT_WORDS = [
"domains",
"registration",
"guard",
"service",
"services",
"masked",
"anonymous",
"identity",
"contact",
];
// Legal forms containing a weak word: "Tata Consultancy Services Private Limited" (India),
// "Example Pte. Ltd." (Singapore)
const LEGAL_FORM_RE = /\(?\b(?:private|pvt|pte)\b\)?\.?\s*(?:limited|ltd)\b\.?/g;
const WEAK_WORD_RE = new RegExp(`\\b(?:${PRIVACY_WEAK_WORDS.join("|")})\\b`, "g");
const CONTEXT_WORD_RE = new RegExp(
`\\b(?:${[...PRIVACY_WEAK_WORDS, ...PRIVACY_CONTEXT_WORDS].join("|")})\\b`,
@@ -68,9 +72,10 @@ const CONTEXT_WORD_RE = new RegExp(
export function isPrivacyName(value: string): boolean {
const v = value.toLowerCase().trim();
if (PRIVACY_STRONG_KEYWORDS.some((k) => v.includes(k))) return true;
if (!WEAK_WORD_RE.test(v)) return false;
const name = v.replace(LEGAL_FORM_RE, " ");
WEAK_WORD_RE.lastIndex = 0;
return new Set(v.match(CONTEXT_WORD_RE)).size >= 2;
if (!WEAK_WORD_RE.test(name)) return false;
return new Set(name.match(CONTEXT_WORD_RE)).size >= 2;
}
const escapeRe = (v: string) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");