@primitivedotdev/sdk 1.18.0 → 1.20.0

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.
@@ -1,4 +1,4 @@
1
- import { I as WebhookAttachment, d as EmailAuth, f as EmailReceivedEvent, u as EmailAnalysis } from "./types-DVjBmOg0.js";
1
+ import { F as ValidateEmailAuthResult, I as WebhookAttachment, d as EmailAuth, f as EmailReceivedEvent, u as EmailAnalysis } from "./types-DVjBmOg0.js";
2
2
  import { ErrorObject } from "ajv";
3
3
 
4
4
  //#region src/webhook/received-email.d.ts
@@ -35,6 +35,179 @@ declare function buildForwardSubject(subject: string | null | undefined): string
35
35
  declare function formatAddress(address: ReceivedEmailAddress): string;
36
36
  declare function parseHeaderAddress(value: string | null | undefined): ReceivedEmailAddress | null;
37
37
  //#endregion
38
+ //#region src/webhook/auth.d.ts
39
+ /**
40
+ * Validate email authentication and compute a verdict.
41
+ *
42
+ * This function analyzes SPF, DKIM, and DMARC results to determine
43
+ * whether an email is likely authentic ("legit"), potentially spoofed
44
+ * ("suspicious"), or indeterminate ("unknown").
45
+ *
46
+ * ## Verdict Logic
47
+ *
48
+ * **Legit (high confidence):**
49
+ * - DMARC pass with DKIM alignment (cryptographic proof of authenticity)
50
+ *
51
+ * **Legit (medium confidence):**
52
+ * - DMARC pass with SPF alignment only (no DKIM)
53
+ * - Note: SPF can break through forwarding, but DMARC pass is still meaningful
54
+ *
55
+ * **Suspicious (high confidence):**
56
+ * - DMARC fail when domain has `reject` or `quarantine` policy
57
+ * - The domain owner explicitly says to distrust failing emails
58
+ * - SPF explicitly fails (IP not authorized by sender)
59
+ *
60
+ * **Suspicious (low confidence):**
61
+ * - DMARC fail when domain has `none` policy (monitoring mode)
62
+ * - No DMARC record but SPF/DKIM fail
63
+ *
64
+ * **Unknown:**
65
+ * - No DMARC record and no clear pass/fail
66
+ * - Temporary errors during authentication
67
+ * - No authentication data available
68
+ *
69
+ * A `legit` verdict means the email authenticated as its own From
70
+ * domain, not as any particular domain you trust. For authorization
71
+ * decisions, pair the verdict with a domain anchor via
72
+ * {@link isTrustedSender} instead of checking the verdict alone.
73
+ *
74
+ * @param auth - Email authentication results from the webhook
75
+ * @returns Verdict, confidence level, and explanatory reasons
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * const result = validateEmailAuth({
80
+ * spf: 'pass',
81
+ * dmarc: 'pass',
82
+ * dmarcPolicy: 'reject',
83
+ * dmarcFromDomain: 'example.com',
84
+ * dmarcSpfAligned: true,
85
+ * dmarcDkimAligned: true,
86
+ * dmarcSpfStrict: false,
87
+ * dmarcDkimStrict: false,
88
+ * dkimSignatures: [{
89
+ * domain: 'example.com',
90
+ * selector: 'default',
91
+ * result: 'pass',
92
+ * aligned: true,
93
+ * keyBits: 2048,
94
+ * algo: 'rsa-sha256',
95
+ * }],
96
+ * });
97
+ *
98
+ * // result.verdict === 'legit'
99
+ * // result.confidence === 'high'
100
+ * // result.reasons === ['DMARC passed with DKIM alignment']
101
+ * ```
102
+ */
103
+ declare function validateEmailAuth(auth: EmailAuth): ValidateEmailAuthResult;
104
+ //#endregion
105
+ //#region src/webhook/trust.d.ts
106
+ /**
107
+ * Why an email was or was not trusted. Stable machine-readable codes,
108
+ * ordered here by the sequence in which the checks run:
109
+ *
110
+ * - `trusted`: every check passed.
111
+ * - `auth-missing`: the event carries no usable `email.auth` object.
112
+ * - `auth-suspicious`: `validateEmailAuth` returned a `suspicious`
113
+ * verdict (DMARC/SPF failure signals).
114
+ * - `dmarc-temperror`: DMARC evaluation hit a temporary DNS error. The
115
+ * only retryable reason; the same email may verify cleanly once DNS
116
+ * recovers.
117
+ * - `auth-unknown`: authenticity could not be determined and the cause
118
+ * is not transient (most commonly the sender domain publishes no
119
+ * DMARC record, or evaluation hit a permanent error).
120
+ * - `dmarc-domain-mismatch`: the email authenticated, but the domain
121
+ * DMARC evaluated (the RFC 5322 From domain seen by the server) is
122
+ * not the expected domain.
123
+ * - `from-header-multiple-addresses`: the From header lists more than
124
+ * one address, which is ambiguous as an identity.
125
+ * - `from-header-invalid`: the From header is missing, malformed, uses
126
+ * group syntax, or fails address validation.
127
+ * - `from-domain-mismatch`: the parsed From address's domain is not the
128
+ * expected domain.
129
+ * - `sender-mismatch`: `options.sender` was given and the parsed From
130
+ * address is a different address.
131
+ */
132
+ type TrustReason = "trusted" | "auth-missing" | "auth-suspicious" | "dmarc-temperror" | "auth-unknown" | "dmarc-domain-mismatch" | "from-header-multiple-addresses" | "from-header-invalid" | "from-domain-mismatch" | "sender-mismatch";
133
+ interface TrustedSenderOptions {
134
+ /**
135
+ * The domain the email must be authenticated as (the RFC 5322 From
136
+ * domain). Matched exactly, case-insensitively: mail from a subdomain
137
+ * of `domain` does not match. Pass the subdomain itself to accept it.
138
+ */
139
+ domain: string;
140
+ /**
141
+ * Optional exact sender to require, as a bare address
142
+ * (`user@example.com`). Compared case-insensitively against the
143
+ * parsed From address. Note that DMARC authenticates the domain, not
144
+ * the local part: the domain owner's infrastructure controls which
145
+ * local parts it signs mail for.
146
+ */
147
+ sender?: string;
148
+ }
149
+ interface TrustedSenderResult {
150
+ /** True when the email is authenticated as the expected domain (and sender, if given). */
151
+ trusted: boolean;
152
+ /**
153
+ * True only for transient failures (`dmarc-temperror`). Callers that
154
+ * respond with a 5xx let webhook redelivery retry the same email
155
+ * after DNS recovers. All other untrusted reasons are permanent for
156
+ * this email.
157
+ */
158
+ retryable: boolean;
159
+ /** Machine-readable code for the first check that failed, or `trusted`. */
160
+ reason: TrustReason;
161
+ /** The underlying `validateEmailAuth` result, for logging and diagnostics. */
162
+ auth: ValidateEmailAuthResult;
163
+ }
164
+ /**
165
+ * Check whether an inbound email is authenticated as an expected domain
166
+ * (and optionally an exact sender address).
167
+ *
168
+ * `trusted` is true only when ALL of the following hold:
169
+ *
170
+ * 1. `validateEmailAuth(event.email.auth)` returns a `legit` verdict.
171
+ * 2. `event.email.auth.dmarcFromDomain` (the domain the server's DMARC
172
+ * evaluation ran against) equals `options.domain`.
173
+ * 3. The From header strict-parses to exactly one valid address whose
174
+ * domain equals `options.domain`.
175
+ * 4. When `options.sender` is given, the parsed From address equals it
176
+ * exactly (case-insensitive).
177
+ *
178
+ * ## Why the extra checks beyond the verdict
179
+ *
180
+ * The verdict alone says an email was authenticated, not which domain
181
+ * it was authenticated as: a fully authenticated email from an
182
+ * attacker-controlled domain is `legit`. Anchoring `dmarcFromDomain`
183
+ * closes that. The strict From parse defends the remaining gaps:
184
+ *
185
+ * - Naively regexing the raw From header is unsafe. A header like
186
+ * `From: "trusted@example.com" <x@evil.com>` plants an allowlisted
187
+ * address in the display name while DMARC evaluates (and passes for)
188
+ * `evil.com`. The strict parser extracts only the real addr-spec and
189
+ * rejects multi-address and group forms outright.
190
+ * - `normalizeReceivedEmail().sender` is NOT a safe anchor for
191
+ * authorization: it uses a lenient parser and falls back to the SMTP
192
+ * envelope sender (`smtp.mail_from`), which the sender fully
193
+ * controls. The same goes for Reply-To (`replyTarget`). This function
194
+ * never consults either.
195
+ * - An `unknown` verdict is not one thing: a DMARC temperror is
196
+ * transient (surfaced as `retryable: true`, respond 5xx and let
197
+ * webhook redelivery retry), while "no DMARC record" is permanent
198
+ * for the email and surfaced as non-retryable.
199
+ *
200
+ * Never throws for malformed event content; malformed input yields an
201
+ * untrusted result with a reason. Throws `TypeError` only for invalid
202
+ * `options` (programmer error).
203
+ *
204
+ * @param event - The verified `email.received` webhook event
205
+ * @param options - Expected domain and optional exact sender
206
+ * @returns Trust decision with a stable reason code and the underlying
207
+ * auth result
208
+ */
209
+ declare function isTrustedSender(event: EmailReceivedEvent, options: TrustedSenderOptions): TrustedSenderResult;
210
+ //#endregion
38
211
  //#region src/webhook/errors.d.ts
39
212
  /**
40
213
  * Verification error definitions.
@@ -242,4 +415,4 @@ declare class RawEmailDecodeError extends PrimitiveWebhookError {
242
415
  constructor(code: RawEmailDecodeErrorCode, message?: string);
243
416
  }
244
417
  //#endregion
245
- export { buildForwardSubject as _, RawEmailDecodeErrorCode as a, normalizeReceivedEmail as b, WebhookPayloadError as c, WebhookValidationErrorCode as d, WebhookVerificationError as f, ReceivedEmailThread as g, ReceivedEmailAddress as h, RawEmailDecodeError as i, WebhookPayloadErrorCode as l, ReceivedEmail as m, PrimitiveWebhookError as n, VERIFICATION_ERRORS as o, WebhookVerificationErrorCode as p, RAW_EMAIL_ERRORS as r, WebhookErrorCode as s, PAYLOAD_ERRORS as t, WebhookValidationError as u, buildReplySubject as v, parseHeaderAddress as x, formatAddress as y };
418
+ export { buildReplySubject as C, parseHeaderAddress as E, buildForwardSubject as S, normalizeReceivedEmail as T, isTrustedSender as _, RawEmailDecodeErrorCode as a, ReceivedEmailAddress as b, WebhookPayloadError as c, WebhookValidationErrorCode as d, WebhookVerificationError as f, TrustedSenderResult as g, TrustedSenderOptions as h, RawEmailDecodeError as i, WebhookPayloadErrorCode as l, TrustReason as m, PrimitiveWebhookError as n, VERIFICATION_ERRORS as o, WebhookVerificationErrorCode as p, RAW_EMAIL_ERRORS as r, WebhookErrorCode as s, PAYLOAD_ERRORS as t, WebhookValidationError as u, validateEmailAuth as v, formatAddress as w, ReceivedEmailThread as x, ReceivedEmail as y };
@@ -1,5 +1,5 @@
1
- import { F as ValidateEmailAuthResult, L as WebhookEvent, d as EmailAuth, f as EmailReceivedEvent } from "./types-DVjBmOg0.js";
2
- import { m as ReceivedEmail, u as WebhookValidationError } from "./errors-BTHBQqJa.js";
1
+ import { L as WebhookEvent, f as EmailReceivedEvent } from "./types-DVjBmOg0.js";
2
+ import { u as WebhookValidationError, y as ReceivedEmail } from "./errors-DAHv2kWU.js";
3
3
 
4
4
  //#region src/validation.d.ts
5
5
  interface ValidationSuccess<T> {
@@ -1380,68 +1380,6 @@ declare const emailReceivedEventJsonSchema: {
1380
1380
  };
1381
1381
  };
1382
1382
  //#endregion
1383
- //#region src/webhook/auth.d.ts
1384
- /**
1385
- * Validate email authentication and compute a verdict.
1386
- *
1387
- * This function analyzes SPF, DKIM, and DMARC results to determine
1388
- * whether an email is likely authentic ("legit"), potentially spoofed
1389
- * ("suspicious"), or indeterminate ("unknown").
1390
- *
1391
- * ## Verdict Logic
1392
- *
1393
- * **Legit (high confidence):**
1394
- * - DMARC pass with DKIM alignment (cryptographic proof of authenticity)
1395
- *
1396
- * **Legit (medium confidence):**
1397
- * - DMARC pass with SPF alignment only (no DKIM)
1398
- * - Note: SPF can break through forwarding, but DMARC pass is still meaningful
1399
- *
1400
- * **Suspicious (high confidence):**
1401
- * - DMARC fail when domain has `reject` or `quarantine` policy
1402
- * - The domain owner explicitly says to distrust failing emails
1403
- * - SPF explicitly fails (IP not authorized by sender)
1404
- *
1405
- * **Suspicious (low confidence):**
1406
- * - DMARC fail when domain has `none` policy (monitoring mode)
1407
- * - No DMARC record but SPF/DKIM fail
1408
- *
1409
- * **Unknown:**
1410
- * - No DMARC record and no clear pass/fail
1411
- * - Temporary errors during authentication
1412
- * - No authentication data available
1413
- *
1414
- * @param auth - Email authentication results from the webhook
1415
- * @returns Verdict, confidence level, and explanatory reasons
1416
- *
1417
- * @example
1418
- * ```typescript
1419
- * const result = validateEmailAuth({
1420
- * spf: 'pass',
1421
- * dmarc: 'pass',
1422
- * dmarcPolicy: 'reject',
1423
- * dmarcFromDomain: 'example.com',
1424
- * dmarcSpfAligned: true,
1425
- * dmarcDkimAligned: true,
1426
- * dmarcSpfStrict: false,
1427
- * dmarcDkimStrict: false,
1428
- * dkimSignatures: [{
1429
- * domain: 'example.com',
1430
- * selector: 'default',
1431
- * result: 'pass',
1432
- * aligned: true,
1433
- * keyBits: 2048,
1434
- * algo: 'rsa-sha256',
1435
- * }],
1436
- * });
1437
- *
1438
- * // result.verdict === 'legit'
1439
- * // result.confidence === 'high'
1440
- * // result.reasons === ['DMARC passed with DKIM alignment']
1441
- * ```
1442
- */
1443
- declare function validateEmailAuth(auth: EmailAuth): ValidateEmailAuthResult;
1444
- //#endregion
1445
1383
  //#region src/webhook/version.d.ts
1446
1384
  /**
1447
1385
  * Webhook API Version
@@ -1726,4 +1664,4 @@ declare function decodeRawEmail(event: EmailReceivedEvent, options?: DecodeRawEm
1726
1664
  */
1727
1665
  declare function verifyRawEmailDownload(downloaded: Buffer | ArrayBuffer | Uint8Array, event: EmailReceivedEvent): Buffer;
1728
1666
  //#endregion
1729
- export { PRIMITIVE_SIGNATURE_HEADER as A, safeValidateEmailReceivedEvent as B, StandardWebhooksSignResult as C, LEGACY_CONFIRMED_HEADER as D, verifyStandardWebhooksSignature as E, GenerateDownloadTokenOptions as F, VerifyDownloadTokenOptions as I, VerifyDownloadTokenResult as L, VerifyOptions as M, signWebhookPayload as N, LEGACY_SIGNATURE_HEADER as O, verifyWebhookSignature as P, generateDownloadToken as R, STANDARD_WEBHOOK_TIMESTAMP_HEADER as S, signStandardWebhooksPayload as T, validateEmailReceivedEvent as V, WEBHOOK_VERSION as _, WebhookHeaders as a, STANDARD_WEBHOOK_ID_HEADER as b, getDownloadTimeRemaining as c, handleWebhookEvent as d, isDownloadExpired as f, verifyRawEmailDownload as g, receive as h, WEBHOOK_EVENT_HEADER as i, SignResult as j, PRIMITIVE_CONFIRMED_HEADER as k, getEventHeader as l, parseWebhookEvent as m, HandleWebhookOptions as n, confirmedHeaders as o, isRawIncluded as p, ReceiveRequestOptions as r, decodeRawEmail as s, DecodeRawEmailOptions as t, handleWebhook as u, validateEmailAuth as v, StandardWebhooksVerifyOptions as w, STANDARD_WEBHOOK_SIGNATURE_HEADER as x, emailReceivedEventJsonSchema as y, verifyDownloadToken as z };
1667
+ export { SignResult as A, validateEmailReceivedEvent as B, StandardWebhooksVerifyOptions as C, LEGACY_SIGNATURE_HEADER as D, LEGACY_CONFIRMED_HEADER as E, VerifyDownloadTokenOptions as F, VerifyDownloadTokenResult as I, generateDownloadToken as L, signWebhookPayload as M, verifyWebhookSignature as N, PRIMITIVE_CONFIRMED_HEADER as O, GenerateDownloadTokenOptions as P, verifyDownloadToken as R, StandardWebhooksSignResult as S, verifyStandardWebhooksSignature as T, WEBHOOK_VERSION as _, WebhookHeaders as a, STANDARD_WEBHOOK_SIGNATURE_HEADER as b, getDownloadTimeRemaining as c, handleWebhookEvent as d, isDownloadExpired as f, verifyRawEmailDownload as g, receive as h, WEBHOOK_EVENT_HEADER as i, VerifyOptions as j, PRIMITIVE_SIGNATURE_HEADER as k, getEventHeader as l, parseWebhookEvent as m, HandleWebhookOptions as n, confirmedHeaders as o, isRawIncluded as p, ReceiveRequestOptions as r, decodeRawEmail as s, DecodeRawEmailOptions as t, handleWebhook as u, emailReceivedEventJsonSchema as v, signStandardWebhooksPayload as w, STANDARD_WEBHOOK_TIMESTAMP_HEADER as x, STANDARD_WEBHOOK_ID_HEADER as y, safeValidateEmailReceivedEvent as z };