aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_contacts.py
AgeCommit message (Collapse)AuthorFilesLines
37 hoursfix: validate a query host at the one point a query is admittedDanilo M.1-0/+98
THE FOURTH PROPERTY was breached through the domain branch of worklist(). The url branch is cleaned by _host_of, which uses urlsplit().hostname, and the domain branch did only value.strip(".").lower(). rdap.query_domain then interpolates that value into the fetch URL with no quoting. parse._domain_of takes everything after the @ of a From, Sender or Reply-To addr-spec, and email.utils.parseaddr permits /, ?, # and % there, so the whole shape is attacker-controlled through a header they own. A From of `Bank <phish@victim%40example.org.invalid>` sent the recipient's own address to a registry, which is precisely the identity disclosure the property exists to prevent, reaching a third party. `a/../../x.invalid` escaped the /domain/ endpoint altogether, and query, fragment and space values all reached the wire. Fixed at the SINGLE admission point rather than in the offending branch, because per-branch validation is what failed here: one branch was cleaned, the next was written without it. is_queryable() now guards worklist()'s add(), so domain, url and any future branch pass through it. A rejected value is not dropped. It keeps an entry with an empty abuse list and an error saying it was never queried, following the rule suspect_path_segments already sets: flagged and visible to the user during review, because silent was the bug. A non-ASCII host is refused rather than encoded to punycode. Guessing the encoding of an attacker-supplied name is a query that cannot be justified, and the ceiling is noted in a ponytail comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wrfqr2xqQfhtXCscU7zrdz
37 hoursfeat: resolve the worklist to abuse contactsDanilo M.1-0/+126
Failure is per query and never stops the run: a timeout on one indicator must not cost the contacts that did resolve, and a missing contact is a normal outcome rather than an error. Bootstraps are passed in rather than fetched here, so this stays testable with no network and the caller owns the cache policy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wrfqr2xqQfhtXCscU7zrdz
37 hoursfeat: build the contacts worklist, host onlyDanilo M.1-0/+113
Adds the fourth non-negotiable property: a query carries a bare host or IP and never a URL. An RDAP query discloses what the user is looking at, and a URL path can carry recipient identity that parse deliberately flags rather than redacts, because a path segment may be the thing being reported. That decision is safe only while the URL stays local. Property 1 governs what is published and a query appears in no report, so property 1 does not cover this and this property does. Hosts fold, so twenty URLs on one host make one query while the item keeps every indicator id behind it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wrfqr2xqQfhtXCscU7zrdz