@bobfrankston/rmfmail 1.1.233 → 1.1.235
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/client/app.bundle.js +9 -1
- package/client/app.bundle.js.map +2 -2
- package/client/components/message-viewer.js +9 -1
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +9 -1
- package/package.json +1 -1
- package/packages/mailx-store/store.d.ts.map +1 -1
- package/packages/mailx-store/store.js +31 -10
- package/packages/mailx-store/store.js.map +1 -1
- package/packages/mailx-store/store.ts +32 -11
|
@@ -255,17 +255,22 @@ export class Store {
|
|
|
255
255
|
const allowList = this.getCachedAllowlist() as any;
|
|
256
256
|
const senderAddr = (envelope.from?.address || "").toLowerCase();
|
|
257
257
|
const senderDomain = senderAddr.split("@")[1] || "";
|
|
258
|
-
|
|
258
|
+
// Recipient allowlist matches against every recipient-ish address, not
|
|
259
|
+
// just To: a mailing list often carries other subscribers in To/Cc and
|
|
260
|
+
// delivers to the user's allowlisted alias only via Delivered-To (added
|
|
261
|
+
// post-parse below).
|
|
262
|
+
const recipients = (allowList.recipients || []).map((r: string) => (r || "").toLowerCase());
|
|
263
|
+
const rcptAddrs = [...(envelope.to || []), ...(envelope.cc || [])]
|
|
264
|
+
.map((a: any) => (a.address || "").toLowerCase());
|
|
259
265
|
|
|
260
266
|
// Allowlist auto-allow: trusted sender / domain / recipient skips
|
|
261
267
|
// sanitization. Same rule as the legacy service implementation.
|
|
262
268
|
if (!allowRemote) {
|
|
263
269
|
const senders = (allowList.senders || []).map((s: string) => (s || "").toLowerCase());
|
|
264
270
|
const domains = (allowList.domains || []).map((d: string) => (d || "").toLowerCase());
|
|
265
|
-
const recipients = (allowList.recipients || []).map((r: string) => (r || "").toLowerCase());
|
|
266
271
|
if (senders.includes(senderAddr) ||
|
|
267
272
|
domains.includes(senderDomain) ||
|
|
268
|
-
|
|
273
|
+
rcptAddrs.some((a: string) => recipients.includes(a))) {
|
|
269
274
|
allowRemote = true;
|
|
270
275
|
}
|
|
271
276
|
}
|
|
@@ -307,8 +312,12 @@ export class Store {
|
|
|
307
312
|
const cached = this.parsedLruGet(cacheKey);
|
|
308
313
|
if (cached) {
|
|
309
314
|
// Allowlist state can change between views even with the same
|
|
310
|
-
// body cached; recompute the volatile fields and overlay.
|
|
311
|
-
|
|
315
|
+
// body cached; recompute the volatile fields and overlay. OR with
|
|
316
|
+
// the cached flag: if the body was already parsed-and-allowed (e.g.
|
|
317
|
+
// via a Delivered-To recipient match, which the early envelope-only
|
|
318
|
+
// check can't see), keep it allowed — the cached HTML is unsanitized
|
|
319
|
+
// and the flag must agree.
|
|
320
|
+
return { ...cached, remoteAllowed: allowRemote || cached.remoteAllowed, isFlagged };
|
|
312
321
|
}
|
|
313
322
|
|
|
314
323
|
// Synchronous parse. The .eml is on disk; read + parse it inline
|
|
@@ -408,12 +417,6 @@ export class Store {
|
|
|
408
417
|
contentId: x.a.contentId || "",
|
|
409
418
|
}));
|
|
410
419
|
|
|
411
|
-
if (bodyHtml && !allowRemote) {
|
|
412
|
-
const result = sanitizeHtml(bodyHtml);
|
|
413
|
-
bodyHtml = result.html;
|
|
414
|
-
hasRemoteContent = result.hasRemoteContent;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
420
|
// Header extraction — Delivered-To, Return-Path, List-Unsubscribe.
|
|
418
421
|
// Each delivery agent PREPENDS its Delivered-To, so the FIRST
|
|
419
422
|
// (topmost) header is the final delivery to the user's actual
|
|
@@ -421,6 +424,8 @@ export class Store {
|
|
|
421
424
|
// routing artifacts. Take [0]. (The old code took the LAST entry —
|
|
422
425
|
// on a forwarded message that grabbed a stale hop, e.g. a malformed
|
|
423
426
|
// `…@elkin.ws@trap-prot` address — Bob 2026-05-21.)
|
|
427
|
+
// This runs BEFORE sanitization because Delivered-To feeds the
|
|
428
|
+
// recipient-allowlist re-check just below.
|
|
424
429
|
let deliveredTo = "";
|
|
425
430
|
const rawDelivered = parsed.headers.get("delivered-to");
|
|
426
431
|
if (rawDelivered) {
|
|
@@ -447,6 +452,22 @@ export class Store {
|
|
|
447
452
|
parseListUnsubscribe(parsed.headers);
|
|
448
453
|
const listUnsubscribe = listUnsubscribeHttp || listUnsubscribeMail;
|
|
449
454
|
|
|
455
|
+
// Recipient-allowlist re-check with Delivered-To in hand. The early
|
|
456
|
+
// pass (envelope only) can't see Delivered-To, so a list message whose
|
|
457
|
+
// To/Cc are other subscribers and whose only allowlisted address is the
|
|
458
|
+
// Delivered-To alias (e.g. shsaa@bob.ma) would still be sanitized.
|
|
459
|
+
// Match the bare address out of "Name <addr>" / "<addr>" / "addr".
|
|
460
|
+
if (!allowRemote && deliveredTo) {
|
|
461
|
+
const dAddr = (deliveredTo.match(/[^\s<>]+@[^\s<>]+/)?.[0] || "").toLowerCase();
|
|
462
|
+
if (dAddr && recipients.includes(dAddr)) allowRemote = true;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (bodyHtml && !allowRemote) {
|
|
466
|
+
const result = sanitizeHtml(bodyHtml);
|
|
467
|
+
bodyHtml = result.html;
|
|
468
|
+
hasRemoteContent = result.hasRemoteContent;
|
|
469
|
+
}
|
|
470
|
+
|
|
450
471
|
const result: StoreMessage = {
|
|
451
472
|
...envelope,
|
|
452
473
|
bodyHtml, bodyText,
|