Skip to content

Fix includeSubdomains dropping all subdomain URLs + broken cache-clear - #15

Merged
stretchcloud merged 1 commit into
mainfrom
fix/map-subdomain-filter
Jul 22, 2026
Merged

Fix includeSubdomains dropping all subdomain URLs + broken cache-clear#15
stretchcloud merged 1 commit into
mainfrom
fix/map-subdomain-filter

Conversation

@stretchcloud

Copy link
Copy Markdown
Owner

Bug 1 — includeSubdomains:true dropped every subdomain URL

POST /api/map on https://www.sonarsource.com with includeSubdomains:true returned 1,727 URLs, all on www. — even though discovery found 38k+ across docs./community./etc. (the sitemaps were pulled). The subdomains it discovered were then thrown away by the filter.

Cause: URLValidationUtils.matchesDomain compared each candidate against the full seed hostname. For a www.<domain> seed it kept only www.<domain> and *.www.<domain>, so docs.sonarsource.com failed both checks and was filtered out — discarding exactly what includeSubdomains exists to include.

Fix: match on the registrable domain. New URLValidationUtils.registrableDomain() reduces www.sonarsource.com and docs.sonarsource.com both to sonarsource.com (with a curated two-level-TLD set so docs.a.co.uk → a.co.uk, not co.uk — which would otherwise match every .co.uk site). The discovery service's baseDomainOf now delegates to the same helper, so the subdomains it finds and the subdomains the filter keeps can't disagree.

Bug 2 — cache/clear was a silent no-op

POST /api/map/cache/clear returned success:true but cleared nothing. Keys were url-discovery:<sha256> (URL folded into the hash), yet clearCache filtered keys with .includes(encodeURIComponent(url)) — which never appears in a hash. Fixed by embedding a normalized URL token in the key (url-discovery:<urlToken>:<optionsHash>) and clearing by that prefix; clearCache now returns the count and the controller reports keysCleared.

Verification (Docker, against the live sites)

Before After
www.sonarsource.com + includeSubdomains 1,727 (www only) 30,000 — community 19,921 / docs 8,350 / www 1,727 / events 2
cross-domain leakage none (registrable boundary holds)
look-alikes (evilsonarsource.com, sonarsource.com.attacker.net) correctly excluded
cache-clear round-trip reported success, cleared 0 prime→fromCache:true→clear keysCleared:1fromCache:false

tsc ✅ · eslint ✅ · 201 tests ✅ (186 + 15 new) · openapi:check

🤖 Generated with Claude Code

## includeSubdomains filter
map on www.sonarsource.com with includeSubdomains:true returned only 1,727
URLs — all on www. — even though discovery found 38k+ across docs./community./
etc. `URLValidationUtils.matchesDomain` compared candidates against the full
SEED hostname: for a `www.<domain>` seed it matched only `www.<domain>` and
`*.www.<domain>`, so `docs.<domain>` failed and was filtered out — discarding
exactly the subdomains includeSubdomains exists to include.

Match on the REGISTRABLE domain instead. New `URLValidationUtils.registrable
Domain()` reduces www.sonarsource.com and docs.sonarsource.com both to
sonarsource.com (with a curated two-level-TLD set so docs.a.co.uk -> a.co.uk,
not co.uk). matchesDomain(includeSubdomains) now compares registrable domains.
The discovery service's baseDomainOf delegates to the same helper so the
subdomains it *finds* and the subdomains the filter *keeps* can't disagree.

Result (Docker, live sites): www.sonarsource.com + includeSubdomains goes
1,727 -> 30,000 (community 19,921 / docs 8,350 / www 1,727 / events 2), zero
cross-domain leakage. Look-alikes (evilsonarsource.com, sonarsource.com.attacker
.net) still correctly excluded.

## cache-clear no-op
POST /api/map/cache/clear returned success but cleared nothing. Cache keys were
`url-discovery:<sha256>` (URL folded into the hash), yet clearCache matched keys
with `.includes(encodeURIComponent(url))` — which never appears in a hash.

Embed a normalized URL token in the key
(`url-discovery:<urlToken>:<optionsHash>`) and clear by that prefix. clearCache
now returns the count; the controller reports `keysCleared`. Verified: prime ->
fromCache:true -> clear (keysCleared:1) -> fromCache:false.

tsc, eslint, 201 tests (186 + 15 new), openapi:check all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@stretchcloud
stretchcloud merged commit a21658e into main Jul 22, 2026
1 check passed
@stretchcloud
stretchcloud deleted the fix/map-subdomain-filter branch July 22, 2026 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant