Fix is_external_url misclassifying sibling domains as internal#2031
Fix is_external_url misclassifying sibling domains as internal#2031jichaowang02-lang wants to merge 1 commit into
Conversation
is_external_url decided same-site vs external with `not url_domain.endswith(base)`,
a raw string-suffix test with no domain-label boundary. Any host that merely
ends with the base string was treated as internal, so look-alike / sibling
domains were mislabeled:
is_external_url("https://notexample.com/x", "example.com") -> False (internal)
even though notexample.com is a different registrable domain. This corrupts the
internal/external link buckets that quick_extract_links() and deep-crawl scoping
rely on (and lets a phishing-style look-alike host pass as same-site).
Require a label boundary: same site iff the domain equals base or is a true
sub-domain (`url_domain == base or url_domain.endswith("." + base)`). Real
subdomains (sub.example.com) and the www variant stay internal; unrelated and
look-alike domains are correctly external.
Adds TestIsExternalUrl in tests/regression/test_reg_utils.py; the sibling-domain
cases fail on the old code and pass with this fix.
chuenchen309
left a comment
There was a problem hiding this comment.
Traced this against the base string and it's a real fix. The old not url_domain.endswith(base) matches on a raw string suffix with no label boundary, so for base = "example.com", "notexample.com".endswith("example.com") is True → the host was classified as internal. That's not just cosmetic link-bucketing — it lets a look-alike/sibling domain (evilexample.com, myexample.com) slip inside the internal set, which for deep-crawl scoping means the crawler can walk off the intended site.
The fix is the standard domain-suffix check: exact match, or endswith("." + base) so a real label boundary is required. Walked the cases:
example.com== base → internal ✓sub.example.com/deep.sub.example.comend with.example.com→ internal ✓notexample.com/evilexample.comdon't → external ✓ (the regression)other.com→ external ✓
Test coverage matches the fix well (same-domain, www variant, real subdomain, unrelated, the parametrized sibling look-alikes, mailto, relative). LGTM.
One adjacent note, not from this PR: the .replace("www.", "") on both domains strips www. anywhere in the string rather than only a leading prefix, so a host like myww w.foo style edge cases could normalize oddly — pre-existing and out of scope here, just flagging in case it's worth a separate follow-up.
Summary
is_external_urldecides same-site vs external with:str.endswithis a raw string-suffix test with no domain-label boundary, soany host whose name merely ends with the base-domain string is treated as
internal:
notexample.com,myexample.com,evilexample.comare all differentregistrable domains from
example.com, but each ends with the stringexample.com, so they are wrongly bucketed as internal.This matters because
quick_extract_links()callsget_base_domain(base_url)then
is_external_url(...)to split every<a href>into the internal /external lists that drive deep-crawl scoping and link reporting — so a
deep crawl can follow a look-alike/sibling domain as if it were the same site,
and internal/external link sets are simply wrong.
Fix
Require a label boundary — a host is same-site only if it equals the base
domain or is a true sub-domain of it:
example.com)example.comwww.example.comsub.example.comnotexample.comevilexample.comother.comTesting
Adds
TestIsExternalUrltotests/regression/test_reg_utils.py:The sibling/look-alike cases fail on the current code (3 failed) and pass
with this fix (9 passed); same-domain,
www, real-subdomain, unrelated,special-scheme, and relative-URL cases are all preserved.