@bobfrankston/rmfmail 1.2.306 → 1.2.308
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 +24 -12
- package/client/android-bootstrap.bundle.js +45 -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 +39 -0
- package/package.json +8 -5
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +22 -15
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +23 -19
- 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/db.d.ts +23 -0
- package/packages/mailx-store/db.d.ts.map +1 -1
- package/packages/mailx-store/db.js +47 -0
- package/packages/mailx-store/db.js.map +1 -1
- package/packages/mailx-store/db.ts +52 -0
- 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 +63 -2
- package/packages/mailx-types/trust.js.map +1 -1
- package/packages/mailx-types/trust.ts +80 -2
package/.commitmsg
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
Trusted intermediaries: services that relay mail on other people's behalf
|
|
2
2
|
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
"How do I mark zoom and sites like that as trusted intermediaries?" (Bob
|
|
4
|
+
2026-09-08, on a Google Calendar invitation From the organizer, signed
|
|
5
|
+
d=google.com, every link wrapped as google.com/url?q=us02web.zoom.us). Two
|
|
6
|
+
cautions fired — the server's spam verdict (GOOG_REDIR/KAM_GOOGLE_REDIR) and
|
|
7
|
+
"a link hides where it goes" — and nothing in the app could answer either:
|
|
8
|
+
the sender is not DMARC-proved, so "stop warning about this sender" was not
|
|
9
|
+
offered, and the redirector rule's escape covers only the proved From.
|
|
10
|
+
|
|
11
|
+
New allowlist.jsonc field `trustedIntermediaries` (e.g. google.com, zoom.us).
|
|
12
|
+
An entry counts only on a message the service PROVABLY handled — a verified
|
|
13
|
+
DKIM signature for its domain in the topmost Authentication-Results, or a
|
|
14
|
+
DMARC pass when it is the From. Then its own redirector is routing, not
|
|
15
|
+
laundering, and the bulk/association spam note is quieted exactly as a
|
|
16
|
+
proved trusted sender quiets it. A message that merely links through
|
|
17
|
+
google.com/url proves nothing and stays flagged; forgery evidence is never
|
|
18
|
+
silenced. Findings now carry `via` (the signer behind the redirector, or the
|
|
19
|
+
signed Sender: domain) and the viewer offers "Trust ‹domain› as an
|
|
20
|
+
intermediary" under the banner; trustSenderOrDomain takes type
|
|
21
|
+
"intermediary" (desktop + Android mirror), getAllowlist returns the list.
|
|
22
|
+
docs/allowlist.md documents trustedSenders/trustedDomains (were missing) and
|
|
23
|
+
the new list. Packages: mailx-types 0.1.83, mailx-store 0.1.115,
|
|
24
|
+
mailx-settings 0.1.77, mailx-service 0.1.34, mailx-store-web 0.1.107,
|
|
25
|
+
client 1.0.46.
|
|
@@ -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,12 @@ 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 } : {};
|
|
2365
2395
|
const numbers = Number.isFinite(a.score) && Number.isFinite(a.threshold) ? `SpamAssassin score ${a.score} of ${a.threshold}` : "flagged by SpamAssassin";
|
|
2366
2396
|
const why = a.reasons.length ? ` \u2014 ${a.reasons.join("; ")}` : "";
|
|
2367
2397
|
const bayes = a.bayesHam ? "; your own trained filter puts it at 0-1% spam (BAYES_00)" : "";
|
|
@@ -2398,7 +2428,8 @@ function serverSpamVerdict(input) {
|
|
|
2398
2428
|
id: "server-spam-verdict",
|
|
2399
2429
|
severity: "caution",
|
|
2400
2430
|
text: "Your mail server classified this as spam before delivering it.",
|
|
2401
|
-
detail: `${proof}${numbers}${why}${bayes}
|
|
2431
|
+
detail: `${proof}${numbers}${why}${bayes}`,
|
|
2432
|
+
...via
|
|
2402
2433
|
};
|
|
2403
2434
|
}
|
|
2404
2435
|
function selfSpoof(input) {
|
|
@@ -2475,6 +2506,8 @@ function provedSenderDomain(input) {
|
|
|
2475
2506
|
function redirectorLink(input) {
|
|
2476
2507
|
const bodyHtml = input.bodyHtml || "";
|
|
2477
2508
|
const senderDomain = provedSenderDomain(input);
|
|
2509
|
+
const intermediary = provedIntermediary(input);
|
|
2510
|
+
const signers = dkimSigners(input);
|
|
2478
2511
|
for (const m of bodyHtml.matchAll(/href\s*=\s*["']([^"']+)["']/gi)) {
|
|
2479
2512
|
let url;
|
|
2480
2513
|
try {
|
|
@@ -2486,6 +2519,8 @@ function redirectorLink(input) {
|
|
|
2486
2519
|
continue;
|
|
2487
2520
|
if (senderDomain && sameOrg(url.hostname, senderDomain))
|
|
2488
2521
|
continue;
|
|
2522
|
+
if (intermediary && sameOrg(url.hostname, intermediary))
|
|
2523
|
+
continue;
|
|
2489
2524
|
for (const [, value] of url.searchParams) {
|
|
2490
2525
|
let target;
|
|
2491
2526
|
try {
|
|
@@ -2497,11 +2532,13 @@ function redirectorLink(input) {
|
|
|
2497
2532
|
continue;
|
|
2498
2533
|
if (sameOrg(target.hostname, url.hostname))
|
|
2499
2534
|
continue;
|
|
2535
|
+
const signer = signers.find((s) => sameOrg(s, url.hostname));
|
|
2500
2536
|
return {
|
|
2501
2537
|
id: "redirector-link",
|
|
2502
2538
|
severity: "caution",
|
|
2503
2539
|
text: `A link hides where it goes: it passes through ${url.hostname} and ends at ${target.hostname}.`,
|
|
2504
|
-
detail: `${url.hostname} -> ${target.hostname}
|
|
2540
|
+
detail: `${url.hostname} -> ${target.hostname}`,
|
|
2541
|
+
...signer ? { via: signer } : {}
|
|
2505
2542
|
};
|
|
2506
2543
|
}
|
|
2507
2544
|
}
|
|
@@ -2564,7 +2601,7 @@ function spamScoreOf(input) {
|
|
|
2564
2601
|
proved: a.proved,
|
|
2565
2602
|
provedBy: a.provedBy,
|
|
2566
2603
|
bulkOnly: a.bulkOnly,
|
|
2567
|
-
trusted: !!input.senderTrusted && a.proved && a.kind !== "forgery" && !a.authFailures.length,
|
|
2604
|
+
trusted: (!!input.senderTrusted && a.proved || !!a.intermediary) && a.kind !== "forgery" && !a.authFailures.length,
|
|
2568
2605
|
reasons: a.reasons
|
|
2569
2606
|
};
|
|
2570
2607
|
}
|
|
@@ -7099,7 +7136,7 @@ var WebMailxService = class _WebMailxService {
|
|
|
7099
7136
|
return { trusted: false };
|
|
7100
7137
|
let trusted = false;
|
|
7101
7138
|
await updateAllowlist((list) => {
|
|
7102
|
-
const key = type === "sender" ? "trustedSenders" : "trustedDomains";
|
|
7139
|
+
const key = type === "sender" ? "trustedSenders" : type === "intermediary" ? "trustedIntermediaries" : "trustedDomains";
|
|
7103
7140
|
const arr = Array.isArray(list[key]) ? list[key] : [];
|
|
7104
7141
|
const idx = arr.findIndex((x) => (x || "").toLowerCase() === v);
|
|
7105
7142
|
if (idx >= 0) {
|
|
@@ -7132,7 +7169,8 @@ var WebMailxService = class _WebMailxService {
|
|
|
7132
7169
|
flaggedSenders: list.flaggedSenders || [],
|
|
7133
7170
|
flaggedDomains: list.flaggedDomains || [],
|
|
7134
7171
|
trustedSenders: list.trustedSenders || [],
|
|
7135
|
-
trustedDomains: list.trustedDomains || []
|
|
7172
|
+
trustedDomains: list.trustedDomains || [],
|
|
7173
|
+
trustedIntermediaries: list.trustedIntermediaries || []
|
|
7136
7174
|
};
|
|
7137
7175
|
}
|
|
7138
7176
|
async allowRemoteContent(type, value) {
|