@bobfrankston/rmfmail 1.2.290 → 1.2.292

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 (53) hide show
  1. package/.commitmsg +24 -34
  2. package/client/android-bootstrap.bundle.js +42 -1
  3. package/client/android-bootstrap.bundle.js.map +2 -2
  4. package/client/app.bundle.js +36 -2
  5. package/client/app.bundle.js.map +2 -2
  6. package/client/components/message-viewer.js +53 -2
  7. package/client/components/message-viewer.js.map +1 -1
  8. package/client/components/message-viewer.ts +53 -3
  9. package/client/compose/compose.bundle.js +5 -1
  10. package/client/compose/compose.bundle.js.map +2 -2
  11. package/client/lib/api-client.js +7 -1
  12. package/client/lib/api-client.js.map +1 -1
  13. package/client/lib/api-client.ts +9 -2
  14. package/client/lib/mailxapi.js +3 -0
  15. package/client/styles/components.css +25 -0
  16. package/npmchanges.md +76 -0
  17. package/package.json +5 -5
  18. package/packages/mailx-imap/package-lock.json +2 -2
  19. package/packages/mailx-imap/package.json +1 -1
  20. package/packages/mailx-service/index.d.ts +20 -0
  21. package/packages/mailx-service/index.d.ts.map +1 -1
  22. package/packages/mailx-service/index.js +37 -0
  23. package/packages/mailx-service/index.js.map +1 -1
  24. package/packages/mailx-service/index.ts +37 -1
  25. package/packages/mailx-service/jsonrpc.js +2 -0
  26. package/packages/mailx-service/jsonrpc.js.map +1 -1
  27. package/packages/mailx-service/jsonrpc.ts +2 -0
  28. package/packages/mailx-service/package.json +1 -1
  29. package/packages/mailx-settings/package.json +1 -1
  30. package/packages/mailx-store/package.json +1 -1
  31. package/packages/mailx-store/store.d.ts.map +1 -1
  32. package/packages/mailx-store/store.js +30 -5
  33. package/packages/mailx-store/store.js.map +1 -1
  34. package/packages/mailx-store/store.ts +30 -5
  35. package/packages/mailx-store-web/android-bootstrap.js +3 -0
  36. package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
  37. package/packages/mailx-store-web/android-bootstrap.ts +3 -0
  38. package/packages/mailx-store-web/package.json +1 -1
  39. package/packages/mailx-store-web/web-jsonrpc.js +2 -0
  40. package/packages/mailx-store-web/web-jsonrpc.js.map +1 -1
  41. package/packages/mailx-store-web/web-jsonrpc.ts +2 -0
  42. package/packages/mailx-store-web/web-service.d.ts +9 -0
  43. package/packages/mailx-store-web/web-service.d.ts.map +1 -1
  44. package/packages/mailx-store-web/web-service.js +28 -0
  45. package/packages/mailx-store-web/web-service.js.map +1 -1
  46. package/packages/mailx-store-web/web-service.ts +23 -1
  47. package/packages/mailx-types/package.json +1 -1
  48. package/packages/mailx-types/trust.d.ts +11 -0
  49. package/packages/mailx-types/trust.d.ts.map +1 -1
  50. package/packages/mailx-types/trust.js +18 -0
  51. package/packages/mailx-types/trust.js.map +1 -1
  52. package/packages/mailx-types/trust.ts +29 -0
  53. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-27100 → node_modules.npmglobalize-stash-50524}/.package-lock.json +0 -0
package/.commitmsg CHANGED
@@ -1,39 +1,29 @@
1
- Spam banner: an unknown rule must be able to condemn on its own
1
+ Trusting a sender must not turn on their tracking pixels
2
2
 
3
- Bob 2026-08-28: "we have a problem since, of course, things like conference
4
- announcements use bulk mailers." An IEEE/ICECER call for papers, DMARC-proved,
5
- BAYES_00, scored exactly 4.5 of 4.5 DCC_CHECK 3.0 + KAM_MARKETINGBL 1.0 +
6
- **1.5 GB_AWSTRACK_REDIR, Amazon SES's click-tracker** and the one rule this
7
- file had never heard of pushed a proved announcement into "Check this message
8
- before acting on it / Links and attachments here should not be opened".
3
+ Bob 2026-08-28, on a newsletter he subscribed to: "I don't need the spam
4
+ reminder but I don't want to say show remote content in order to prevent
5
+ tracking." He was right, and the shape of the bug is that the allowlist was
6
+ answering two different questions with one list. The only gesture in the whole
7
+ app that said "this correspondent is fine" was the remote-content banner's
8
+ "Always: …", which buys a tracking pixel with every vote of confidence.
9
9
 
10
- The "unknown means caution" policy was right; testing it by PRESENCE was not.
11
- An unknown rule now keeps a proved sender at caution only when it could have
12
- flagged the message BY ITSELF — its own scores summed against the server's own
13
- threshold. That adds no constant to tune: "enough to condemn" already has a
14
- number and it is the one the server used. KAM_BENEFICIARY, the case the policy
15
- exists for, is worth 10.0 alone and still cautions; a 1.5 click-tracker cannot.
16
- Unscored rules (no X-Spam-Report) leave the sum unknown, and unknown stays
17
- cautious.
10
+ Two lists now, both in allowlist.jsonc:
18
11
 
19
- Two classification fixes found by sampling what still cautioned a PROVED
20
- sender across the store:
12
+ senders / domains load their images, and I trust them
13
+ trustedSenders / trustedDomains ONLY stop warning me about them
21
14
 
22
- - Branded ESP click-trackers (GB_AWSTRACK*, SendGrid, Mailchimp) are bulk-mail
23
- machinery. Open redirectors (URI_PHP_REDIR and friends) stay unclassified
24
- a tracking domain the sending platform owns is not an abused redirect.
25
- - KAM_*URIBL_* ("Body contains URI listed in PCCC WILD RBL", 9.0) and
26
- SH_HBL_EMAILS ("Email address listed in email blocklist", 8.0) are the same
27
- evidence as URIBL_SBL — somebody else's list about a host the message
28
- mentions — and were the bulk of the remaining noise: a Business Insider
29
- newsletter, an MIT cancer-center invitation, a campaign mailing.
30
- KAM_INFOUSMEBIZ judges the TLD a domain lives in, which is guilt by
31
- neighbourhood.
15
+ Both feed `senderTrusted` in the store; only the first feeds `allowRemote`, and
16
+ that line carries a comment saying why it must never be widened to the second.
32
17
 
33
- Over the same 20,308 messages, of 1,131 flagged: 294 danger (unchanged — proof
34
- never overrides forgery), 278 caution, 559 quiet. What still cautions a proved
35
- sender is now almost entirely content-scam rules: ADVANCE_FEE, DEAR_BENEFICIARY,
36
- KAM_CRIM, UNDISC_MONEY, BITCOIN_DIRECT. The two false positives left in that
37
- seta Substack news digest about displacement tripping KAM_CRIM, a Qatar
38
- Airways statement tripping BITCOIN_DIRECT are exactly what sender trust is
39
- for, and are deliberately NOT being fixed by inventing more rule exceptions.
18
+ The trust note gains the action it was missing: **"Stop warning about
19
+ ‹address›"** and **"Stop warning about *@‹domain›"**, naming the sender rather
20
+ than saying "this one" so the choice is checkable afterwards. New IPC
21
+ `trustSenderOrDomain` (toggle, like flagSenderOrDomain) wired through all six
22
+ placesservice, jsonrpc, web-service, web-jsonrpc, android-bootstrap,
23
+ api-client + mailxapi.js; the bridge-contract guard passes at 110 client calls.
24
+
25
+ The buttons appear only where the server's own verdict is the WHOLE finding and
26
+ the sender is proved. A forgery banner gets no such button: no list should be
27
+ able to silence "this message is not what it says it is", and there is nothing
28
+ to trust when identity is the thing in doubt. A failed write reports itself on
29
+ the button rather than looking like it worked.
@@ -2350,6 +2350,9 @@ function serverSpamVerdict(input) {
2350
2350
  const a = analyzeServerSpam(input);
2351
2351
  if (!a || !a.flagged)
2352
2352
  return null;
2353
+ const trustedAndProved = input.senderTrusted && a.proved;
2354
+ if (trustedAndProved && a.kind !== "forgery" && !a.authFailures.length)
2355
+ return null;
2353
2356
  const numbers = Number.isFinite(a.score) && Number.isFinite(a.threshold) ? `SpamAssassin score ${a.score} of ${a.threshold}` : "flagged by SpamAssassin";
2354
2357
  const why = a.reasons.length ? ` \u2014 ${a.reasons.join("; ")}` : "";
2355
2358
  const bayes = a.bayesHam ? "; your own trained filter puts it at 0-1% spam (BAYES_00)" : "";
@@ -2375,6 +2378,13 @@ function serverSpamVerdict(input) {
2375
2378
  detail: `${numbers}${why}${bayes}`
2376
2379
  };
2377
2380
  const proof = a.proved ? `${provedNote(a)} ` : "";
2381
+ if (input.senderTrusted && !a.proved)
2382
+ return {
2383
+ id: "server-spam-verdict",
2384
+ severity: "caution",
2385
+ text: "This claims to be a sender you trust, but nothing proves it is.",
2386
+ detail: `No DMARC pass, no valid signature from the sending domain, and it passed through hosts your server does not trust. ${numbers}${why}`
2387
+ };
2378
2388
  return {
2379
2389
  id: "server-spam-verdict",
2380
2390
  severity: "caution",
@@ -2534,6 +2544,7 @@ function spamScoreOf(input) {
2534
2544
  kind: a.kind,
2535
2545
  proved: a.proved,
2536
2546
  provedBy: a.provedBy,
2547
+ trusted: !!input.senderTrusted && a.proved && a.kind !== "forgery" && !a.authFailures.length,
2537
2548
  reasons: a.reasons
2538
2549
  };
2539
2550
  }
@@ -7002,6 +7013,31 @@ var WebMailxService = class _WebMailxService {
7002
7013
  });
7003
7014
  return { flagged };
7004
7015
  }
7016
+ /** Trust (or untrust) a sender / domain for the spam banner ONLY — no
7017
+ * remote content. Mirrors MailxService.trustSenderOrDomain; the desktop
7018
+ * and Android halves must answer "is this sender trusted" the same way or
7019
+ * the same message reads differently on the phone. */
7020
+ async trustSenderOrDomain(type, value) {
7021
+ const v = (value || "").trim().toLowerCase();
7022
+ if (!v)
7023
+ return { trusted: false };
7024
+ let trusted = false;
7025
+ await updateAllowlist((list) => {
7026
+ const key = type === "sender" ? "trustedSenders" : "trustedDomains";
7027
+ const arr = Array.isArray(list[key]) ? list[key] : [];
7028
+ const idx = arr.findIndex((x) => (x || "").toLowerCase() === v);
7029
+ if (idx >= 0) {
7030
+ arr.splice(idx, 1);
7031
+ trusted = false;
7032
+ } else {
7033
+ arr.push(v);
7034
+ trusted = true;
7035
+ }
7036
+ list[key] = arr;
7037
+ return list;
7038
+ });
7039
+ return { trusted };
7040
+ }
7005
7041
  async updateFlags(accountId, uid, flags) {
7006
7042
  const envelope = this.db.getMessageByUid(accountId, uid);
7007
7043
  await this.syncManager.updateFlagsLocal(accountId, uid, envelope?.folderId || 0, flags);
@@ -7018,7 +7054,9 @@ var WebMailxService = class _WebMailxService {
7018
7054
  domains: list.domains || [],
7019
7055
  recipients: list.recipients || [],
7020
7056
  flaggedSenders: list.flaggedSenders || [],
7021
- flaggedDomains: list.flaggedDomains || []
7057
+ flaggedDomains: list.flaggedDomains || [],
7058
+ trustedSenders: list.trustedSenders || [],
7059
+ trustedDomains: list.trustedDomains || []
7022
7060
  };
7023
7061
  }
7024
7062
  async allowRemoteContent(type, value) {
@@ -12487,6 +12525,9 @@ function installBridge() {
12487
12525
  return { ok: true };
12488
12526
  },
12489
12527
  getAllowlist: () => service.getAllowlist(),
12528
+ trustSenderOrDomain: async (type, value) => {
12529
+ return await service.trustSenderOrDomain(type, value);
12530
+ },
12490
12531
  flagSenderOrDomain: async (type, value) => {
12491
12532
  return await service.flagSenderOrDomain(type, value);
12492
12533
  },