@bobfrankston/mailx-store 0.1.91 → 0.1.93
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/package.json +5 -5
- package/store.d.ts +4 -0
- package/store.js +18 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-store",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.93",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
13
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
12
|
+
"@bobfrankston/mailx-types": "^0.1.61",
|
|
13
|
+
"@bobfrankston/mailx-settings": "^0.1.60",
|
|
14
14
|
"@bobfrankston/mailx-bus": "^0.1.2",
|
|
15
15
|
"mailparser": "^3.7.2"
|
|
16
16
|
},
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
".transformedSnapshot": {
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
33
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
32
|
+
"@bobfrankston/mailx-types": "^0.1.61",
|
|
33
|
+
"@bobfrankston/mailx-settings": "^0.1.60",
|
|
34
34
|
"@bobfrankston/mailx-bus": "^0.1.2",
|
|
35
35
|
"mailparser": "^3.7.2"
|
|
36
36
|
}
|
package/store.d.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* be able to consume it without depending on mailx-service — that's the
|
|
20
20
|
* "arrow points the right way" property of the architecture.
|
|
21
21
|
*/
|
|
22
|
+
import { type TrustFinding } from "@bobfrankston/mailx-types";
|
|
22
23
|
import type { ParsedInvite } from "@bobfrankston/mailx-types";
|
|
23
24
|
import { MailxDB } from "./db.js";
|
|
24
25
|
import { FileMessageStore } from "./file-store.js";
|
|
@@ -53,6 +54,9 @@ export interface StoreMessage extends MessageEnvelope {
|
|
|
53
54
|
bodyOnDisk: boolean;
|
|
54
55
|
deliveredTo: string;
|
|
55
56
|
returnPath: string;
|
|
57
|
+
/** Evidence that this message is forged — empty for ordinary mail. See
|
|
58
|
+
* assessMessageTrust; the viewer renders these above the body. */
|
|
59
|
+
trust: TrustFinding[];
|
|
56
60
|
listUnsubscribe: string;
|
|
57
61
|
listUnsubscribeMail: string;
|
|
58
62
|
listUnsubscribeHttp: string;
|
package/store.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import * as fs from "node:fs";
|
|
23
23
|
import { parseSerial } from "./parse-serial.js";
|
|
24
|
+
import { assessMessageTrust } from "@bobfrankston/mailx-types";
|
|
24
25
|
import { parseInvite } from "@bobfrankston/mailx-types";
|
|
25
26
|
import { storeBus } from "./bus.js";
|
|
26
27
|
import { sanitizeHtml, htmlToPlainText } from "@bobfrankston/mailx-types";
|
|
@@ -532,6 +533,22 @@ export class Store {
|
|
|
532
533
|
if (dAddr && recipients.includes(dAddr))
|
|
533
534
|
allowRemote = true;
|
|
534
535
|
}
|
|
536
|
+
// Forgery evidence. Runs on the RAW html on purpose: sanitizeHtml
|
|
537
|
+
// disarms the image-map overlay, so by the time bodyHtml is safe the
|
|
538
|
+
// thing worth reporting is gone. Header-only checks would be immune,
|
|
539
|
+
// but the link checks would silently stop firing.
|
|
540
|
+
//
|
|
541
|
+
// "Own addresses" is the union of the recipient allowlist and the
|
|
542
|
+
// address this copy was delivered to. Delivered-To matters most: it
|
|
543
|
+
// needs no configuration and it is exactly the address a self-spoof
|
|
544
|
+
// forges, so an alias the user never got round to listing is still
|
|
545
|
+
// covered. (Claude Code 2026-08-25)
|
|
546
|
+
const trust = assessMessageTrust({
|
|
547
|
+
headerLines: (parsed.headerLines || []).map(h => ({ key: h.key, line: h.line })),
|
|
548
|
+
fromAddress: envelope.from?.address || "",
|
|
549
|
+
ownAddresses: [...recipients, (deliveredTo.match(/[^\s<>]+@[^\s<>]+/) || [""])[0]].filter(Boolean),
|
|
550
|
+
bodyHtml,
|
|
551
|
+
});
|
|
535
552
|
if (bodyHtml && !allowRemote) {
|
|
536
553
|
const result = sanitizeHtml(bodyHtml);
|
|
537
554
|
bodyHtml = result.html;
|
|
@@ -543,7 +560,7 @@ export class Store {
|
|
|
543
560
|
hasRemoteContent, remoteAllowed: allowRemote,
|
|
544
561
|
attachments,
|
|
545
562
|
cached: true,
|
|
546
|
-
deliveredTo, returnPath,
|
|
563
|
+
deliveredTo, returnPath, trust,
|
|
547
564
|
listUnsubscribe, listUnsubscribeMail, listUnsubscribeHttp, listUnsubscribeOneClick,
|
|
548
565
|
// body_path in the DB is stored relative to the body-store
|
|
549
566
|
// basePath; resolve to absolute so the UI's "Source" button
|