@lunora/mail 1.0.0-alpha.59 → 1.0.0-alpha.60
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/dist/inbound/index.d.mts
CHANGED
|
@@ -26,6 +26,16 @@ interface InboundAttachment {
|
|
|
26
26
|
* Verdicts are best-effort: when the receiving MX did not stamp an
|
|
27
27
|
* `Authentication-Results` header, every field is `null` ("unknown").
|
|
28
28
|
*
|
|
29
|
+
* SECURITY: a bare `"pass"` is NOT proof the `From` header is genuine. SPF
|
|
30
|
+
* authenticates the envelope `MAIL FROM` domain and DKIM the signing domain
|
|
31
|
+
* (`d=`), and an attacker controls both — `spf=pass smtp.mailfrom=evil.example;
|
|
32
|
+
* dkim=pass header.d=evil.example` is routine for a message whose `From` says
|
|
33
|
+
* `ceo@victim.example`. Each verdict therefore carries the identifier it is
|
|
34
|
+
* about (`*Domain`): an SPF or DKIM pass only vouches for `from` when that
|
|
35
|
+
* domain equals the `From` address's domain (RFC 7489 strict alignment). Only a
|
|
36
|
+
* DMARC pass already checked alignment for you. A pass with no identifier
|
|
37
|
+
* reported cannot be aligned and must be treated as unauthenticated.
|
|
38
|
+
*
|
|
29
39
|
* SECURITY: verdicts are read from the **first/topmost** `Authentication-Results`
|
|
30
40
|
* header in document order. The receiving MX prepends its own genuine header per
|
|
31
41
|
* RFC 8601, so the topmost occurrence is the trustworthy one; any lower
|
|
@@ -37,20 +47,28 @@ interface InboundAttachment {
|
|
|
37
47
|
interface InboundAuthentication {
|
|
38
48
|
/** DKIM verdict (`"pass"`/`"fail"`/…), or `null` when not reported. */
|
|
39
49
|
dkim: string | null;
|
|
50
|
+
/** Signing domain the DKIM verdict is about (`header.d=`, lowercased), or `null` when not reported. */
|
|
51
|
+
dkimDomain: string | null;
|
|
40
52
|
/** DMARC verdict (`"pass"`/`"fail"`/…), or `null` when not reported. */
|
|
41
53
|
dmarc: string | null;
|
|
54
|
+
/** `From` domain the DMARC verdict evaluated (`header.from=`, lowercased), or `null` when not reported. */
|
|
55
|
+
dmarcDomain: string | null;
|
|
42
56
|
/** SPF verdict (`"pass"`/`"fail"`/…), or `null` when not reported. */
|
|
43
57
|
spf: string | null;
|
|
58
|
+
/** Envelope `MAIL FROM` domain the SPF verdict is about (`smtp.mailfrom=`, local part dropped, lowercased), or `null` when not reported. */
|
|
59
|
+
spfDomain: string | null;
|
|
44
60
|
}
|
|
45
61
|
/** Normalised, transport-agnostic view of a received message. */
|
|
46
62
|
interface InboundEmail {
|
|
47
63
|
/** Decoded attachments (empty array when none). */
|
|
48
64
|
attachments: InboundAttachment[];
|
|
49
65
|
/**
|
|
50
|
-
* Sender-authentication verdicts (DKIM/SPF/DMARC)
|
|
51
|
-
* MX's **first/topmost**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
66
|
+
* Sender-authentication verdicts (DKIM/SPF/DMARC) and the domain each is
|
|
67
|
+
* about, parsed from the receiving MX's **first/topmost**
|
|
68
|
+
* `Authentication-Results` header. SECURITY: see
|
|
69
|
+
* {@link InboundAuthentication} — `from` is spoofable, and an SPF/DKIM pass
|
|
70
|
+
* vouches for it only when its `*Domain` equals the `From` domain (a DMARC
|
|
71
|
+
* pass checked that already). Reading the raw `headers["authentication-results"]`
|
|
54
72
|
* map instead exposes last-wins (a lower, potentially attacker-injected)
|
|
55
73
|
* value — trust `authentication`, not the raw map.
|
|
56
74
|
*/
|
package/dist/inbound/index.d.ts
CHANGED
|
@@ -26,6 +26,16 @@ interface InboundAttachment {
|
|
|
26
26
|
* Verdicts are best-effort: when the receiving MX did not stamp an
|
|
27
27
|
* `Authentication-Results` header, every field is `null` ("unknown").
|
|
28
28
|
*
|
|
29
|
+
* SECURITY: a bare `"pass"` is NOT proof the `From` header is genuine. SPF
|
|
30
|
+
* authenticates the envelope `MAIL FROM` domain and DKIM the signing domain
|
|
31
|
+
* (`d=`), and an attacker controls both — `spf=pass smtp.mailfrom=evil.example;
|
|
32
|
+
* dkim=pass header.d=evil.example` is routine for a message whose `From` says
|
|
33
|
+
* `ceo@victim.example`. Each verdict therefore carries the identifier it is
|
|
34
|
+
* about (`*Domain`): an SPF or DKIM pass only vouches for `from` when that
|
|
35
|
+
* domain equals the `From` address's domain (RFC 7489 strict alignment). Only a
|
|
36
|
+
* DMARC pass already checked alignment for you. A pass with no identifier
|
|
37
|
+
* reported cannot be aligned and must be treated as unauthenticated.
|
|
38
|
+
*
|
|
29
39
|
* SECURITY: verdicts are read from the **first/topmost** `Authentication-Results`
|
|
30
40
|
* header in document order. The receiving MX prepends its own genuine header per
|
|
31
41
|
* RFC 8601, so the topmost occurrence is the trustworthy one; any lower
|
|
@@ -37,20 +47,28 @@ interface InboundAttachment {
|
|
|
37
47
|
interface InboundAuthentication {
|
|
38
48
|
/** DKIM verdict (`"pass"`/`"fail"`/…), or `null` when not reported. */
|
|
39
49
|
dkim: string | null;
|
|
50
|
+
/** Signing domain the DKIM verdict is about (`header.d=`, lowercased), or `null` when not reported. */
|
|
51
|
+
dkimDomain: string | null;
|
|
40
52
|
/** DMARC verdict (`"pass"`/`"fail"`/…), or `null` when not reported. */
|
|
41
53
|
dmarc: string | null;
|
|
54
|
+
/** `From` domain the DMARC verdict evaluated (`header.from=`, lowercased), or `null` when not reported. */
|
|
55
|
+
dmarcDomain: string | null;
|
|
42
56
|
/** SPF verdict (`"pass"`/`"fail"`/…), or `null` when not reported. */
|
|
43
57
|
spf: string | null;
|
|
58
|
+
/** Envelope `MAIL FROM` domain the SPF verdict is about (`smtp.mailfrom=`, local part dropped, lowercased), or `null` when not reported. */
|
|
59
|
+
spfDomain: string | null;
|
|
44
60
|
}
|
|
45
61
|
/** Normalised, transport-agnostic view of a received message. */
|
|
46
62
|
interface InboundEmail {
|
|
47
63
|
/** Decoded attachments (empty array when none). */
|
|
48
64
|
attachments: InboundAttachment[];
|
|
49
65
|
/**
|
|
50
|
-
* Sender-authentication verdicts (DKIM/SPF/DMARC)
|
|
51
|
-
* MX's **first/topmost**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
66
|
+
* Sender-authentication verdicts (DKIM/SPF/DMARC) and the domain each is
|
|
67
|
+
* about, parsed from the receiving MX's **first/topmost**
|
|
68
|
+
* `Authentication-Results` header. SECURITY: see
|
|
69
|
+
* {@link InboundAuthentication} — `from` is spoofable, and an SPF/DKIM pass
|
|
70
|
+
* vouches for it only when its `*Domain` equals the `From` domain (a DMARC
|
|
71
|
+
* pass checked that already). Reading the raw `headers["authentication-results"]`
|
|
54
72
|
* map instead exposes last-wins (a lower, potentially attacker-injected)
|
|
55
73
|
* value — trust `authentication`, not the raw map.
|
|
56
74
|
*/
|
package/dist/inbound/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createInboundEmailHandler as r,dispatchToLunoraFunction as a}from"../packem_shared/createInboundEmailHandler-C6V47mz5.mjs";import{parseInboundEmail as t}from"../packem_shared/parseInboundEmail-
|
|
1
|
+
import{createInboundEmailHandler as r,dispatchToLunoraFunction as a}from"../packem_shared/createInboundEmailHandler-C6V47mz5.mjs";import{parseInboundEmail as t}from"../packem_shared/parseInboundEmail-BG0NU_e9.mjs";export{r as createInboundEmailHandler,a as dispatchToLunoraFunction,t as parseInboundEmail};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import f from"postal-mime";import{a as d}from"./address-soPj2Z0j.mjs";const t=(n,e)=>{if(e!==void 0)return d(`inbound ${n}`,e),e},u=n=>n.address!==void 0&&n.address!==""?n.name?`${n.name} <${n.address}>`:n.address:n.group?n.group.map(e=>e.address??"").filter(e=>e!=="").join(", "):n.name??"",m=(n,e,i)=>{const s=new RegExp(String.raw`(?:^|;)\s*${e}\s*=\s*([a-z]+)([^;]*)`,"i").exec(n.replaceAll(/\([^()]*\)/g,""));if(!s)return[null,null];const r=new RegExp(String.raw`\b${i.replaceAll(".",String.raw`\.`)}\s*=\s*"?([^\s;"]+)`,"i").exec(s[2]??"")?.[1];return[s[1]?.toLowerCase()??null,r===void 0?null:r.slice(r.lastIndexOf("@")+1).toLowerCase()]},p=n=>{if(n===void 0||n==="")return{dkim:null,dkimDomain:null,dmarc:null,dmarcDomain:null,spf:null,spfDomain:null};const[e,i]=m(n,"dkim","header.d"),[s,r]=m(n,"dmarc","header.from"),[a,c]=m(n,"spf","smtp.mailfrom");return{dkim:e,dkimDomain:i,dmarc:s,dmarcDomain:r,spf:a,spfDomain:c}},b=async n=>{const e=await f.parse(n),i={};for(const o of e.headers)d(`inbound header \`${o.key}\``,o.value),i[o.key]=o.value;const s=e.headers.find(o=>o.key==="authentication-results")?.value,r=(e.to??[]).map(o=>{const l=u(o);return d("inbound to",l),l}),a=e.from?u(e.from):"";return d("inbound from",a),{attachments:e.attachments.map(o=>({content:o.content,disposition:o.disposition,...o.encoding===void 0?{}:{encoding:o.encoding},filename:o.filename,mimeType:o.mimeType})),authentication:p(s),from:a,headers:i,...e.html===void 0?{}:{html:e.html},...t("inReplyTo",e.inReplyTo)===void 0?{}:{inReplyTo:e.inReplyTo},...t("messageId",e.messageId)===void 0?{}:{messageId:e.messageId},...t("references",e.references)===void 0?{}:{references:e.references},...t("subject",e.subject)===void 0?{}:{subject:e.subject},...e.text===void 0?{}:{text:e.text},to:r}};export{b as parseInboundEmail};
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import u from"postal-mime";import{a as r}from"./address-soPj2Z0j.mjs";const s=(o,e)=>{if(e!==void 0)return r(`inbound ${o}`,e),e},m=o=>o.address!==void 0&&o.address!==""?o.name?`${o.name} <${o.address}>`:o.address:o.group?o.group.map(e=>e.address??"").filter(e=>e!=="").join(", "):o.name??"",t=(o,e)=>new RegExp(String.raw`\b${e}=([a-zA-Z]+)`,"i").exec(o)?.[1]?.toLowerCase()??null,p=o=>o===void 0||o===""?{dkim:null,dmarc:null,spf:null}:{dkim:t(o,"dkim"),dmarc:t(o,"dmarc"),spf:t(o,"spf")},b=async o=>{const e=await u.parse(o),i={};for(const n of e.headers)r(`inbound header \`${n.key}\``,n.value),i[n.key]=n.value;const c=e.headers.find(n=>n.key==="authentication-results")?.value,f=(e.to??[]).map(n=>{const a=m(n);return r("inbound to",a),a}),d=e.from?m(e.from):"";return r("inbound from",d),{attachments:e.attachments.map(n=>({content:n.content,disposition:n.disposition,...n.encoding===void 0?{}:{encoding:n.encoding},filename:n.filename,mimeType:n.mimeType})),authentication:p(c),from:d,headers:i,...e.html===void 0?{}:{html:e.html},...s("inReplyTo",e.inReplyTo)===void 0?{}:{inReplyTo:e.inReplyTo},...s("messageId",e.messageId)===void 0?{}:{messageId:e.messageId},...s("references",e.references)===void 0?{}:{references:e.references},...s("subject",e.subject)===void 0?{}:{subject:e.subject},...e.text===void 0?{}:{text:e.text},to:f}};export{b as parseInboundEmail};
|