mailauth 4.13.0 → 4.13.2
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +66 -0
- package/docs/arc.md +89 -89
- package/docs/bimi.md +154 -153
- package/docs/dkim.md +137 -137
- package/docs/dmarc.md +128 -128
- package/docs/mta-sts.md +90 -97
- package/docs/spf.md +122 -122
- package/lib/dkim/body/relaxed.js +2 -2
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.13.2](https://github.com/postalsys/mailauth/compare/v4.13.1...v4.13.2) (2026-04-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* prevent chunk-boundary-dependent DKIM relaxed body hash ([9ae9745](https://github.com/postalsys/mailauth/commit/9ae9745a4b82737c2ef9cfe6de083e9f934b57e4)), closes [#115](https://github.com/postalsys/mailauth/issues/115)
|
|
9
|
+
|
|
10
|
+
## [4.13.1](https://github.com/postalsys/mailauth/compare/v4.13.0...v4.13.1) (2026-03-03)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* trigger build ([33b05b7](https://github.com/postalsys/mailauth/commit/33b05b7df806b0104c3ac3a93986f6406b02393a))
|
|
16
|
+
|
|
3
17
|
## [4.13.0](https://github.com/postalsys/mailauth/compare/v4.12.1...v4.13.0) (2026-02-04)
|
|
4
18
|
|
|
5
19
|
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
mailauth is a Node.js library and CLI tool for email authentication. It implements SPF, DKIM, DMARC, ARC, BIMI, and MTA-STS protocols. Pure JavaScript, no native dependencies. Requires Node.js >= 20.18.1.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
- **Run all tests (lint + mocha):** `npm test`
|
|
12
|
+
- **Run a single test file:** `npx mocha test/dkim/body/relaxed-test.js --reporter spec`
|
|
13
|
+
- **Run tests matching a pattern:** `npx mocha --recursive "./test/**/*.js" --reporter spec --grep "pattern"`
|
|
14
|
+
- **Lint only:** `npx eslint "lib/**/*.js" "test/**/*.js"`
|
|
15
|
+
- **Format code:** `npm run format` (uses Prettier)
|
|
16
|
+
|
|
17
|
+
## Code Style
|
|
18
|
+
|
|
19
|
+
- CommonJS (`require`/`module.exports`), not ES modules
|
|
20
|
+
- `'use strict'` at top of every file
|
|
21
|
+
- Mocha + Chai (expect style) for tests
|
|
22
|
+
- Prettier: 160 print width, 4-space indent, single quotes, no trailing commas, LF line endings
|
|
23
|
+
- ESLint: unused vars prefixed with `_`, `no-console` allowed
|
|
24
|
+
|
|
25
|
+
## Architecture
|
|
26
|
+
|
|
27
|
+
### Entry Point
|
|
28
|
+
|
|
29
|
+
`lib/mailauth.js` — exports `authenticate()` (the main all-in-one function) and individual protocol functions. The `authenticate` pipeline runs in order: DKIM verify → SPF → ARC → DMARC → BIMI, each step feeding results to the next.
|
|
30
|
+
|
|
31
|
+
### Protocol Modules
|
|
32
|
+
|
|
33
|
+
Each protocol lives in its own directory under `lib/`:
|
|
34
|
+
|
|
35
|
+
- **`lib/dkim/`** — DKIM signing (`sign.js`, `dkim-signer.js`, `DkimSignStream`) and verification (`verify.js`, `dkim-verifier.js`). Body canonicalization in `body/` (relaxed/simple), header canonicalization in `header/` (relaxed/simple). `message-parser.js` is the streaming RFC822 parser used by both signing and verification.
|
|
36
|
+
- **`lib/spf/`** — SPF verification (`spf-verify.js`), macro expansion (`macro.js`). Entry point `index.js` wraps the verifier.
|
|
37
|
+
- **`lib/dmarc/`** — DMARC verification (`verify.js`) and DNS record fetching (`get-dmarc-record.js`). Takes DKIM and SPF results as input.
|
|
38
|
+
- **`lib/arc/`** — ARC chain validation and seal creation. `trustlist.js` contains known ARC trust anchors.
|
|
39
|
+
- **`lib/bimi/`** — BIMI record resolution and SVG validation (`validate-svg.js`). Depends on DMARC passing.
|
|
40
|
+
- **`lib/mta-sts.js`** — MTA-STS policy fetching and MX validation (standalone, not part of authenticate pipeline).
|
|
41
|
+
|
|
42
|
+
### Shared Utilities
|
|
43
|
+
|
|
44
|
+
- **`lib/tools.js`** — DNS helpers, key parsing, header formatting, stream utilities, domain alignment checks
|
|
45
|
+
- **`lib/parse-dkim-headers.js`** — Parses structured DKIM/ARC header fields
|
|
46
|
+
- **`lib/parse-received.js`** — Parses Received headers
|
|
47
|
+
|
|
48
|
+
### CLI
|
|
49
|
+
|
|
50
|
+
`bin/mailauth.js` — yargs-based CLI with subcommands: `report`, `sign`, `seal`, `spf`, `vmc`, `bodyhash`. Command implementations in `lib/commands/`.
|
|
51
|
+
|
|
52
|
+
### Tests
|
|
53
|
+
|
|
54
|
+
Tests mirror the `lib/` structure under `test/`. Test fixtures in `test/fixtures/` include sample emails, DNS response caches, keys, and RFC test suite YAML files. Many tests use mock DNS resolvers that return fixture data instead of making real DNS queries.
|
|
55
|
+
|
|
56
|
+
### Type Definitions
|
|
57
|
+
|
|
58
|
+
`index.d.ts` and per-module `.d.ts` files provide TypeScript type definitions for consumers.
|
|
59
|
+
|
|
60
|
+
### Key Design Pattern
|
|
61
|
+
|
|
62
|
+
All protocol functions accept an optional `resolver` parameter (async function matching `dns.promises.resolve` signature) for DNS lookups. This enables testing with mock DNS and allows consumers to implement caching or custom resolution.
|
|
63
|
+
|
|
64
|
+
## Releases
|
|
65
|
+
|
|
66
|
+
Version numbers, changelogs, and releases are managed automatically by release-please via GitHub Actions. Do not manually edit `package.json` version, `CHANGELOG.md`, or release-related files.
|
package/docs/arc.md
CHANGED
|
@@ -16,70 +16,70 @@ const { arc } = await authenticate(message, {
|
|
|
16
16
|
|
|
17
17
|
## Validation Result Object Fields
|
|
18
18
|
|
|
19
|
-
| Field
|
|
20
|
-
|
|
21
|
-
| `status`
|
|
22
|
-
| `chain`
|
|
23
|
-
| `i`
|
|
24
|
-
| `signature`
|
|
25
|
-
| `authenticationResults` | `object`
|
|
26
|
-
| `info`
|
|
19
|
+
| Field | Type | Presence | Description |
|
|
20
|
+
| ----------------------- | --------------- | ----------------- | ----------------------------------------------------------- |
|
|
21
|
+
| `status` | `object` | Always | Validation status object (see below) |
|
|
22
|
+
| `chain` | `array` | Non-enumerable | Array of ARC chain entries (hidden from JSON serialization) |
|
|
23
|
+
| `i` | `number\|false` | Always | Last instance number in chain, or `false` if no chain |
|
|
24
|
+
| `signature` | `object` | Chain exists | Verification result for last ARC-Message-Signature |
|
|
25
|
+
| `authenticationResults` | `object` | Chain exists | Parsed last ARC-Authentication-Results |
|
|
26
|
+
| `info` | `string` | Result not "none" | Formatted Authentication-Results header value |
|
|
27
27
|
|
|
28
28
|
## status Object
|
|
29
29
|
|
|
30
|
-
| Field
|
|
31
|
-
|
|
32
|
-
| `result`
|
|
33
|
-
| `comment`
|
|
34
|
-
| `shouldSeal` | `boolean` | On fail
|
|
35
|
-
| `policy`
|
|
30
|
+
| Field | Type | Presence | Description |
|
|
31
|
+
| ------------ | --------- | ------------ | ------------------------------------------------------------- |
|
|
32
|
+
| `result` | `string` | Always | ARC result code (see below) |
|
|
33
|
+
| `comment` | `string` | On pass/fail | Description or error message |
|
|
34
|
+
| `shouldSeal` | `boolean` | On fail | Whether to continue sealing despite failure |
|
|
35
|
+
| `policy` | `object` | Policy issue | Policy violation details (e.g., `{"dkim-rules": "weak-key"}`) |
|
|
36
36
|
|
|
37
37
|
## authenticationResults Object
|
|
38
38
|
|
|
39
39
|
When ARC validation passes, the `authenticationResults` object contains parsed results from the last ARC-Authentication-Results header:
|
|
40
40
|
|
|
41
|
-
| Field
|
|
42
|
-
|
|
43
|
-
| `mta`
|
|
44
|
-
| `arc`
|
|
45
|
-
| `spf`
|
|
41
|
+
| Field | Type | Description |
|
|
42
|
+
| ------- | -------- | ------------------------------------------------------------ |
|
|
43
|
+
| `mta` | `string` | Hostname of the MTA that added this ARC set |
|
|
44
|
+
| `arc` | `object` | ARC result (`{result: "pass\|fail\|none", ...}`) |
|
|
45
|
+
| `spf` | `object` | SPF result (`{result: "pass\|fail\|...", ...}`) |
|
|
46
46
|
| `dmarc` | `object` | DMARC result (`{result: "pass\|fail\|none", header: {...}}`) |
|
|
47
|
-
| `dkim`
|
|
47
|
+
| `dkim` | `array` | Array of DKIM results |
|
|
48
48
|
|
|
49
49
|
## Result Values
|
|
50
50
|
|
|
51
|
-
| Result | Description
|
|
52
|
-
|
|
53
|
-
| `none` | No ARC chain present in message
|
|
51
|
+
| Result | Description |
|
|
52
|
+
| ------ | -------------------------------- |
|
|
53
|
+
| `none` | No ARC chain present in message |
|
|
54
54
|
| `pass` | ARC chain validated successfully |
|
|
55
|
-
| `fail` | ARC chain validation failed
|
|
55
|
+
| `fail` | ARC chain validation failed |
|
|
56
56
|
|
|
57
57
|
## Comment Values (on failure)
|
|
58
58
|
|
|
59
|
-
| Comment Pattern
|
|
60
|
-
|
|
61
|
-
| `"i={n} seal signature validation failed"` | ARC-Seal cryptographic verification failed
|
|
62
|
-
| `"i={n} no valid signature"`
|
|
63
|
-
| `"i={n} multiple {header} values"`
|
|
64
|
-
| `"chain-length={n}"`
|
|
65
|
-
| `"i={n} expected={m}"`
|
|
66
|
-
| `"i={n} no {header} set"`
|
|
67
|
-
| `"i=1 cv={value}"`
|
|
68
|
-
| `"i={n} cv={value}"`
|
|
69
|
-
| `"i={n} invalid as c"`
|
|
70
|
-
| `"i={n} invalid ams h"`
|
|
71
|
-
| `"no key for {domain}"`
|
|
72
|
-
| `"unknown key version for {domain}"`
|
|
73
|
-
| `"unknown key type for {domain}"`
|
|
74
|
-
| `"invalid public key for {domain}"`
|
|
75
|
-
| `"weak key for {domain}"`
|
|
59
|
+
| Comment Pattern | Description |
|
|
60
|
+
| ------------------------------------------ | ------------------------------------------------- |
|
|
61
|
+
| `"i={n} seal signature validation failed"` | ARC-Seal cryptographic verification failed |
|
|
62
|
+
| `"i={n} no valid signature"` | ARC-Message-Signature verification failed |
|
|
63
|
+
| `"i={n} multiple {header} values"` | Duplicate ARC headers for same instance |
|
|
64
|
+
| `"chain-length={n}"` | Chain exceeds 50 instances |
|
|
65
|
+
| `"i={n} expected={m}"` | Missing or out-of-order instance numbers |
|
|
66
|
+
| `"i={n} no {header} set"` | Missing required ARC header |
|
|
67
|
+
| `"i=1 cv={value}"` | First instance must have `cv=none` |
|
|
68
|
+
| `"i={n} cv={value}"` | Non-first instance must have `cv=pass` |
|
|
69
|
+
| `"i={n} invalid as c"` | Invalid canonicalization for ARC-Seal |
|
|
70
|
+
| `"i={n} invalid ams h"` | ARC-Message-Signature signed arc-seal (forbidden) |
|
|
71
|
+
| `"no key for {domain}"` | DNS key not found |
|
|
72
|
+
| `"unknown key version for {domain}"` | Unsupported key version |
|
|
73
|
+
| `"unknown key type for {domain}"` | Unsupported key type |
|
|
74
|
+
| `"invalid public key for {domain}"` | Malformed public key |
|
|
75
|
+
| `"weak key for {domain}"` | RSA key too short |
|
|
76
76
|
|
|
77
77
|
## Seal Result Object
|
|
78
78
|
|
|
79
79
|
When sealing a message with `sealMessage()`, the result contains:
|
|
80
80
|
|
|
81
|
-
| Field
|
|
82
|
-
|
|
81
|
+
| Field | Type | Description |
|
|
82
|
+
| --------- | ---------- | ------------------------------------------------------------------------------------------------ |
|
|
83
83
|
| `headers` | `string[]` | Array of ARC headers to prepend: `[ARC-Seal, ARC-Message-Signature, ARC-Authentication-Results]` |
|
|
84
84
|
|
|
85
85
|
## Example Output
|
|
@@ -88,44 +88,44 @@ When sealing a message with `sealMessage()`, the result contains:
|
|
|
88
88
|
|
|
89
89
|
```json
|
|
90
90
|
{
|
|
91
|
-
"status": {
|
|
92
|
-
"result": "pass",
|
|
93
|
-
"comment": "i=2 spf=pass dkim=pass dkdomain=example.com dmarc=pass fromdomain=example.com"
|
|
94
|
-
},
|
|
95
|
-
"i": 2,
|
|
96
|
-
"signature": {
|
|
97
|
-
"id": "abc123...",
|
|
98
|
-
"signingDomain": "forwarder.example.net",
|
|
99
|
-
"selector": "arc",
|
|
100
91
|
"status": {
|
|
101
|
-
"result": "pass"
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
"authenticationResults": {
|
|
105
|
-
"mta": "mx.forwarder.example.net",
|
|
106
|
-
"spf": {
|
|
107
|
-
"result": "pass",
|
|
108
|
-
"smtp": {
|
|
109
|
-
"mailfrom": "user@example.com"
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
"dkim": [
|
|
113
|
-
{
|
|
114
92
|
"result": "pass",
|
|
115
|
-
"
|
|
116
|
-
|
|
117
|
-
|
|
93
|
+
"comment": "i=2 spf=pass dkim=pass dkdomain=example.com dmarc=pass fromdomain=example.com"
|
|
94
|
+
},
|
|
95
|
+
"i": 2,
|
|
96
|
+
"signature": {
|
|
97
|
+
"id": "abc123...",
|
|
98
|
+
"signingDomain": "forwarder.example.net",
|
|
99
|
+
"selector": "arc",
|
|
100
|
+
"status": {
|
|
101
|
+
"result": "pass"
|
|
118
102
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
103
|
+
},
|
|
104
|
+
"authenticationResults": {
|
|
105
|
+
"mta": "mx.forwarder.example.net",
|
|
106
|
+
"spf": {
|
|
107
|
+
"result": "pass",
|
|
108
|
+
"smtp": {
|
|
109
|
+
"mailfrom": "user@example.com"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"dkim": [
|
|
113
|
+
{
|
|
114
|
+
"result": "pass",
|
|
115
|
+
"header": {
|
|
116
|
+
"i": "@example.com",
|
|
117
|
+
"s": "selector1"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"dmarc": {
|
|
122
|
+
"result": "pass",
|
|
123
|
+
"header": {
|
|
124
|
+
"from": "example.com"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"info": "arc=pass (i=2 spf=pass dkim=pass dkdomain=example.com dmarc=pass fromdomain=example.com)"
|
|
129
129
|
}
|
|
130
130
|
```
|
|
131
131
|
|
|
@@ -133,13 +133,13 @@ When sealing a message with `sealMessage()`, the result contains:
|
|
|
133
133
|
|
|
134
134
|
```json
|
|
135
135
|
{
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
"status": {
|
|
137
|
+
"result": "fail",
|
|
138
|
+
"comment": "i=2 seal signature validation failed",
|
|
139
|
+
"shouldSeal": true
|
|
140
|
+
},
|
|
141
|
+
"i": 2,
|
|
142
|
+
"info": "arc=fail (i=2 seal signature validation failed)"
|
|
143
143
|
}
|
|
144
144
|
```
|
|
145
145
|
|
|
@@ -147,10 +147,10 @@ When sealing a message with `sealMessage()`, the result contains:
|
|
|
147
147
|
|
|
148
148
|
```json
|
|
149
149
|
{
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
"status": {
|
|
151
|
+
"result": "none"
|
|
152
|
+
},
|
|
153
|
+
"i": 0
|
|
154
154
|
}
|
|
155
155
|
```
|
|
156
156
|
|