From 4ea2cffd2c901b652b1236a3d0eb3ce39cc62c1d Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 8 Sep 2026 15:09:11 +0200 Subject: docs: README covers init and parse Marks what is built rather than describing the whole pipeline as though it existed, documents the three ways to answer the trust-boundary question, and shows what a case directory holds and what an origin and confidence mean on an indicator. Every command in it was run before committing, including the two non-interactive forms. The tests section names the two checks that are not ordinary unit tests, because they are the ones a reader would otherwise not know to keep: the Received-chain mutation check, and the parser suite running with sockets disabled. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KphFXTc2QajxXsHWyvGJ4R --- README.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 10 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 78eb42f..9354ee0 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,84 @@ 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: early.** The design is settled and the first part is being built. -Nothing here submits anything yet. +**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. ## What it does ``` +abusectl init # asks, writes ~/.config/abusectl/config.toml abusectl parse msg.eml # -> case directory, IOCs offline abusectl contacts # + abuse contacts via RDAP network, read-only abusectl report # + report bodies offline # review the bodies, by hand or in a mail client abusectl submit # MISP, then the vendors network, writes +abusectl retry # whatever is due cron ``` -Each subcommand runs on its own and is useful on its own. `parse` triages a -message with no configuration at all; `parse`, `contacts` and `report` -together produce a document you can send by hand with no API key anywhere. +`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. -State lives in a **case directory** rather than in memory, so a review can -take a week and survive a reboot. +State lives in a **case directory** rather than in memory, so a review can take +a week and survive a reboot. + +## Getting started + +```bash +abusectl init # asks, writes the config +abusectl parse message.eml # prints the case directory it created +``` + +`init` needs to know which `Received` hops your own mail infrastructure adds, +because everything below that boundary was written by whoever was talking to +your server and can be forged. Three ways to answer, at one prompt: + +- type the CIDRs, if you know them +- name a provider: `gmail`, `fastmail`, `proton`, `outlook`, `privateemail`, + `zoho`, whose published sending ranges ship with the tool +- point it at a message you know arrived legitimately with + `--from-sample good.eml` and pick your own hops out of the real chain + +`parse` refuses to run until that boundary is set. Guessing it wrong means +reporting an innocent third party, so it does not guess. + +For scripted or agent-driven setup, every question is also a flag and nothing +prompts: + +```bash +abusectl init --non-interactive --trusted-relays 192.0.2.0/24 198.51.100.0/24 +abusectl init --non-interactive --provider fastmail +``` + +Re-running `init` shows what is already configured and asks before replacing +it. Either way the old file is copied to `config.toml.bak-` first, +and any section this run does not set, such as `[misp]`, is carried across +untouched. + +## What a case looks like + +``` +~/.local/share/abusectl/2026-09-08-a3f1/ + source.eml the original, unredacted + manifest.json IOCs, and later contacts and per-destination status + bodies/ report bodies, once `report` exists +``` + +`manifest.json` after `parse`: + +```json +{ + "id": "ioc-1", "type": "ipv4", "value": "203.0.113.99", + "origin": "received-chain", "confidence": "boundary-hop" +} +``` + +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 @@ -56,9 +115,23 @@ gets its own spec before it is built. ## Requirements -Python 3.11 or newer. `parse` and `report` need nothing else; `contacts` needs -an HTTP client and `submit` needs PyMISP. Development runs from a venv in the -checkout. +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. + +## Tests + +```bash +python3 -m unittest discover tests +``` + +100 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. ## License -- cgit v1.2.3