@bobfrankston/rmfmail 1.2.286 → 1.2.288

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.commitmsg CHANGED
@@ -1,43 +1,29 @@
1
- Spam banner: classify the rules instead of reusing the delivery score
1
+ Trust: a message that never crossed a trust boundary proves itself
2
2
 
3
- The trust banner asked "is this forged" and answered it with SpamAssassin's
4
- bottom line, a number tuned to answer "should this be delivered". Four
5
- newsletters Bob subscribed to (Constant Contact, Adobe MAX, Mokin, ActBlue)
6
- scored 2.2 to 10.9 and two of them raised "This message is not what it says
7
- it is" on mail that passes DMARC aligned on a published reject policy,
8
- where that headline is factually false. What scored them was bulk-mail rules
9
- and third-party blocklist entries about hosts they merely link to: Mokin's
10
- 6.0 URIBL_SBL is the Spamhaus listing of the IP of the nameserver of a linked
11
- domain, fourth-order guilt by association, more than the whole 4.5 threshold
12
- on its own.
3
+ Bob 2026-08-27, on his own reply to John Levine about spam handling: "why is
4
+ my own mail spam". It carried no Authentication-Results at all his own
5
+ authenticated submission never left his systems, so nothing added one — and
6
+ `proved` therefore came back false, dropping it to a caution banner that told
7
+ him not to open the links in prose he had just written. What flagged it was
8
+ `URIBL_DBL_SPAM` 8.0 on blueorchestra.org: the domain he was QUOTING, in a
9
+ discussion about spam. Guilt by association at its purest.
13
10
 
14
- trust.ts now reads the `tests=` list that was sitting in the same header and
15
- sorts the rules into what they are evidence OF — forgery, third-party
16
- association, bulk mail, or unclassified — and reads the topmost
17
- Authentication-Results for a DMARC-aligned pass. Severity follows the rules,
18
- never the number:
11
+ SpamAssassin states two proofs in its own rule list that such mail carries
12
+ instead of an AR header:
19
13
 
20
- - a forgery rule fired, or the server itself recorded an SPF/DKIM/DMARC
21
- failure -> danger, unchanged. Phishing and malware URL listings count as
22
- identity evidence, so a lookalike domain with its own valid DKIM cannot
23
- buy silence.
24
- - sender DMARC-proved and every rule that scored is one this file can name,
25
- or the whole score is bulk rules -> info: a new neutral severity with no
26
- headline and no "do not open anything" footer.
27
- - anything else -> caution, including a proved sender whose score came from
28
- rules with no class. Unknown is never a reason to go quiet.
14
+ ALL_TRUSTED Passed through trusted hosts only via SMTP
15
+ DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain
29
16
 
30
- Findings and the score chip now come from one analysis, so they cannot
31
- disagree; the chip goes grey rather than red when the sender is proved or the
32
- score is all bulk, and both name their evidence ("8.0 SH_DBL_HEADERS (A domain
33
- found in headers is listed in DBL)") instead of showing a bare number. The
34
- `threshold/2` chip floor is untouched see docs/spam-false-positives.md on
35
- why tuning it is not the fix.
17
+ DKIM_VALID_AU is the same evidence DMARC alignment rests on the author
18
+ domain signed it and the signature verified so it proves the From line on
19
+ its own. ALL_TRUSTED says nothing untrusted handled the message in transit.
20
+ `proved` now comes from any of the three, and `provedBy` names which, so the
21
+ banner says WHICH proof rather than asserting DMARC that isn't there. His
22
+ message is now a grey note: "…but bob.ma proved it sent this. Valid signature
23
+ from bob.ma, the sending domain itself (DKIM_VALID_AU)."
36
24
 
37
- Over 20,291 messages in the store, 1,131 were flagged spam and every one of
38
- them used to get the red banner: now 294 do, 672 are caution, 165 are quiet
39
- notes. Tests cover all four sample messages verbatim, plus the two ways the
40
- proof could be gamed (a forged Authentication-Results below the server's own,
41
- and one naming a different From).
25
+ Forgery still outranks proof — across the store the danger count is unchanged
26
+ at 294 of 1,131 flagged; caution 599, quiet 238.
42
27
 
43
- TODO C165 first cut. Sender trust via allowlist.jsonc still open.
28
+ Also: the Insert menu gains Blockquote and Quoted text (paste) — rmf-tiny
29
+ 0.1.53, see its .commitmsg.
@@ -2309,6 +2309,9 @@ function analyzeServerSpam(input) {
2309
2309
  const scored = (r) => !(r.score <= 0);
2310
2310
  const present = (cls) => rules.some((r) => r.cls === cls && scored(r));
2311
2311
  const kind = dmarc.authFailures.length || present("forgery") ? "forgery" : present("association") ? "association" : present("other") ? "unclassified" : rules.length ? "bulk" : "unclassified";
2312
+ const has = (name) => rules.some((r) => r.name === name);
2313
+ const provedBy = dmarc.pass ? "dmarc" : has("DKIM_VALID_AU") ? "dkim" : has("ALL_TRUSTED") ? "trusted" : "";
2314
+ const fromDomain = (bare(input.fromAddress).split("@")[1] || "").toLowerCase();
2312
2315
  const reasons = rules.filter((r) => r.cls !== "neutral" && scored(r)).sort((a, b) => (b.score || 0) - (a.score || 0)).slice(0, 3).map((r) => {
2313
2316
  const num = Number.isFinite(r.score) ? `${r.score.toFixed(1)} ` : "";
2314
2317
  return r.description ? `${num}${r.name} (${r.description})` : `${num}${r.name}`;
@@ -2319,14 +2322,27 @@ function analyzeServerSpam(input) {
2319
2322
  flagged,
2320
2323
  rules,
2321
2324
  kind,
2322
- proved: dmarc.pass,
2323
- provedDomain: dmarc.domain,
2325
+ proved: !!provedBy,
2326
+ provedBy,
2327
+ provedDomain: dmarc.domain || fromDomain,
2324
2328
  provedPolicy: dmarc.policy,
2325
2329
  bayesHam: rules.some((r) => r.name === "BAYES_00" || r.name === "BAYES_01"),
2326
2330
  authFailures: dmarc.authFailures,
2327
2331
  reasons
2328
2332
  };
2329
2333
  }
2334
+ function provedNote(a) {
2335
+ switch (a.provedBy) {
2336
+ case "dmarc":
2337
+ return `DMARC pass, aligned${a.provedPolicy ? `, policy ${a.provedPolicy}` : ""}.`;
2338
+ case "dkim":
2339
+ return `Valid signature from ${a.provedDomain}, the sending domain itself (DKIM_VALID_AU).`;
2340
+ case "trusted":
2341
+ return "It passed through trusted hosts only (ALL_TRUSTED) \u2014 nothing untrusted handled it.";
2342
+ default:
2343
+ return "";
2344
+ }
2345
+ }
2330
2346
  function serverSpamVerdict(input) {
2331
2347
  const a = analyzeServerSpam(input);
2332
2348
  if (!a || !a.flagged)
@@ -2345,8 +2361,8 @@ function serverSpamVerdict(input) {
2345
2361
  return {
2346
2362
  id: "server-spam-verdict",
2347
2363
  severity: "info",
2348
- text: `Your mail server scored this as spam, but ${a.provedDomain} proved it sent this.`,
2349
- detail: `DMARC pass, aligned${a.provedPolicy ? `, policy ${a.provedPolicy}` : ""}. ${numbers}${why}${bayes}`
2364
+ text: a.provedBy === "trusted" ? "Your mail server scored this as spam, but it reached you without leaving your own systems." : `Your mail server scored this as spam, but ${a.provedDomain} proved it sent this.`,
2365
+ detail: `${provedNote(a)} ${numbers}${why}${bayes}`
2350
2366
  };
2351
2367
  if (a.kind === "bulk")
2352
2368
  return {
@@ -2355,7 +2371,7 @@ function serverSpamVerdict(input) {
2355
2371
  text: "Your mail server rates this bulk mail \u2014 nothing that scored tests who sent it.",
2356
2372
  detail: `${numbers}${why}${bayes}`
2357
2373
  };
2358
- const proof = a.proved ? `${a.provedDomain} passed DMARC, so the From line is genuine. ` : "";
2374
+ const proof = a.proved ? `${provedNote(a)} ` : "";
2359
2375
  return {
2360
2376
  id: "server-spam-verdict",
2361
2377
  severity: "caution",