Summary
The user URL allowlist is currently overloaded to mean two different things at once:
- "Trust these exact hosts" (exclude them from scanning), and
- "Protect these brands from squatting" (treat them as protected domains to match lookalikes against).
These are separate concerns and should not share one list. Conflating them produces a surprising and security-relevant result: allowlisting a domain can make the customer's own legitimate lookalike host trip the squatting detector, which is the opposite of what an admin expects from a "whitelist."
This issue proposes separating the control into a dedicated protected-brands configuration option, distinct from urlAllowlist, and is intended to be implemented on its own branch later. It is filed for tracking and design, not for immediate merge.
Current behavior (evidence)
In scripts/modules/domain-squatting-detector.js:
initialize() extracts domains from the URL allowlist and merges them into protectedDomains:
const allowlistDomains = this.extractDomainsFromAllowlist(urlAllowlist);
if (allowlistDomains.length > 0) {
this.protectedDomains = [...new Set([...this.protectedDomains, ...allowlistDomains])];
}
checkDomain() only skips an exact base-domain self-match, then scores the visited host against every other protected domain:
for (const protectedDomain of this.protectedDomains) {
const protectedBase = this.extractBaseDomain(protectedDomain);
if (testBase === protectedBase) { continue; } // only exact self-match skipped
// ... levenshtein / homoglyph / typosquat / combosquat vs every OTHER brand
}
Because the skip is per-protected-domain (a continue), not a global allowlist gate, a host that is explicitly on the allowlist is still scored against other seeded brands.
Worked example (from a real report)
Allowlist contained, among others:
salesforce.com
https://client.my.salesforce.com/
https://client.my.salesforce-setup.com/
client.my.salesforce-setup.com
Visited host client.my.salesforce-setup.com (base token salesforce-setup) was still flagged as a combosquat of salesforce.com (base token salesforce), even though that exact host was on the allowlist twice. The allowlist entry for salesforce.com is precisely what seeded salesforce as a brand to match against.
Contributing facts that make this worse
rules/detection-rules.json ships "protected_domains": [] (empty), so the URL allowlist seeding is currently the only functional source of protected brands.
initialize() does not read runtimeConfig.domainSquatting.protectedDomains (camelCase, from managed schema / CIPP), so admin-supplied brands are dropped at init.
updateConfig(), which would read protectedDomains, is dead code and is never called.
Net effect: there is no clean, dedicated way for an admin to declare protected brands today, and the only path that works has the harmful side effect described above.
Proposed direction (future work, separate branch)
- Introduce a dedicated protected-brands configuration option (for example
domainSquatting.protectedDomains), surfaced in the managed schema / CIPP and the options UI, that is completely separate from urlAllowlist.
- Stop seeding the URL allowlist into
protectedDomains. The allowlist should be a pure trust list.
- Add a global allowlist short-circuit at the top of
checkDomain() that returns null when the visited host is on the user allowlist. Match on exact host plus subdomains only (do not use the loose base-domain token compare), so that close typosquats of a trusted brand are still detected.
- Wire the dedicated brand config through
initialize() (and retire or fix the dead updateConfig() path) so brands from rules and managed policy are honored.
Acceptance criteria
- Allowlisting a host excludes that host and its subdomains from squatting detection entirely.
- Allowlisting a host does not implicitly turn its brand into a protected domain.
- Protected brands are configured through their own option, independent of the allowlist.
- Close typosquats of a trusted brand are still detected (the exclusion is exact-host plus subdomain, not base-token).
Notes
Deferred by design. This will be implemented on a separate branch. Related shipped fixes: the log action handling (#161) and the deep-link allowlist matching in urlPatternToRegex (#162).
Summary
The user URL allowlist is currently overloaded to mean two different things at once:
These are separate concerns and should not share one list. Conflating them produces a surprising and security-relevant result: allowlisting a domain can make the customer's own legitimate lookalike host trip the squatting detector, which is the opposite of what an admin expects from a "whitelist."
This issue proposes separating the control into a dedicated protected-brands configuration option, distinct from
urlAllowlist, and is intended to be implemented on its own branch later. It is filed for tracking and design, not for immediate merge.Current behavior (evidence)
In
scripts/modules/domain-squatting-detector.js:initialize()extracts domains from the URL allowlist and merges them intoprotectedDomains:checkDomain()only skips an exact base-domain self-match, then scores the visited host against every other protected domain:Because the skip is per-protected-domain (a
continue), not a global allowlist gate, a host that is explicitly on the allowlist is still scored against other seeded brands.Worked example (from a real report)
Allowlist contained, among others:
salesforce.comhttps://client.my.salesforce.com/https://client.my.salesforce-setup.com/client.my.salesforce-setup.comVisited host
client.my.salesforce-setup.com(base tokensalesforce-setup) was still flagged as a combosquat ofsalesforce.com(base tokensalesforce), even though that exact host was on the allowlist twice. The allowlist entry forsalesforce.comis precisely what seededsalesforceas a brand to match against.Contributing facts that make this worse
rules/detection-rules.jsonships"protected_domains": [](empty), so the URL allowlist seeding is currently the only functional source of protected brands.initialize()does not readruntimeConfig.domainSquatting.protectedDomains(camelCase, from managed schema / CIPP), so admin-supplied brands are dropped at init.updateConfig(), which would readprotectedDomains, is dead code and is never called.Net effect: there is no clean, dedicated way for an admin to declare protected brands today, and the only path that works has the harmful side effect described above.
Proposed direction (future work, separate branch)
domainSquatting.protectedDomains), surfaced in the managed schema / CIPP and the options UI, that is completely separate fromurlAllowlist.protectedDomains. The allowlist should be a pure trust list.checkDomain()that returns null when the visited host is on the user allowlist. Match on exact host plus subdomains only (do not use the loose base-domain token compare), so that close typosquats of a trusted brand are still detected.initialize()(and retire or fix the deadupdateConfig()path) so brands from rules and managed policy are honored.Acceptance criteria
Notes
Deferred by design. This will be implemented on a separate branch. Related shipped fixes: the
logaction handling (#161) and the deep-link allowlist matching inurlPatternToRegex(#162).