aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_cli.py
AgeCommit message (Collapse)AuthorFilesLines
28 hoursfeat: add the report subcommandDanilo M.1-0/+200
Refuses without a configured reporter identity rather than filing a report with no reply address, and turns a frozen or edited case into an error message rather than a traceback. The identity rule is EMAIL REQUIRED, NAME AND ORG OPTIONAL, not the plan's "all three required". Each [reporter] key is individually skippable at init and config drops a skipped one rather than storing "", so a partial identity is a normal shape; text_part() already renders whatever subset is present. Requiring all three would refuse a config init itself writes without complaint. The address is different in kind because it becomes the From. That closes backlog item 5: report.build() read identity["email"] directly and raised a bare KeyError on a name-without-address identity, which is exactly what skipping one init prompt while answering another produces. The check lives in the command rather than the builder, since that is what decides whether the user sees an exit code and a sentence or a traceback. Two further defects in the plan's code, both fixed here: - It called config.load() with no argument, so the global --config option was silently ignored for this subcommand alone. It now resolves the path the way _cmd_parse does. Reporting against the wrong identity is the failure that would have caused. - Its summary printed counts and no path. Nothing sends these bodies yet, so reviewing them by hand is the entire point of the command, and the case's bodies directory is now named in the output. case.py stays the only writer of the manifest: generate() returns it and case.save() writes it atomically. Bodies are written before that save, so a failed save leaves bodies the manifest does not record; the next run finds no recorded hash, treats them as regenerable rather than hand-edited and overwrites them, and nothing has been sent, so no machinery is warranted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LByBnw83xr9YP85nskzkyE
44 hoursfix: strip the envelope recipient in every shape the grammar allowsDanilo M.1-1/+6
The for-clause rule anchored on the clause terminator, which is strictly stronger than RFC 5321 4.4: For sits inside Opt-info, so With, ID, Via or a CFWS comment may legitimately follow it, and its ABNF is 1*( Path / Mailbox ) where Mailbox carries no angle brackets. Only the tidiest shape was stripped. Four ordinary ones published the victim's address in the one header a report reproduces verbatim, including "for <a@b> (envelope-from <c@d>);", which is routine Exim and Sendmail output. Anchored on the address instead. The envelope SENDER survives the cut, since that is what the report is about, and the leftover whitespace, orphan separator and emptied comment are tidied because the line is published to a third party verbatim. Table-driven over all five shapes; reverting the regex fails six of nine tests in the class. Two accepted disclosures are now named in the spec's "Where the headers come from" rather than living in a test comment: our own relay's hostname, which the by and authserv-id clauses carry by construction, and attacker-controlled free text in Subject and the From display name, which the whitelist does not and should not filter. The second is asserted as a documented limit so it reads as deliberate, and the manifest assertion now bars the obfuscated you%40example.org spelling alongside the plain one. case.create() seeds an empty headers block so an unparsed case has the same shape as a parsed one. Additive, so FORMAT_VERSION is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xj1ayFRSUQ2u7cwb3S4axE
44 hoursfeat: store a whitelist of publishable headers in the manifestDanilo M.1-1/+19
report must never open source.eml, so parse decides once what may be published and report formats only what it is given. To, Cc, Delivered-To and X-Original-To are absent by construction rather than stripped. Received is cut to the boundary hop alone, in both directions. Above it are our own relays; below it is the attacker's own writing, and a forged chain names an innocent third party there, so publishing a hop below the boundary puts someone else's address into a report a desk will act on. That is the third property applied to disclosure rather than to sending_ip(). Truncating the chain was not sufficient on its own: the surviving line is written by our own relay and records the envelope recipient in its optional "for <addr>" clause, so the whitelist alone would have published the victim's address verbatim in the one header a report reproduces in full. The clause is removed and the rest of the hop kept. The manifest-wide "no example.org" assertion is narrowed to the headers block only, where the whitelist deliberately publishes our receiving relay's name in a by/authserv-id clause. The address itself is still barred there, asserted separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xj1ayFRSUQ2u7cwb3S4axE
2 daysfeat: add the contacts subcommandDanilo M.1-0/+73
A re-run overwrites contacts[] wholesale rather than merging. A merge would let a contact resolved a week ago survive into a report filed today, which is the stale-address hazard the response caching policy already refuses, and overwriting makes a re-run always safe, which matters because a partial network failure makes re-running the natural next step. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wrfqr2xqQfhtXCscU7zrdz
3 daysfeat: command line for init and parseDanilo M.1-0/+167
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KphFXTc2QajxXsHWyvGJ4R