@bobfrankston/rmfmail 1.2.307 → 1.2.309
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 +10 -19
- package/client/android-bootstrap.bundle.js +47 -7
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +26 -1
- package/client/app.bundle.js.map +2 -2
- package/client/components/message-viewer.js +36 -0
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +35 -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/client/package.json +1 -1
- package/docs/allowlist.md +19 -1
- package/npmchanges.md +51 -0
- package/package.json +8 -5
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts +2 -1
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +4 -1
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +6 -3
- package/packages/mailx-service/package.json +1 -1
- package/packages/mailx-settings/docs/allowlist.md +19 -1
- package/packages/mailx-settings/docs/rules-design.md +43 -0
- 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 +3 -0
- package/packages/mailx-store/store.js.map +1 -1
- package/packages/mailx-store/store.ts +3 -0
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-store-web/web-service.d.ts +2 -1
- package/packages/mailx-store-web/web-service.d.ts.map +1 -1
- package/packages/mailx-store-web/web-service.js +3 -1
- package/packages/mailx-store-web/web-service.js.map +1 -1
- package/packages/mailx-store-web/web-service.ts +5 -3
- package/packages/mailx-types/package.json +1 -1
- package/packages/mailx-types/trust.d.ts +17 -0
- package/packages/mailx-types/trust.d.ts.map +1 -1
- package/packages/mailx-types/trust.js +76 -4
- package/packages/mailx-types/trust.js.map +1 -1
- package/packages/mailx-types/trust.ts +93 -4
package/.commitmsg
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
Trust banner names the intermediary and the action that trusts it
|
|
2
2
|
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
with any mail traffic ever received its card fields either.
|
|
13
|
-
|
|
14
|
-
New MailxDB.upsertGoogleContact(googleId, name, email, card) owns the google
|
|
15
|
-
row: name and card follow Google on every sync, discovered rows are left
|
|
16
|
-
alone, and stray duplicate google rows for one address are collapsed. The
|
|
17
|
-
People sync calls it instead of recordSentAddress + inline SQL (which also
|
|
18
|
-
inflated use_count on every full sync). A one-time full resync per account
|
|
19
|
-
(kv contacts/resync:<account>) refreshes the names already stored stale.
|
|
20
|
-
Packages: mailx-store 0.1.113, mailx-imap 0.1.174.
|
|
3
|
+
"Is there a 'trust this intermediary' operation or do I need to edit it
|
|
4
|
+
manually? If so the banner should tell which and how" (Bob 2026-09-08). The
|
|
5
|
+
button existed since v1.2.308 but the finding text did not mention it. The
|
|
6
|
+
spam-verdict note now ends with: Sent on the author's behalf by google.com,
|
|
7
|
+
which signed it; "Trust google.com as an intermediary" stops this note for
|
|
8
|
+
mail google.com signs. The redirector warning ends the same way, naming the
|
|
9
|
+
signer — or, when nobody signed for the redirector host, says plainly that
|
|
10
|
+
there is no intermediary to trust and only the sender could vouch for the
|
|
11
|
+
link. mailx-types 0.1.85.
|
|
@@ -2250,6 +2250,31 @@ function classifyRule(name, dmarcFailed) {
|
|
|
2250
2250
|
return "bulk";
|
|
2251
2251
|
return "other";
|
|
2252
2252
|
}
|
|
2253
|
+
function dkimSigners(input) {
|
|
2254
|
+
const auth = headerAll(input.headerLines, "authentication-results")[0] || "";
|
|
2255
|
+
const out = [];
|
|
2256
|
+
for (const part of auth.split(";")) {
|
|
2257
|
+
if (!/\bdkim=pass\b/i.test(part))
|
|
2258
|
+
continue;
|
|
2259
|
+
const d = (part.match(/header\.d=([^\s;,]+)/i)?.[1] || "").toLowerCase();
|
|
2260
|
+
if (d && !out.includes(d))
|
|
2261
|
+
out.push(d);
|
|
2262
|
+
}
|
|
2263
|
+
return out;
|
|
2264
|
+
}
|
|
2265
|
+
function provedIntermediary(input) {
|
|
2266
|
+
const list = (input.trustedIntermediaries || []).map((d) => (d || "").trim().toLowerCase()).filter(Boolean);
|
|
2267
|
+
if (!list.length)
|
|
2268
|
+
return "";
|
|
2269
|
+
const proved = dkimSigners(input);
|
|
2270
|
+
const dmarc = dmarcProof(input);
|
|
2271
|
+
if (dmarc.pass && dmarc.domain)
|
|
2272
|
+
proved.push(dmarc.domain);
|
|
2273
|
+
for (const entry of list)
|
|
2274
|
+
if (proved.some((p) => sameOrg(p, entry)))
|
|
2275
|
+
return entry;
|
|
2276
|
+
return "";
|
|
2277
|
+
}
|
|
2253
2278
|
function dmarcProof(input) {
|
|
2254
2279
|
const auth = headerAll(input.headerLines, "authentication-results")[0] || "";
|
|
2255
2280
|
if (!auth)
|
|
@@ -2340,7 +2365,9 @@ function analyzeServerSpam(input) {
|
|
|
2340
2365
|
provedPolicy: dmarc.policy,
|
|
2341
2366
|
bayesHam: rules.some((r) => r.name === "BAYES_00" || r.name === "BAYES_01"),
|
|
2342
2367
|
authFailures: dmarc.authFailures,
|
|
2343
|
-
reasons
|
|
2368
|
+
reasons,
|
|
2369
|
+
intermediary: provedIntermediary(input),
|
|
2370
|
+
signers: dkimSigners(input)
|
|
2344
2371
|
};
|
|
2345
2372
|
}
|
|
2346
2373
|
function provedNote(a) {
|
|
@@ -2359,9 +2386,13 @@ function serverSpamVerdict(input) {
|
|
|
2359
2386
|
const a = analyzeServerSpam(input);
|
|
2360
2387
|
if (!a || !a.flagged)
|
|
2361
2388
|
return null;
|
|
2362
|
-
const trustedAndProved = input.senderTrusted && a.proved;
|
|
2389
|
+
const trustedAndProved = input.senderTrusted && a.proved || !!a.intermediary;
|
|
2363
2390
|
if (trustedAndProved && a.kind !== "forgery" && !a.authFailures.length)
|
|
2364
2391
|
return null;
|
|
2392
|
+
const senderDomain = (bare(header(input.headerLines, "sender")).split("@")[1] || "").toLowerCase();
|
|
2393
|
+
const relay = senderDomain ? a.signers.find((s) => sameOrg(s, senderDomain)) : "";
|
|
2394
|
+
const via = relay && !a.proved ? { via: relay } : {};
|
|
2395
|
+
const relayNote = relay && !a.proved ? `. Sent on the author's behalf by ${relay}, which signed it; "Trust ${relay} as an intermediary" stops this note for mail ${relay} signs.` : "";
|
|
2365
2396
|
const numbers = Number.isFinite(a.score) && Number.isFinite(a.threshold) ? `SpamAssassin score ${a.score} of ${a.threshold}` : "flagged by SpamAssassin";
|
|
2366
2397
|
const why = a.reasons.length ? ` \u2014 ${a.reasons.join("; ")}` : "";
|
|
2367
2398
|
const bayes = a.bayesHam ? "; your own trained filter puts it at 0-1% spam (BAYES_00)" : "";
|
|
@@ -2398,7 +2429,8 @@ function serverSpamVerdict(input) {
|
|
|
2398
2429
|
id: "server-spam-verdict",
|
|
2399
2430
|
severity: "caution",
|
|
2400
2431
|
text: "Your mail server classified this as spam before delivering it.",
|
|
2401
|
-
detail: `${proof}${numbers}${why}${bayes}
|
|
2432
|
+
detail: `${proof}${numbers}${why}${bayes}${relayNote}`,
|
|
2433
|
+
...via
|
|
2402
2434
|
};
|
|
2403
2435
|
}
|
|
2404
2436
|
function selfSpoof(input) {
|
|
@@ -2475,6 +2507,8 @@ function provedSenderDomain(input) {
|
|
|
2475
2507
|
function redirectorLink(input) {
|
|
2476
2508
|
const bodyHtml = input.bodyHtml || "";
|
|
2477
2509
|
const senderDomain = provedSenderDomain(input);
|
|
2510
|
+
const intermediary = provedIntermediary(input);
|
|
2511
|
+
const signers = dkimSigners(input);
|
|
2478
2512
|
for (const m of bodyHtml.matchAll(/href\s*=\s*["']([^"']+)["']/gi)) {
|
|
2479
2513
|
let url;
|
|
2480
2514
|
try {
|
|
@@ -2486,6 +2520,8 @@ function redirectorLink(input) {
|
|
|
2486
2520
|
continue;
|
|
2487
2521
|
if (senderDomain && sameOrg(url.hostname, senderDomain))
|
|
2488
2522
|
continue;
|
|
2523
|
+
if (intermediary && sameOrg(url.hostname, intermediary))
|
|
2524
|
+
continue;
|
|
2489
2525
|
for (const [, value] of url.searchParams) {
|
|
2490
2526
|
let target;
|
|
2491
2527
|
try {
|
|
@@ -2497,11 +2533,14 @@ function redirectorLink(input) {
|
|
|
2497
2533
|
continue;
|
|
2498
2534
|
if (sameOrg(target.hostname, url.hostname))
|
|
2499
2535
|
continue;
|
|
2536
|
+
const signer = signers.find((s) => sameOrg(s, url.hostname));
|
|
2537
|
+
const how = signer ? ` ${signer} signed this message; "Trust ${signer} as an intermediary" stops this warning for links through ${signer} in mail it signs.` : ` Nobody signed this message for ${url.hostname}, so there is no intermediary to trust; only the sender could vouch for the link.`;
|
|
2500
2538
|
return {
|
|
2501
2539
|
id: "redirector-link",
|
|
2502
2540
|
severity: "caution",
|
|
2503
2541
|
text: `A link hides where it goes: it passes through ${url.hostname} and ends at ${target.hostname}.`,
|
|
2504
|
-
detail: `${url.hostname} -> ${target.hostname}
|
|
2542
|
+
detail: `${url.hostname} -> ${target.hostname}.${how}`,
|
|
2543
|
+
...signer ? { via: signer } : {}
|
|
2505
2544
|
};
|
|
2506
2545
|
}
|
|
2507
2546
|
}
|
|
@@ -2564,7 +2603,7 @@ function spamScoreOf(input) {
|
|
|
2564
2603
|
proved: a.proved,
|
|
2565
2604
|
provedBy: a.provedBy,
|
|
2566
2605
|
bulkOnly: a.bulkOnly,
|
|
2567
|
-
trusted: !!input.senderTrusted && a.proved && a.kind !== "forgery" && !a.authFailures.length,
|
|
2606
|
+
trusted: (!!input.senderTrusted && a.proved || !!a.intermediary) && a.kind !== "forgery" && !a.authFailures.length,
|
|
2568
2607
|
reasons: a.reasons
|
|
2569
2608
|
};
|
|
2570
2609
|
}
|
|
@@ -7099,7 +7138,7 @@ var WebMailxService = class _WebMailxService {
|
|
|
7099
7138
|
return { trusted: false };
|
|
7100
7139
|
let trusted = false;
|
|
7101
7140
|
await updateAllowlist((list) => {
|
|
7102
|
-
const key = type === "sender" ? "trustedSenders" : "trustedDomains";
|
|
7141
|
+
const key = type === "sender" ? "trustedSenders" : type === "intermediary" ? "trustedIntermediaries" : "trustedDomains";
|
|
7103
7142
|
const arr = Array.isArray(list[key]) ? list[key] : [];
|
|
7104
7143
|
const idx = arr.findIndex((x) => (x || "").toLowerCase() === v);
|
|
7105
7144
|
if (idx >= 0) {
|
|
@@ -7132,7 +7171,8 @@ var WebMailxService = class _WebMailxService {
|
|
|
7132
7171
|
flaggedSenders: list.flaggedSenders || [],
|
|
7133
7172
|
flaggedDomains: list.flaggedDomains || [],
|
|
7134
7173
|
trustedSenders: list.trustedSenders || [],
|
|
7135
|
-
trustedDomains: list.trustedDomains || []
|
|
7174
|
+
trustedDomains: list.trustedDomains || [],
|
|
7175
|
+
trustedIntermediaries: list.trustedIntermediaries || []
|
|
7136
7176
|
};
|
|
7137
7177
|
}
|
|
7138
7178
|
async allowRemoteContent(type, value) {
|