@bobfrankston/rmfmail 1.2.320 → 1.2.322

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.
Files changed (57) hide show
  1. package/.commitmsg +25 -12
  2. package/.llm/trusted-lists.md +3 -0
  3. package/client/android-bootstrap.bundle.js +35 -9
  4. package/client/android-bootstrap.bundle.js.map +2 -2
  5. package/client/app.bundle.js +27 -1
  6. package/client/app.bundle.js.map +2 -2
  7. package/client/components/message-viewer.js +33 -0
  8. package/client/components/message-viewer.js.map +1 -1
  9. package/client/components/message-viewer.ts +32 -1
  10. package/client/compose/compose.bundle.js +3 -2
  11. package/client/compose/compose.bundle.js.map +2 -2
  12. package/client/compose/compose.js +8 -1
  13. package/client/compose/compose.js.map +1 -1
  14. package/client/compose/compose.ts +8 -1
  15. package/client/lib/api-client.js +1 -1
  16. package/client/lib/api-client.js.map +1 -1
  17. package/client/lib/api-client.ts +3 -3
  18. package/docs/allowlist.md +15 -1
  19. package/npmchanges.md +30 -0
  20. package/package.json +3 -3
  21. package/packages/mailx-imap/package-lock.json +2 -2
  22. package/packages/mailx-imap/package.json +1 -1
  23. package/packages/mailx-service/index.d.ts +1 -0
  24. package/packages/mailx-service/index.d.ts.map +1 -1
  25. package/packages/mailx-service/index.js +1 -0
  26. package/packages/mailx-service/index.js.map +1 -1
  27. package/packages/mailx-service/index.ts +2 -1
  28. package/packages/mailx-service/package.json +1 -1
  29. package/packages/mailx-settings/docs/allowlist.md +15 -1
  30. package/packages/mailx-settings/index.d.ts +1 -0
  31. package/packages/mailx-settings/index.d.ts.map +1 -1
  32. package/packages/mailx-settings/index.js +4 -0
  33. package/packages/mailx-settings/index.js.map +1 -1
  34. package/packages/mailx-settings/index.ts +4 -0
  35. package/packages/mailx-settings/package.json +1 -1
  36. package/packages/mailx-store/package.json +1 -1
  37. package/packages/mailx-store/store.d.ts.map +1 -1
  38. package/packages/mailx-store/store.js +2 -0
  39. package/packages/mailx-store/store.js.map +1 -1
  40. package/packages/mailx-store/store.ts +2 -0
  41. package/packages/mailx-store-web/package.json +1 -1
  42. package/packages/mailx-store-web/web-service.d.ts +1 -0
  43. package/packages/mailx-store-web/web-service.d.ts.map +1 -1
  44. package/packages/mailx-store-web/web-service.js +1 -0
  45. package/packages/mailx-store-web/web-service.js.map +1 -1
  46. package/packages/mailx-store-web/web-service.ts +2 -1
  47. package/packages/mailx-store-web/web-settings.d.ts +1 -0
  48. package/packages/mailx-store-web/web-settings.d.ts.map +1 -1
  49. package/packages/mailx-store-web/web-settings.js +1 -0
  50. package/packages/mailx-store-web/web-settings.js.map +1 -1
  51. package/packages/mailx-store-web/web-settings.ts +1 -0
  52. package/packages/mailx-types/package.json +1 -1
  53. package/packages/mailx-types/trust.d.ts +18 -0
  54. package/packages/mailx-types/trust.d.ts.map +1 -1
  55. package/packages/mailx-types/trust.js +42 -4
  56. package/packages/mailx-types/trust.js.map +1 -1
  57. package/packages/mailx-types/trust.ts +60 -4
package/.commitmsg CHANGED
@@ -1,14 +1,27 @@
1
- Compose (Quill): typing after a pasted or edited link is plain text
1
+ Trust a sender via the relay that signs for it ("groundnews.email via amazonses.com")
2
2
 
3
- Bob 2026-09-10: "you can risk the change for Quill since it is a known
4
- bug and I can test it" the Quill counterpart of v1.2.319's TinyMCE fix.
3
+ Bob 2026-09-10, Ground News: "another valid site. How do I approve it?"
4
+ The banner read "This claims to be a sender you trust, but nothing proves
5
+ it is" — groundnews.email is on his list, but Ground News sends through
6
+ Amazon SES, the message is signed only as amazonses.com, and the domain
7
+ publishes no DMARC record, so the sender can never be proved and the list
8
+ was ignored, correctly, with no way forward. Trusting amazonses.com as an
9
+ intermediary was not offered (no Sender: header) and would be too broad.
5
10
 
6
- Quill carries the format of the character before the caret into what is
7
- typed next, so every place that inserted a linked run and then set the
8
- caret at its end (the five paste paths single-anchor clipboard, URL in
9
- an HTML clipboard, plain URL with and without a selection and both
10
- branches of the Edit-link dialog) left the next keystrokes extending the
11
- link. One hoisted placeCaretAfterLink(index) in createQuillEditor now
12
- does setSelection + format("link", false) for all seven sites; format()
13
- on a collapsed selection sets the cursor format, which is how the
14
- toolbar's own toggle-then-type works. Untested on Quill — Bob tests.
11
+ New allowlist.jsonc `trustedSendersVia`: "‹sender domain› via ‹relay
12
+ domain›" pairs. An entry counts only when the From domain matches AND the
13
+ named relay's DKIM signature verified at the receiving servera relay
14
+ like SES lets a customer send only From identities it has verified, so
15
+ the pair is evidence a forger's own relay account cannot supply, and a
16
+ message without the relay's signature matches nothing. Never silences
17
+ forgery evidence. The trusted-but-unproved caution now names the relay
18
+ that signed and the action; the banner offers "Trust ‹sender› via
19
+ ‹relay›". trustSenderOrDomain type "via", TRUST_LIST_KEYS carries the
20
+ key for desktop and Android; getAllowlist returns it.
21
+
22
+ Tests: Ground News headers; pair silences only with that relay's
23
+ signature (not unsigned, not another relay, not another From through the
24
+ same relay); a phishing listing stays red. Verified on the live message:
25
+ caution with the offer before, silent after. mailx-types 0.1.97,
26
+ mailx-settings 0.1.89, mailx-store 0.1.124, mailx-service 0.1.38,
27
+ mailx-store-web 0.1.117.
@@ -65,3 +65,6 @@ tests/trust.test.ts, .commitmsg, TODO.md C165, root version 1.2.311. Then rebuil
65
65
  - v1.2.318: New event Calendar picker (writable calendars via accessRole from listCalendars). Store-sync drain now passes calendarId to create/update/delete (was always "primary" — shared-calendar edits 404'd forever). Google: create/subscribe/share calendars on calendar.google.com; rmfmail lists the ones selected there.
66
66
  - v1.2.319: TinyMCE smart-paste left the caret INSIDE the pasted <a>; stepCaretOutOfPastedLink in client/compose/editor.ts moves it after the anchor one tick after paste. Quill path has the same shape (insertText link + setSelection) — untouched, Bob uses TinyMCE.
67
67
  - v1.2.320: Quill counterpart — placeCaretAfterLink(index) = setSelection + format("link", false) at 5 paste sites + 2 Edit-link dialog sites. Untested; Bob will switch editor to test.
68
+ - v1.2.321: signature block uses a 3cm <hr> instead of '-- ' (compose.ts sigBlock).
69
+ - Bob confirmed 2026-09-10: 'quill worked' (v1.2.320 placeCaretAfterLink).
70
+ - v1.2.322: trustedSendersVia ('sender via relay' pairs) — Ground News via amazonses.com. Banner offers 'Trust ‹sender› via ‹relay›' on the trusted-but-unproved caution; counts only when the relay DKIM-signed. 45/45 trust tests.
@@ -2297,6 +2297,23 @@ function provedIntermediary(input) {
2297
2297
  return entry;
2298
2298
  return "";
2299
2299
  }
2300
+ function parseSenderVia(entry) {
2301
+ const m = (entry || "").trim().toLowerCase().match(/^(\S+)\s+via\s+(\S+)$/);
2302
+ return m ? { domain: m[1], signer: m[2] } : null;
2303
+ }
2304
+ function provedSenderVia(input) {
2305
+ const entries = (input.trustedSendersVia || []).map(parseSenderVia).filter(Boolean);
2306
+ if (!entries.length)
2307
+ return "";
2308
+ const fromDomain = (bare(input.fromAddress).split("@")[1] || "").toLowerCase();
2309
+ if (!fromDomain)
2310
+ return "";
2311
+ const signers = dkimSigners(input);
2312
+ for (const e of entries)
2313
+ if (sameOrg(fromDomain, e.domain) && signers.some((s) => sameOrg(s, e.signer)))
2314
+ return `${e.domain} via ${e.signer}`;
2315
+ return "";
2316
+ }
2300
2317
  function listProof(input) {
2301
2318
  const none = { id: "", proved: false, host: "" };
2302
2319
  const raw = header(input.headerLines, "list-id");
@@ -2420,7 +2437,8 @@ function analyzeServerSpam(input) {
2420
2437
  reasons,
2421
2438
  intermediary: provedIntermediary(input),
2422
2439
  signers: dkimSigners(input),
2423
- list
2440
+ list,
2441
+ senderVia: provedSenderVia(input)
2424
2442
  };
2425
2443
  }
2426
2444
  function provedNote(a) {
@@ -2439,7 +2457,7 @@ function serverSpamVerdict(input) {
2439
2457
  const a = analyzeServerSpam(input);
2440
2458
  if (!a || !a.flagged)
2441
2459
  return null;
2442
- const trustedAndProved = input.senderTrusted && a.proved || !!a.intermediary || a.list.trusted;
2460
+ const trustedAndProved = input.senderTrusted && a.proved || !!a.intermediary || a.list.trusted || !!a.senderVia;
2443
2461
  if (trustedAndProved && a.kind !== "forgery" && !a.authFailures.length)
2444
2462
  return null;
2445
2463
  const senderDomain = (bare(header(input.headerLines, "sender")).split("@")[1] || "").toLowerCase();
@@ -2473,14 +2491,19 @@ function serverSpamVerdict(input) {
2473
2491
  detail: `${numbers}${why}${bayes}`
2474
2492
  };
2475
2493
  const proof = a.proved ? `${provedNote(a)} ` : "";
2476
- if (input.senderTrusted && !a.proved)
2494
+ if (input.senderTrusted && !a.proved) {
2495
+ const fromDomain = (bare(input.fromAddress).split("@")[1] || "").toLowerCase();
2496
+ const relaySigner = a.signers.find((s) => !sameOrg(s, fromDomain)) || "";
2497
+ const senderViaNote = relaySigner ? `. ${relaySigner} signed this message on the sender's behalf; "Trust ${fromDomain} via ${relaySigner}" stops this note for mail from ${fromDomain} that ${relaySigner} signs.` : "";
2477
2498
  return {
2478
2499
  id: "server-spam-verdict",
2479
2500
  severity: "caution",
2480
2501
  text: "This claims to be a sender you trust, but nothing proves it is.",
2481
- detail: `No DMARC pass, no valid signature from the sending domain, and it passed through hosts your server does not trust. ${numbers}${why}${listNote}`,
2482
- ...viaList
2502
+ detail: `No DMARC pass, no valid signature from the sending domain, and it passed through hosts your server does not trust. ${numbers}${why}${listNote}${senderViaNote}`,
2503
+ ...viaList,
2504
+ ...relaySigner ? { senderVia: `${fromDomain} via ${relaySigner}` } : {}
2483
2505
  };
2506
+ }
2484
2507
  return {
2485
2508
  id: "server-spam-verdict",
2486
2509
  severity: "caution",
@@ -2664,7 +2687,7 @@ function spamScoreOf(input) {
2664
2687
  proved: a.proved,
2665
2688
  provedBy: a.provedBy,
2666
2689
  bulkOnly: a.bulkOnly,
2667
- trusted: (!!input.senderTrusted && a.proved || !!a.intermediary || a.list.trusted) && a.kind !== "forgery" && !a.authFailures.length,
2690
+ trusted: (!!input.senderTrusted && a.proved || !!a.intermediary || a.list.trusted || !!a.senderVia) && a.kind !== "forgery" && !a.authFailures.length,
2668
2691
  reasons: a.reasons,
2669
2692
  listId: a.list.id,
2670
2693
  listProved: a.list.proved,
@@ -2679,7 +2702,8 @@ var init_trust = __esm({
2679
2702
  sender: "trustedSenders",
2680
2703
  domain: "trustedDomains",
2681
2704
  intermediary: "trustedIntermediaries",
2682
- list: "trustedLists"
2705
+ list: "trustedLists",
2706
+ via: "trustedSendersVia"
2683
2707
  };
2684
2708
  FORGERY_RULES = [
2685
2709
  /^SPF_(HELO_)?(FAIL|SOFTFAIL)$/,
@@ -4291,7 +4315,8 @@ var init_web_settings = __esm({
4291
4315
  trustedSenders: [],
4292
4316
  trustedDomains: [],
4293
4317
  // Mailing lists by List-Id — mirrors mailx-settings DEFAULT_ALLOWLIST (2026-09-09).
4294
- trustedLists: []
4318
+ trustedLists: [],
4319
+ trustedSendersVia: []
4295
4320
  };
4296
4321
  PREV_KEEP = 5;
4297
4322
  SHARED_REFRESH_MS = 5 * 6e4;
@@ -7256,7 +7281,8 @@ var WebMailxService = class _WebMailxService {
7256
7281
  trustedSenders: list.trustedSenders || [],
7257
7282
  trustedDomains: list.trustedDomains || [],
7258
7283
  trustedIntermediaries: list.trustedIntermediaries || [],
7259
- trustedLists: list.trustedLists || []
7284
+ trustedLists: list.trustedLists || [],
7285
+ trustedSendersVia: list.trustedSendersVia || []
7260
7286
  };
7261
7287
  }
7262
7288
  async allowRemoteContent(type, value) {