diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 66 |
1 files changed, 51 insertions, 15 deletions
@@ -4,9 +4,9 @@ Abuse reporting for phishing mail. Parses a flagged message, extracts its indicators, resolves who to report each one to, and files the result to a MISP instance and to public abuse channels. -**Status: `init` and `parse` are built.** The rest of the pipeline is designed -but not written, see `docs/specs/2026-09-08-abusectl-design.md`. Nothing here -submits anything to anyone yet. +**Status: `init`, `parse` and `contacts` are built.** The rest of the pipeline +is designed but not written, see `docs/specs/2026-09-08-abusectl-design.md`. +Nothing here submits anything to anyone yet. ## What it does @@ -20,10 +20,10 @@ abusectl submit <case> # MISP, then the vendors network, writes abusectl retry # whatever is due cron ``` -`init` and `parse` exist today. Each subcommand runs on its own and is useful -on its own: `parse` triages a message with no keys configured at all, and -`parse` plus `contacts` plus `report` will produce a document you can send by -hand before any API key exists. +`init`, `parse` and `contacts` exist today. Each subcommand runs on its own and +is useful on its own: `parse` triages a message with no keys configured at all, +and `parse` plus `contacts` plus `report` will produce a document you can send +by hand before any API key exists. State lives in a **case directory** rather than in memory, so a review can take a week and survive a reboot. @@ -61,6 +61,34 @@ it. Either way the old file is copied to `config.toml.bak-<timestamp>` first, and any section this run does not set, such as `[misp]`, is carried across untouched. +## Resolving the contacts + +```bash +abusectl contacts ~/.local/share/abusectl/2026-09-08-a3f1 +``` + +`contacts` reads the indicators `parse` recorded and asks the registries who to +report each one to, over RDAP. This is the first command that needs **network +access**, and no API key: RDAP is public. + +It is **read-only**. It queries registries and sends nothing to anyone, so +running it does not report you, or the message, to anybody. A query carries a +bare host or IP address and never a URL, so the path and query string of a +phishing link stay on your machine. Twenty URLs on one host produce one query. + +**A re-run is safe.** It overwrites the contacts in the manifest rather than +merging into them, so a run interrupted halfway leaves nothing half-merged, and +running it again after a registry recovers simply replaces the result. The +indicators themselves are untouched. + +**A missing abuse contact is a normal outcome, not an error.** Many TLDs publish +no RDAP service at all, and a registry that answers may publish a handle with no +`abuse` role on it. Those are recorded with the reason, and the run continues: +one indicator failing must not cost the contacts that did resolve. An indicator +that is not a bare host or IP is kept as unusable, with why, rather than +silently dropped, so you can see during review whether the parser mangled a real +host or the sender planted something. + ## What a case looks like ``` @@ -83,7 +111,7 @@ Every indicator says where it came from. An IP from the trust boundary is `boundary-hop`, the one address that can be stood behind; anything below it is `untrusted-hop`, recorded because it may be useful but never presented as fact. -## Two properties that are not negotiable +## Three properties that are not negotiable **Recipient identifiers are never captured.** Not the `To`, `Cc`, `Delivered-To` or `X-Original-To` headers, not your Message-IDs, not maildir @@ -98,6 +126,12 @@ redirect chains, not remote images. Following a link confirms your address is live to the sender and fires exactly the tracker the message wanted. Redirect chains are read from headers and link text, never by following them. +**A registry query carries a bare host or IP, never a URL.** `contacts` is the +first command that talks to anyone, and an RDAP query tells a third party what +you are looking at. A URL path can carry your identity, and the registrar you +ask may be the attacker's own, so the path, query and fragment of a phishing +link never leave your machine. Only the host does. + ## Reversible and irreversible `submit` writes to MISP first and stops if that fails. MISP is your own @@ -115,10 +149,11 @@ gets its own spec before it is built. ## Requirements -Python 3.12 or newer, and nothing else for what is built today: `init` and -`parse` are standard library only. `contacts` will need an HTTP client and -`submit` will need PyMISP, so a venv in the checkout is the development -arrangement; packaging comes once the tool does something worth installing. +Python 3.12 or newer, and nothing else for what is built today: `init`, `parse` +and `contacts` are standard library only, `contacts` included, which reaches the +registries with `urllib`. `submit` will need PyMISP, so a venv in the checkout is +the development arrangement; packaging comes once the tool does something worth +installing. ## Tests @@ -126,12 +161,13 @@ arrangement; packaging comes once the tool does something worth installing. python3 -m unittest discover tests ``` -100 tests, no framework, no network. Two of them are not ordinary unit tests +192 tests, no framework, no network. Two of them are not ordinary unit tests and are the ones worth knowing about. The `Received`-chain test is mutation-checked: walking one hop too far makes it report an innocent party named in a header the attacker wrote, and the test fails if that regresses. -And the parser's suite passes with `socket` disabled entirely, so -"nothing is fetched" is verified rather than documented. +And the suite passes with `socket` disabled entirely, `contacts` and its RDAP +code included, so "nothing is fetched" is verified rather than documented. The +network goes in through a `fetch` argument the tests replace. ## License |
