@bobfrankston/mailx-types 0.1.94 → 0.1.98
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/mailx-api.d.ts +2 -0
- package/package.json +1 -1
- package/trust.d.ts +18 -0
- package/trust.js +42 -4
package/mailx-api.d.ts
CHANGED
|
@@ -165,11 +165,13 @@ export interface MailxApi {
|
|
|
165
165
|
ok: boolean;
|
|
166
166
|
reason?: string;
|
|
167
167
|
}>;
|
|
168
|
+
/** accessRole is Google's: owner / writer can take a new event, reader / freeBusyReader cannot. */
|
|
168
169
|
getCalendars(): Promise<Array<{
|
|
169
170
|
id: string;
|
|
170
171
|
name: string;
|
|
171
172
|
color: string;
|
|
172
173
|
primary: boolean;
|
|
174
|
+
accessRole?: string;
|
|
173
175
|
}>>;
|
|
174
176
|
createCalendarEvent(ev: any): Promise<{
|
|
175
177
|
uuid: string;
|
package/package.json
CHANGED
package/trust.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export declare const TRUST_LIST_KEYS: {
|
|
|
39
39
|
readonly domain: "trustedDomains";
|
|
40
40
|
readonly intermediary: "trustedIntermediaries";
|
|
41
41
|
readonly list: "trustedLists";
|
|
42
|
+
readonly via: "trustedSendersVia";
|
|
42
43
|
};
|
|
43
44
|
export type TrustListType = keyof typeof TRUST_LIST_KEYS;
|
|
44
45
|
/** One piece of evidence that a message is not what it claims to be. */
|
|
@@ -63,6 +64,11 @@ export interface TrustFinding {
|
|
|
63
64
|
* when the list is not yet in allowlist.jsonc `trustedLists`, so the
|
|
64
65
|
* viewer can offer "Trust the ‹list› list". (2026-09-09) */
|
|
65
66
|
list?: string;
|
|
67
|
+
/** "‹from domain› via ‹signer›": the reader trusts this sender, nothing
|
|
68
|
+
* proves the From line, but a relay DID sign the message — so the
|
|
69
|
+
* viewer can offer to trust the sender when that relay signs for it
|
|
70
|
+
* (allowlist.jsonc `trustedSendersVia`). (2026-09-10) */
|
|
71
|
+
senderVia?: string;
|
|
66
72
|
}
|
|
67
73
|
export interface TrustInput {
|
|
68
74
|
/** Every header line, in order, as mailparser's `headerLines`. Repeated
|
|
@@ -113,6 +119,18 @@ export interface TrustInput {
|
|
|
113
119
|
* spf=pass for the list's bounce address, or dkim=pass for its domain.
|
|
114
120
|
* An entry counts only on such a message. See listProof. */
|
|
115
121
|
trustedLists?: string[];
|
|
122
|
+
/** "‹from domain› via ‹signer domain›" entries from allowlist.jsonc
|
|
123
|
+
* `trustedSendersVia`. Bob 2026-09-10, Ground News: From
|
|
124
|
+
* groundnews.email, sent through Amazon SES, signed only as
|
|
125
|
+
* amazonses.com, no DMARC record — the sender is on his list and can
|
|
126
|
+
* never be proved, so the list was ignored (correctly) and nothing could
|
|
127
|
+
* be approved. The evidence he does have is the pair: this sender always
|
|
128
|
+
* arrives signed by this relay, and a relay like SES lets a customer send
|
|
129
|
+
* only From identities it has verified. An entry counts only on a
|
|
130
|
+
* message the named relay DKIM-signed, for the named From domain; a
|
|
131
|
+
* forger with their own SES account is not groundnews.email's, and one
|
|
132
|
+
* without a relay signature matches nothing. See provedSenderVia. */
|
|
133
|
+
trustedSendersVia?: string[];
|
|
116
134
|
}
|
|
117
135
|
/**
|
|
118
136
|
* Everything findable about this message, worst first.
|
package/trust.js
CHANGED
|
@@ -39,6 +39,7 @@ export const TRUST_LIST_KEYS = {
|
|
|
39
39
|
domain: "trustedDomains",
|
|
40
40
|
intermediary: "trustedIntermediaries",
|
|
41
41
|
list: "trustedLists",
|
|
42
|
+
via: "trustedSendersVia",
|
|
42
43
|
};
|
|
43
44
|
/** First value of a header, case-insensitive. */
|
|
44
45
|
function header(lines, name) {
|
|
@@ -256,6 +257,27 @@ function provedIntermediary(input) {
|
|
|
256
257
|
return entry;
|
|
257
258
|
return "";
|
|
258
259
|
}
|
|
260
|
+
/** Parse a `trustedSendersVia` entry — "groundnews.email via amazonses.com". */
|
|
261
|
+
function parseSenderVia(entry) {
|
|
262
|
+
const m = (entry || "").trim().toLowerCase().match(/^(\S+)\s+via\s+(\S+)$/);
|
|
263
|
+
return m ? { domain: m[1], signer: m[2] } : null;
|
|
264
|
+
}
|
|
265
|
+
/** The `trustedSendersVia` entry this message satisfies, or "": the From
|
|
266
|
+
* domain matches the entry's, and the entry's relay is among the domains
|
|
267
|
+
* whose signature the receiving server verified. (2026-09-10) */
|
|
268
|
+
function provedSenderVia(input) {
|
|
269
|
+
const entries = (input.trustedSendersVia || []).map(parseSenderVia).filter(Boolean);
|
|
270
|
+
if (!entries.length)
|
|
271
|
+
return "";
|
|
272
|
+
const fromDomain = (bare(input.fromAddress).split("@")[1] || "").toLowerCase();
|
|
273
|
+
if (!fromDomain)
|
|
274
|
+
return "";
|
|
275
|
+
const signers = dkimSigners(input);
|
|
276
|
+
for (const e of entries)
|
|
277
|
+
if (sameOrg(fromDomain, e.domain) && signers.some(s => sameOrg(s, e.signer)))
|
|
278
|
+
return `${e.domain} via ${e.signer}`;
|
|
279
|
+
return "";
|
|
280
|
+
}
|
|
259
281
|
/** The mailing list this message came through, and whether the receiving
|
|
260
282
|
* server proved the list sent it. (2026-09-09)
|
|
261
283
|
*
|
|
@@ -484,6 +506,7 @@ function analyzeServerSpam(input) {
|
|
|
484
506
|
intermediary: provedIntermediary(input),
|
|
485
507
|
signers: dkimSigners(input),
|
|
486
508
|
list,
|
|
509
|
+
senderVia: provedSenderVia(input),
|
|
487
510
|
};
|
|
488
511
|
}
|
|
489
512
|
/**
|
|
@@ -541,7 +564,10 @@ function serverSpamVerdict(input) {
|
|
|
541
564
|
// A trusted mailing LIST answers it for every post the list provably
|
|
542
565
|
// delivered, whoever wrote it — the From line is the list's word, not
|
|
543
566
|
// the author's, and the reader subscribed to the list (2026-09-09).
|
|
544
|
-
|
|
567
|
+
// A sender trusted "via" a relay that signed this message answers it
|
|
568
|
+
// too — the pair is the reader's evidence for a sender that never
|
|
569
|
+
// proves itself (2026-09-10).
|
|
570
|
+
const trustedAndProved = (input.senderTrusted && a.proved) || !!a.intermediary || a.list.trusted || !!a.senderVia;
|
|
545
571
|
if (trustedAndProved && a.kind !== "forgery" && !a.authFailures.length)
|
|
546
572
|
return null;
|
|
547
573
|
// The party that sent this on the author's behalf, when it signed: the
|
|
@@ -604,14 +630,26 @@ function serverSpamVerdict(input) {
|
|
|
604
630
|
detail: `${numbers}${why}${bayes}`,
|
|
605
631
|
};
|
|
606
632
|
const proof = a.proved ? `${provedNote(a)} ` : "";
|
|
607
|
-
if (input.senderTrusted && !a.proved)
|
|
633
|
+
if (input.senderTrusted && !a.proved) {
|
|
634
|
+
// The one thing that did verify: a relay's signature. Name it and
|
|
635
|
+
// the action — a trusted sender that can never be proved otherwise
|
|
636
|
+
// leaves the reader with a banner and no way forward (Bob
|
|
637
|
+
// 2026-09-10, Ground News via Amazon SES: "another valid site. How
|
|
638
|
+
// do I approve it?").
|
|
639
|
+
const fromDomain = (bare(input.fromAddress).split("@")[1] || "").toLowerCase();
|
|
640
|
+
const relaySigner = a.signers.find(s => !sameOrg(s, fromDomain)) || "";
|
|
641
|
+
const senderViaNote = relaySigner
|
|
642
|
+
? `. ${relaySigner} signed this message on the sender's behalf; "Trust ${fromDomain} via ${relaySigner}" stops this note for mail from ${fromDomain} that ${relaySigner} signs.`
|
|
643
|
+
: "";
|
|
608
644
|
return {
|
|
609
645
|
id: "server-spam-verdict",
|
|
610
646
|
severity: "caution",
|
|
611
647
|
text: "This claims to be a sender you trust, but nothing proves it is.",
|
|
612
|
-
detail: `No DMARC pass, no valid signature from the sending domain, and it passed through hosts your server does not trust. ${numbers}${why}${listNote}`,
|
|
648
|
+
detail: `No DMARC pass, no valid signature from the sending domain, and it passed through hosts your server does not trust. ${numbers}${why}${listNote}${senderViaNote}`,
|
|
613
649
|
...viaList,
|
|
650
|
+
...(relaySigner ? { senderVia: `${fromDomain} via ${relaySigner}` } : {}),
|
|
614
651
|
};
|
|
652
|
+
}
|
|
615
653
|
return {
|
|
616
654
|
id: "server-spam-verdict",
|
|
617
655
|
severity: "caution",
|
|
@@ -982,7 +1020,7 @@ export function spamScoreOf(input) {
|
|
|
982
1020
|
proved: a.proved,
|
|
983
1021
|
provedBy: a.provedBy,
|
|
984
1022
|
bulkOnly: a.bulkOnly,
|
|
985
|
-
trusted: ((!!input.senderTrusted && a.proved) || !!a.intermediary || a.list.trusted) && a.kind !== "forgery" && !a.authFailures.length,
|
|
1023
|
+
trusted: ((!!input.senderTrusted && a.proved) || !!a.intermediary || a.list.trusted || !!a.senderVia) && a.kind !== "forgery" && !a.authFailures.length,
|
|
986
1024
|
reasons: a.reasons,
|
|
987
1025
|
listId: a.list.id,
|
|
988
1026
|
listProved: a.list.proved,
|