@bobfrankston/rmfmail 1.2.321 → 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.
- package/.commitmsg +26 -9
- package/.llm/trusted-lists.md +2 -0
- package/client/android-bootstrap.bundle.js +35 -9
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +27 -1
- package/client/app.bundle.js.map +2 -2
- package/client/components/message-viewer.js +33 -0
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +32 -1
- package/client/compose/compose.bundle.js +1 -1
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/lib/api-client.js +1 -1
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +3 -3
- package/docs/allowlist.md +15 -1
- package/npmchanges.md +13 -0
- package/package.json +3 -3
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts +1 -0
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +1 -0
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +2 -1
- package/packages/mailx-service/package.json +1 -1
- package/packages/mailx-settings/docs/allowlist.md +15 -1
- package/packages/mailx-settings/index.d.ts +1 -0
- package/packages/mailx-settings/index.d.ts.map +1 -1
- package/packages/mailx-settings/index.js +4 -0
- package/packages/mailx-settings/index.js.map +1 -1
- package/packages/mailx-settings/index.ts +4 -0
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store/store.d.ts.map +1 -1
- package/packages/mailx-store/store.js +2 -0
- package/packages/mailx-store/store.js.map +1 -1
- package/packages/mailx-store/store.ts +2 -0
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-store-web/web-service.d.ts +1 -0
- package/packages/mailx-store-web/web-service.d.ts.map +1 -1
- package/packages/mailx-store-web/web-service.js +1 -0
- package/packages/mailx-store-web/web-service.js.map +1 -1
- package/packages/mailx-store-web/web-service.ts +2 -1
- package/packages/mailx-store-web/web-settings.d.ts +1 -0
- package/packages/mailx-store-web/web-settings.d.ts.map +1 -1
- package/packages/mailx-store-web/web-settings.js +1 -0
- package/packages/mailx-store-web/web-settings.js.map +1 -1
- package/packages/mailx-store-web/web-settings.ts +1 -0
- package/packages/mailx-types/package.json +1 -1
- package/packages/mailx-types/trust.d.ts +18 -0
- package/packages/mailx-types/trust.d.ts.map +1 -1
- package/packages/mailx-types/trust.js +42 -4
- package/packages/mailx-types/trust.js.map +1 -1
- package/packages/mailx-types/trust.ts +60 -4
package/.commitmsg
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
Trust a sender via the relay that signs for it ("groundnews.email via amazonses.com")
|
|
2
2
|
|
|
3
|
-
Bob 2026-09-10: "
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
10
|
+
|
|
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 server — a 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.
|
package/.llm/trusted-lists.md
CHANGED
|
@@ -66,3 +66,5 @@ tests/trust.test.ts, .commitmsg, TODO.md C165, root version 1.2.311. Then rebuil
|
|
|
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
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) {
|