mailauth 4.9.5 → 4.10.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.
- package/.ncurc.js +1 -5
- package/.prettierignore +10 -0
- package/.release-please-manifest.json +3 -0
- package/CHANGELOG.md +42 -0
- package/LICENSE.txt +1 -1
- package/README.md +65 -65
- package/cli.md +73 -74
- package/eslint.config.js +40 -0
- package/lib/spf/index.js +103 -8
- package/lib/spf/spf-verify.js +10 -3
- package/package.json +10 -8
- package/release-please-config.json +9 -0
package/.ncurc.js
CHANGED
package/.prettierignore
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.10.0](https://github.com/postalsys/mailauth/compare/mailauth-v4.9.5...mailauth-v4.10.0) (2025-10-31)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* added `forwardemail.net` to ARC trusted list ([#86](https://github.com/postalsys/mailauth/issues/86)) ([8cb577b](https://github.com/postalsys/mailauth/commit/8cb577b5cceaf0a61f02744811ad2f9533550032))
|
|
9
|
+
* **cert-type:** BIMI authority information includes the type of the cert ('VMC' or 'CMC') ([0dd8db8](https://github.com/postalsys/mailauth/commit/0dd8db81b2ffc8b9d84d1a4396c65bfa9a347088))
|
|
10
|
+
* **deploy:** Set up automatic publishing ([f9b9c32](https://github.com/postalsys/mailauth/commit/f9b9c325e4dbac060114aa12c5887ea8c92c0bf8))
|
|
11
|
+
* **dkim-sign:** Added new Transfor stream class DkimSignStream to sign emails in a stream processing pipeline ([130a1a3](https://github.com/postalsys/mailauth/commit/130a1a3812fac2ad710f244510ca60887c2d33a9))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **ARC:** ensure that instance value is 1 if ARC chain does not exist yet ([ab4c5e9](https://github.com/postalsys/mailauth/commit/ab4c5e9ae0158e196b10f346321ca55b8f06c679))
|
|
17
|
+
* **ARC:** Updated built-in trust list for ARC ([ea9fc8c](https://github.com/postalsys/mailauth/commit/ea9fc8c6f8c5609b66053f1ffe95891c0b4efcb7))
|
|
18
|
+
* **bimi:** Bumped VMC module to add support for GLobalSign VMC root ([d0e9ecf](https://github.com/postalsys/mailauth/commit/d0e9ecf89b699aae8ad9953445f052b558250f5a))
|
|
19
|
+
* **bimi:** skip bimi with oversized DKIM signatures ([d666d74](https://github.com/postalsys/mailauth/commit/d666d7476cbcae8b3161c78a7e737559ad112fd9))
|
|
20
|
+
* **BodyHashStream:** Skip header ([3da03d2](https://github.com/postalsys/mailauth/commit/3da03d23baa90acb119c7946c2cd740a72ba069d))
|
|
21
|
+
* bumped 2022 in copyright notices to 2024 ([cc89823](https://github.com/postalsys/mailauth/commit/cc8982349d14b42a28581ebc52aa6de2e11b5be8))
|
|
22
|
+
* bumped deps ([006475e](https://github.com/postalsys/mailauth/commit/006475ee7bbf61a8c7c00de793f4007f66dba61a))
|
|
23
|
+
* **cli:** Updated help strings for the cli script ([8a86e51](https://github.com/postalsys/mailauth/commit/8a86e51bff0300a7daea26062481ac56904202a8))
|
|
24
|
+
* **deps:** Bumped deps to clear out security warnings ([4ca35fe](https://github.com/postalsys/mailauth/commit/4ca35fef37e37ae715c420b8a52c7cb202e4b360))
|
|
25
|
+
* **deps:** Bumped deps to get updated vmc root store ([5ad7464](https://github.com/postalsys/mailauth/commit/5ad746450f97d348217607802e83445e08737faf))
|
|
26
|
+
* **deps:** Removed uuid dependency in favor of crypto.randomUUID() ([0b5d8f5](https://github.com/postalsys/mailauth/commit/0b5d8f5328d0b82f75daea7fdbd74e1e76e8b642))
|
|
27
|
+
* **dkim-relaxed:** Faster DKIM hash calculation for relaxed body if the body contains extremely long lines ([fd8c89e](https://github.com/postalsys/mailauth/commit/fd8c89edd87a114464f99ebf79a1e903a8287876))
|
|
28
|
+
* **dkim-verify:** Show the length of the source body in DKIM results ([d28663b](https://github.com/postalsys/mailauth/commit/d28663b30b0bfaf07d395e9d3eaea044c9085657))
|
|
29
|
+
* **dkim:** Added new output property mimeStructureStart ([8f25353](https://github.com/postalsys/mailauth/commit/8f25353fa6a67ba3e1f0c5091325007b2434a29d))
|
|
30
|
+
* **dkim:** New class BodyHashStream ([88d2fad](https://github.com/postalsys/mailauth/commit/88d2fad329a9a6fc8ebc1da4efc1c4844ae49507))
|
|
31
|
+
* **dkim:** Store byteLength in BodyHashStream ([081f823](https://github.com/postalsys/mailauth/commit/081f82340505d4beb88f12728919d851d35b6576))
|
|
32
|
+
* **dmarc-alignment:** Fixed tldts usage to allow private domains ([cc7dfa8](https://github.com/postalsys/mailauth/commit/cc7dfa8d820c1a4112602340192010354d51cd52))
|
|
33
|
+
* downgraded yargs because of ESM ([215c71a](https://github.com/postalsys/mailauth/commit/215c71aaa108744970533f346408c41b38590500))
|
|
34
|
+
* **ed25519:** Fixed ed25519 signing and verification ([40f1245](https://github.com/postalsys/mailauth/commit/40f12457d8f49f0ea21015fe4203b4de746ab7b8))
|
|
35
|
+
* expose verifyASChain ([#89](https://github.com/postalsys/mailauth/issues/89)) ([cd11d85](https://github.com/postalsys/mailauth/commit/cd11d851f3c8cea125209676f3ba26676c700c5b))
|
|
36
|
+
* protect against prototype pollution ([3b7515d](https://github.com/postalsys/mailauth/commit/3b7515df768ce1d2e4e02858fdfca8efca6243fb))
|
|
37
|
+
* **psl:** Replaced psl module with tldts for up to date public suffix list ([cab894b](https://github.com/postalsys/mailauth/commit/cab894b54a3544b33a641f377783db67a43bec0e))
|
|
38
|
+
* **spf:** expand macros in mx mechanism ([d8c05f9](https://github.com/postalsys/mailauth/commit/d8c05f90589e3fb5a56ecb4498e6dcb795dcc047))
|
|
39
|
+
* **spf:** optimize dual-stack A/AAAA void lookup counting ([3069e5a](https://github.com/postalsys/mailauth/commit/3069e5afa946589e54fe8aec8ffe186d90eca810))
|
|
40
|
+
* use minLength option for rsa keys ([#84](https://github.com/postalsys/mailauth/issues/84)) ([cbfed81](https://github.com/postalsys/mailauth/commit/cbfed816d953eee3c7eed99055c53f689a46a101))
|
|
41
|
+
* ZMS-246: add required policy headers in BIMI for Apple Mail ([#92](https://github.com/postalsys/mailauth/issues/92)) ([f6b3008](https://github.com/postalsys/mailauth/commit/f6b300837f9453877386ce3e76aff80fee01d913))
|
|
42
|
+
* ZMS-262 remove control chars from record add support for mappers in validateTagValueRecord ([#95](https://github.com/postalsys/mailauth/issues/95)) ([42828a6](https://github.com/postalsys/mailauth/commit/42828a6cb38add3aed35881f102488f8143407cb))
|
|
43
|
+
* ZMS-262: Add raw record sanitanization and validation util functions ([#93](https://github.com/postalsys/mailauth/issues/93)) ([e4842cf](https://github.com/postalsys/mailauth/commit/e4842cf222bd6db29f34c25434b5c38c44edefdc))
|
|
44
|
+
|
|
3
45
|
## [4.9.5](https://github.com/postalsys/mailauth/compare/v4.9.4...v4.9.5) (2025-09-10)
|
|
4
46
|
|
|
5
47
|
|
package/LICENSE.txt
CHANGED
package/README.md
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
**Key Features:**
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
9
|
+
- **SPF** verification
|
|
10
|
+
- **DKIM** signing and verification
|
|
11
|
+
- **DMARC** verification
|
|
12
|
+
- **ARC** verification and sealing
|
|
13
|
+
- Sealing during authentication
|
|
14
|
+
- Sealing after message modifications
|
|
15
|
+
- **BIMI** resolving and **VMC** validation
|
|
16
|
+
- **MTA-STS** helper functions
|
|
17
17
|
|
|
18
18
|
mailauth is a pure JavaScript implementation, requiring no external applications or compilation. It runs on any server or device with Node.js version 16 or later.
|
|
19
19
|
|
|
@@ -73,24 +73,24 @@ await authenticate(message [, options])
|
|
|
73
73
|
|
|
74
74
|
#### Parameters
|
|
75
75
|
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
76
|
+
- **message**: A `String`, `Buffer`, or `Readable` stream representing the email message.
|
|
77
|
+
- **options** (optional):
|
|
78
|
+
- **sender** (`string`): Email address from the MAIL FROM command. Defaults to the `Return-Path` header if not set.
|
|
79
|
+
- **ip** (`string`): IP address of the remote client that sent the message.
|
|
80
|
+
- **helo** (`string`): Hostname from the HELO/EHLO command.
|
|
81
|
+
- **trustReceived** (`boolean`): If `true`, parses `ip` and `helo` from the latest `Received` header if not provided. Defaults to `false`.
|
|
82
|
+
- **mta** (`string`): Hostname of the server performing the authentication. Defaults to `os.hostname()`. Included in Authentication headers.
|
|
83
|
+
- **minBitLength** (`number`): Minimum allowed bits for RSA public keys. Defaults to `1024`. Keys with fewer bits will fail validation.
|
|
84
|
+
- **disableArc** (`boolean`): If `true`, skips ARC checks.
|
|
85
|
+
- **disableDmarc** (`boolean`): If `true`, skips DMARC checks, also disabling dependent checks like BIMI.
|
|
86
|
+
- **disableBimi** (`boolean`): If `true`, skips BIMI checks.
|
|
87
|
+
- **seal** (`object`): Options for ARC sealing if the message doesn't have a broken ARC chain.
|
|
88
|
+
- **signingDomain** (`string`): ARC key domain name.
|
|
89
|
+
- **selector** (`string`): ARC key selector.
|
|
90
|
+
- **privateKey** (`string` or `Buffer`): Private key for signing (RSA or Ed25519).
|
|
91
|
+
- **resolver** (`async function`): Custom DNS resolver function. Defaults to [`dns.promises.resolve`](https://nodejs.org/api/dns.html#dns_dnspromises_resolve_hostname_rrtype).
|
|
92
|
+
- **maxResolveCount** (`number`): DNS lookup limit for SPF. Defaults to `10` as per [RFC7208](https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4).
|
|
93
|
+
- **maxVoidCount** (`number`): DNS lookup limit for SPF producing empty results. Defaults to `2` as per [RFC7208](https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4).
|
|
94
94
|
|
|
95
95
|
#### Example
|
|
96
96
|
|
|
@@ -155,18 +155,18 @@ const signResult = await dkimSign(message, options);
|
|
|
155
155
|
|
|
156
156
|
##### Parameters
|
|
157
157
|
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
158
|
+
- **message**: A `String`, `Buffer`, or `Readable` stream representing the email message.
|
|
159
|
+
- **options**:
|
|
160
|
+
- **canonicalization** (`string`): Canonicalization method. Defaults to `'relaxed/relaxed'`.
|
|
161
|
+
- **algorithm** (`string`): Signing and hashing algorithm. Defaults to `'rsa-sha256'`.
|
|
162
|
+
- **signTime** (`Date`): Signing time. Defaults to current time.
|
|
163
|
+
- **signatureData** (`Array`): Array of signature objects. Each object may contain:
|
|
164
|
+
- **signingDomain** (`string`): DKIM key domain name.
|
|
165
|
+
- **selector** (`string`): DKIM key selector.
|
|
166
|
+
- **privateKey** (`string` or `Buffer`): Private key for signing (RSA or Ed25519).
|
|
167
|
+
- **algorithm** (`string`, optional): Overrides parent `algorithm`.
|
|
168
|
+
- **canonicalization** (`string`, optional): Overrides parent `canonicalization`.
|
|
169
|
+
- **maxBodyLength** (`number`, optional): Maximum number of canonicalized body bytes to sign (`l=` tag). Not recommended for general use.
|
|
170
170
|
|
|
171
171
|
##### Example
|
|
172
172
|
|
|
@@ -286,11 +286,11 @@ const result = await spf(options);
|
|
|
286
286
|
|
|
287
287
|
##### Parameters
|
|
288
288
|
|
|
289
|
-
-
|
|
290
|
-
-
|
|
291
|
-
-
|
|
292
|
-
-
|
|
293
|
-
-
|
|
289
|
+
- **options**:
|
|
290
|
+
- **sender** (`string`): MAIL FROM address.
|
|
291
|
+
- **ip** (`string`): SMTP client IP.
|
|
292
|
+
- **helo** (`string`): HELO/EHLO hostname.
|
|
293
|
+
- **mta** (`string`): Hostname of the MTA performing the check.
|
|
294
294
|
|
|
295
295
|
##### Example
|
|
296
296
|
|
|
@@ -433,8 +433,8 @@ const dmarcRecord = await getDmarcRecord(domain [, resolver]);
|
|
|
433
433
|
|
|
434
434
|
###### Parameters
|
|
435
435
|
|
|
436
|
-
-
|
|
437
|
-
-
|
|
436
|
+
- **domain** (`string`): The domain to check for a DMARC record.
|
|
437
|
+
- **resolver** (`function`, optional): Custom DNS resolver function. Defaults to `dns.resolve`.
|
|
438
438
|
|
|
439
439
|
###### Example
|
|
440
440
|
|
|
@@ -486,8 +486,8 @@ if (bimi?.location) {
|
|
|
486
486
|
|
|
487
487
|
**Note:**
|
|
488
488
|
|
|
489
|
-
-
|
|
490
|
-
-
|
|
489
|
+
- The `BIMI-Location` header is ignored by mailauth.
|
|
490
|
+
- The `BIMI-Selector` header can be used for selector selection if available.
|
|
491
491
|
|
|
492
492
|
#### Verified Mark Certificate (VMC)
|
|
493
493
|
|
|
@@ -495,8 +495,8 @@ If an Authority Evidence Document is specified in the BIMI record, its location
|
|
|
495
495
|
|
|
496
496
|
**Example Authority Evidence Documents:**
|
|
497
497
|
|
|
498
|
-
-
|
|
499
|
-
-
|
|
498
|
+
- [CNN's VMC](https://amplify.valimail.com/bimi/time-warner/LysAFUdG-Hw-cnn_vmc.pem)
|
|
499
|
+
- [Entrust's VMC](https://www.entrustdatacard.com/-/media/certificate/Entrust%20VMC%20July%2014%202020.pem)
|
|
500
500
|
|
|
501
501
|
### MTA-STS
|
|
502
502
|
|
|
@@ -517,8 +517,8 @@ const { policy, status } = await getPolicy(domain [, knownPolicy]);
|
|
|
517
517
|
|
|
518
518
|
##### Parameters
|
|
519
519
|
|
|
520
|
-
-
|
|
521
|
-
-
|
|
520
|
+
- **domain** (`string`): The domain to retrieve the policy for.
|
|
521
|
+
- **knownPolicy** (`object`, optional): Previously cached policy for the domain.
|
|
522
522
|
|
|
523
523
|
##### Example
|
|
524
524
|
|
|
@@ -539,11 +539,11 @@ if (policy.mode === 'enforce') {
|
|
|
539
539
|
|
|
540
540
|
**Possible Status Values:**
|
|
541
541
|
|
|
542
|
-
-
|
|
543
|
-
-
|
|
544
|
-
-
|
|
545
|
-
-
|
|
546
|
-
-
|
|
542
|
+
- `"not_found"`: No policy was found.
|
|
543
|
+
- `"cached"`: Existing policy is still valid.
|
|
544
|
+
- `"found"`: New or updated policy found.
|
|
545
|
+
- `"renew"`: Existing policy is valid; renew cache.
|
|
546
|
+
- `"errored"`: Policy discovery failed due to a temporary error.
|
|
547
547
|
|
|
548
548
|
#### MX Validation
|
|
549
549
|
|
|
@@ -560,8 +560,8 @@ const validation = validateMx(mx, policy);
|
|
|
560
560
|
|
|
561
561
|
##### Parameters
|
|
562
562
|
|
|
563
|
-
-
|
|
564
|
-
-
|
|
563
|
+
- **mx** (`string`): The resolved MX hostname.
|
|
564
|
+
- **policy** (`object`): The MTA-STS policy object.
|
|
565
565
|
|
|
566
566
|
##### Example
|
|
567
567
|
|
|
@@ -586,18 +586,18 @@ mailauth uses the following test suites:
|
|
|
586
586
|
|
|
587
587
|
Based on the [OpenSPF test suite](http://www.openspf.org/Test_Suite), with some differences:
|
|
588
588
|
|
|
589
|
-
-
|
|
590
|
-
-
|
|
591
|
-
-
|
|
592
|
-
-
|
|
589
|
+
- Less strict whitespace checks.
|
|
590
|
+
- Some macro tests are skipped.
|
|
591
|
+
- Some tests are skipped where the invalid component is after a matching part.
|
|
592
|
+
- All other tests pass.
|
|
593
593
|
|
|
594
594
|
### ARC Test Suite from ValiMail
|
|
595
595
|
|
|
596
596
|
Based on ValiMail's [arc_test_suite](https://github.com/ValiMail/arc_test_suite):
|
|
597
597
|
|
|
598
|
-
-
|
|
599
|
-
-
|
|
600
|
-
-
|
|
598
|
+
- mailauth is less strict on header tags and casing.
|
|
599
|
+
- Signing test suite is used for input; mailauth validates signatures and checks for the same `cv=` output.
|
|
600
|
+
- All tests pass, aside from minor differences.
|
|
601
601
|
|
|
602
602
|
## License
|
|
603
603
|
|
package/cli.md
CHANGED
|
@@ -6,33 +6,32 @@ mailauth provides a command-line utility for email authentication, complementing
|
|
|
6
6
|
|
|
7
7
|
## Table of Contents
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Getting Help](#getting-help)
|
|
11
|
+
- [Available Commands](#available-commands)
|
|
12
|
+
- [`report`](#report) — Validate SPF, DKIM, DMARC, ARC, and BIMI
|
|
13
|
+
- [`sign`](#sign) — Sign an email with DKIM
|
|
14
|
+
- [`seal`](#seal) — Seal an email with ARC
|
|
15
|
+
- [`spf`](#spf) — Validate SPF for an IP address and email address
|
|
16
|
+
- [`vmc`](#vmc) — Validate BIMI VMC logo files
|
|
17
|
+
- [`bodyhash`](#bodyhash) — Generate the body hash value for an email
|
|
18
|
+
- [`license`](#license) — Display licenses for mailauth and included modules
|
|
19
|
+
- [DNS Cache File](#dns-cache-file)
|
|
20
|
+
- [License](#license)
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
24
24
|
Install the mailauth CLI by downloading the appropriate package for your platform or via npm:
|
|
25
25
|
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
- Install globally using npm:
|
|
26
|
+
- **MacOS:**
|
|
27
|
+
- [Intel processors](https://github.com/postalsys/mailauth/releases/latest/download/mailauth.pkg)
|
|
28
|
+
- [Apple silicon](https://github.com/postalsys/mailauth/releases/latest/download/mailauth-arm.pkg)
|
|
29
|
+
- **Linux:**
|
|
30
|
+
- [Download mailauth.tar.gz](https://github.com/postalsys/mailauth/releases/latest/download/mailauth.tar.gz)
|
|
31
|
+
- **Windows:**
|
|
32
|
+
- [Download mailauth.exe](https://github.com/postalsys/mailauth/releases/latest/download/mailauth.exe)
|
|
33
|
+
- **NPM Registry:**
|
|
34
|
+
- Install globally using npm:
|
|
36
35
|
|
|
37
36
|
```bash
|
|
38
37
|
npm install -g mailauth
|
|
@@ -72,18 +71,18 @@ The `report` command analyzes an email message and returns a JSON-formatted repo
|
|
|
72
71
|
mailauth report [options] [email]
|
|
73
72
|
```
|
|
74
73
|
|
|
75
|
-
-
|
|
74
|
+
- **email**: (Optional) Path to the EML-formatted email message file. If omitted, the email is read from standard input.
|
|
76
75
|
|
|
77
76
|
#### Options
|
|
78
77
|
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
78
|
+
- `--client-ip x.x.x.x`, `-i x.x.x.x`: IP address of the remote client that sent the email. If not provided, it's parsed from the latest `Received` header.
|
|
79
|
+
- `--sender user@example.com`, `-f user@example.com`: Email address from the MAIL FROM command. If not provided, it's parsed from the latest `Return-Path` header.
|
|
80
|
+
- `--helo hostname`, `-e hostname`: Hostname from the HELO/EHLO command. Used in some SPF validations.
|
|
81
|
+
- `--mta hostname`, `-m hostname`: Hostname of the server performing validations. Defaults to the local hostname.
|
|
82
|
+
- `--dns-cache /path/to/dns.json`, `-n /path/to/dns.json`: Path to a DNS cache file. When provided, DNS queries use cached responses.
|
|
83
|
+
- `--verbose`, `-v`: Enables verbose output, displaying debugging information.
|
|
84
|
+
- `--max-lookups number`, `-x number`: Sets the maximum number of DNS lookups for SPF checks. Defaults to `10`.
|
|
85
|
+
- `--max-void-lookups number`, `-z number`: Sets the maximum number of void DNS lookups for SPF checks. Defaults to `2`.
|
|
87
86
|
|
|
88
87
|
#### Example
|
|
89
88
|
|
|
@@ -116,19 +115,19 @@ The `sign` command signs an email message using a DKIM signature.
|
|
|
116
115
|
mailauth sign [options] [email]
|
|
117
116
|
```
|
|
118
117
|
|
|
119
|
-
-
|
|
118
|
+
- **email**: (Optional) Path to the EML-formatted email message file. If omitted, the email is read from standard input.
|
|
120
119
|
|
|
121
120
|
#### Options
|
|
122
121
|
|
|
123
|
-
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
122
|
+
- `--private-key /path/to/private.key`, `-k /path/to/private.key`: Path to the private key used for signing.
|
|
123
|
+
- `--domain example.com`, `-d example.com`: Domain name for the DKIM signature (`d=` tag).
|
|
124
|
+
- `--selector selector`, `-s selector`: Selector for the DKIM key (`s=` tag).
|
|
125
|
+
- `--algo algorithm`, `-a algorithm`: Signing algorithm (e.g., `rsa-sha256`). Defaults based on the private key type.
|
|
126
|
+
- `--canonicalization method`, `-c method`: Canonicalization method (e.g., `relaxed/relaxed`). Defaults to `relaxed/relaxed`.
|
|
127
|
+
- `--time timestamp`, `-t timestamp`: Signing time as a Unix timestamp (`t=` tag).
|
|
128
|
+
- `--header-fields "field1:field2"`, `-h "field1:field2"`: Colon-separated list of header fields to include in the signature (`h=` tag).
|
|
129
|
+
- `--body-length length`, `-l length`: Maximum length of the body to include in the signature (`l=` tag).
|
|
130
|
+
- `--headers-only`, `-o`: Outputs only the DKIM signature headers without the entire message.
|
|
132
131
|
|
|
133
132
|
#### Example
|
|
134
133
|
|
|
@@ -161,28 +160,28 @@ The `seal` command adds an ARC (Authenticated Received Chain) seal to an email m
|
|
|
161
160
|
mailauth seal [options] [email]
|
|
162
161
|
```
|
|
163
162
|
|
|
164
|
-
-
|
|
163
|
+
- **email**: (Optional) Path to the EML-formatted email message file. If omitted, the email is read from standard input.
|
|
165
164
|
|
|
166
165
|
#### Options
|
|
167
166
|
|
|
168
167
|
**Sealing Options:**
|
|
169
168
|
|
|
170
|
-
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
169
|
+
- `--private-key /path/to/private.key`, `-k /path/to/private.key`: Path to the private key used for sealing.
|
|
170
|
+
- `--domain example.com`, `-d example.com`: Domain name for the ARC seal (`d=` tag).
|
|
171
|
+
- `--selector selector`, `-s selector`: Selector for the ARC key (`s=` tag).
|
|
172
|
+
- `--algo algorithm`, `-a algorithm`: Sealing algorithm (e.g., `rsa-sha256`). Defaults based on the private key type.
|
|
173
|
+
- `--time timestamp`, `-t timestamp`: Sealing time as a Unix timestamp (`t=` tag).
|
|
174
|
+
- `--header-fields "field1:field2"`, `-h "field1:field2"`: Colon-separated list of header fields to include in the seal (`h=` tag).
|
|
175
|
+
- `--headers-only`, `-o`: Outputs only the ARC seal headers without the entire message.
|
|
177
176
|
|
|
178
177
|
**Authentication Options (from `report` command):**
|
|
179
178
|
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
185
|
-
-
|
|
179
|
+
- `--client-ip x.x.x.x`, `-i x.x.x.x`: IP address of the remote client that sent the email.
|
|
180
|
+
- `--sender user@example.com`, `-f user@example.com`: Email address from the MAIL FROM command.
|
|
181
|
+
- `--helo hostname`, `-e hostname`: Hostname from the HELO/EHLO command.
|
|
182
|
+
- `--mta hostname`, `-m hostname`: Hostname of the server performing validations.
|
|
183
|
+
- `--dns-cache /path/to/dns.json`, `-n /path/to/dns.json`: Path to a DNS cache file.
|
|
184
|
+
- `--verbose`, `-v`: Enables verbose output.
|
|
186
185
|
|
|
187
186
|
**Note:** The canonicalization method (`c=` tag) for ARC sealing is always `relaxed/relaxed` and cannot be changed.
|
|
188
187
|
|
|
@@ -219,15 +218,15 @@ mailauth spf [options]
|
|
|
219
218
|
|
|
220
219
|
#### Options
|
|
221
220
|
|
|
222
|
-
-
|
|
223
|
-
-
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
-
|
|
230
|
-
-
|
|
221
|
+
- `--sender user@example.com`, `-f user@example.com`: Email address from the MAIL FROM command. **Required.**
|
|
222
|
+
- `--client-ip x.x.x.x`, `-i x.x.x.x`: IP address of the remote client that sent the email. **Required.**
|
|
223
|
+
- `--helo hostname`, `-e hostname`: Hostname from the HELO/EHLO command.
|
|
224
|
+
- `--mta hostname`, `-m hostname`: Hostname of the server performing the SPF check.
|
|
225
|
+
- `--dns-cache /path/to/dns.json`, `-n /path/to/dns.json`: Path to a DNS cache file.
|
|
226
|
+
- `--verbose`, `-v`: Enables verbose output.
|
|
227
|
+
- `--headers-only`, `-o`: Outputs only the SPF authentication header.
|
|
228
|
+
- `--max-lookups number`, `-x number`: Sets the maximum number of DNS lookups. Defaults to `10`.
|
|
229
|
+
- `--max-void-lookups number`, `-z number`: Sets the maximum number of void DNS lookups. Defaults to `2`.
|
|
231
230
|
|
|
232
231
|
#### Example
|
|
233
232
|
|
|
@@ -263,9 +262,9 @@ mailauth vmc [options]
|
|
|
263
262
|
|
|
264
263
|
#### Options
|
|
265
264
|
|
|
266
|
-
-
|
|
267
|
-
-
|
|
268
|
-
-
|
|
265
|
+
- `--authority <url>`, `-a <url>`: URL of the VMC resource.
|
|
266
|
+
- `--authorityPath <path>`, `-p <path>`: Path to a local VMC file, used to avoid network requests.
|
|
267
|
+
- `--domain <domain>`, `-d <domain>`: Sender domain to validate against the certificate.
|
|
269
268
|
|
|
270
269
|
#### Example
|
|
271
270
|
|
|
@@ -332,14 +331,14 @@ The `bodyhash` command computes the body hash value of an email message, which i
|
|
|
332
331
|
mailauth bodyhash [options] [email]
|
|
333
332
|
```
|
|
334
333
|
|
|
335
|
-
-
|
|
334
|
+
- **email**: (Optional) Path to the EML-formatted email message file. If omitted, the email is read from standard input.
|
|
336
335
|
|
|
337
336
|
#### Options
|
|
338
337
|
|
|
339
|
-
-
|
|
340
|
-
-
|
|
341
|
-
-
|
|
342
|
-
-
|
|
338
|
+
- `--algo algorithm`, `-a algorithm`: Hashing algorithm (e.g., `sha256`). Defaults to `sha256`. Can also specify DKIM-style algorithms (e.g., `rsa-sha256`).
|
|
339
|
+
- `--canonicalization method`, `-c method`: Body canonicalization method (e.g., `relaxed`). Defaults to `relaxed`. Can use DKIM-style (e.g., `relaxed/relaxed`).
|
|
340
|
+
- `--body-length length`, `-l length`: Maximum length of the body to hash (`l=` tag).
|
|
341
|
+
- `--verbose`, `-v`: Enables verbose output.
|
|
343
342
|
|
|
344
343
|
#### Example
|
|
345
344
|
|
|
@@ -390,8 +389,8 @@ The `--dns-cache` option allows you to use a JSON-formatted DNS cache file for t
|
|
|
390
389
|
|
|
391
390
|
The DNS cache file is a JSON object where:
|
|
392
391
|
|
|
393
|
-
-
|
|
394
|
-
-
|
|
392
|
+
- **Keys**: Fully qualified domain names (e.g., `"example.com"`).
|
|
393
|
+
- **Values**: Objects with DNS record types as keys (e.g., `"TXT"`, `"MX"`) and their corresponding values.
|
|
395
394
|
|
|
396
395
|
**Example:**
|
|
397
396
|
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const prettierConfig = require('eslint-config-prettier');
|
|
2
|
+
|
|
3
|
+
module.exports = [
|
|
4
|
+
{
|
|
5
|
+
ignores: ['node_modules/**', 'ee-dist/**', 'test/fixtures/**', 'examples/devel-*']
|
|
6
|
+
},
|
|
7
|
+
prettierConfig,
|
|
8
|
+
{
|
|
9
|
+
languageOptions: {
|
|
10
|
+
ecmaVersion: 2020,
|
|
11
|
+
sourceType: 'commonjs',
|
|
12
|
+
globals: {
|
|
13
|
+
BigInt: true,
|
|
14
|
+
console: 'readonly',
|
|
15
|
+
process: 'readonly',
|
|
16
|
+
Buffer: 'readonly',
|
|
17
|
+
__dirname: 'readonly',
|
|
18
|
+
__filename: 'readonly',
|
|
19
|
+
exports: 'writable',
|
|
20
|
+
module: 'writable',
|
|
21
|
+
require: 'readonly',
|
|
22
|
+
setTimeout: 'readonly',
|
|
23
|
+
setInterval: 'readonly',
|
|
24
|
+
clearTimeout: 'readonly',
|
|
25
|
+
clearInterval: 'readonly',
|
|
26
|
+
setImmediate: 'readonly',
|
|
27
|
+
clearImmediate: 'readonly'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
rules: {
|
|
31
|
+
'no-await-in-loop': 0,
|
|
32
|
+
'require-atomic-updates': 0,
|
|
33
|
+
'no-unused-vars': ['error', {
|
|
34
|
+
argsIgnorePattern: '^_',
|
|
35
|
+
caughtErrors: 'none'
|
|
36
|
+
}],
|
|
37
|
+
'no-console': 0
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
];
|
package/lib/spf/index.js
CHANGED
|
@@ -19,7 +19,85 @@ const formatHeaders = result => {
|
|
|
19
19
|
return libmime.foldLines(header, 160);
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Dual-stack DNS resolver for SPF A/AAAA mechanism queries
|
|
24
|
+
*
|
|
25
|
+
* When evaluating A or AAAA mechanisms, both record types should be considered
|
|
26
|
+
* to determine if a lookup is "void" (empty). This prevents incorrectly counting
|
|
27
|
+
* IPv4-only or IPv6-only hosts as void lookups.
|
|
28
|
+
*
|
|
29
|
+
* Behavior:
|
|
30
|
+
* - Queries both A and AAAA records in parallel (optimization)
|
|
31
|
+
* - Only counts as void if BOTH A and AAAA return ENOTFOUND/ENODATA
|
|
32
|
+
* - Real errors (ETIMEOUT, EREFUSED) for the client's IP type are propagated
|
|
33
|
+
* - Returns only the records matching the client's IP type (IPv4 → A, IPv6 → AAAA)
|
|
34
|
+
*
|
|
35
|
+
* Example: IPv6 client checking an IPv4-only host
|
|
36
|
+
* - A query returns: 192.0.2.1
|
|
37
|
+
* - AAAA query returns: ENODATA (empty)
|
|
38
|
+
* - Result: Returns empty AAAA array (no match), but does NOT count as void
|
|
39
|
+
*
|
|
40
|
+
* @param {Function} resolver - Base DNS resolver function
|
|
41
|
+
* @param {String} domain - Domain to query
|
|
42
|
+
* @param {Object} opts - Options object with clientIpType (4 or 6)
|
|
43
|
+
* @returns {Promise<Array>} - Array of IP addresses matching client type
|
|
44
|
+
* @throws {Error} - Throws on real DNS errors or when both A and AAAA are void
|
|
45
|
+
*/
|
|
46
|
+
let dualStackResolver = async (resolver, domain, opts) => {
|
|
47
|
+
const isIPv6 = opts.clientIpType === 6;
|
|
48
|
+
|
|
49
|
+
// Query both A and AAAA records in parallel for efficiency
|
|
50
|
+
const [aResult, aaaaResult] = await Promise.allSettled([resolver(domain, 'A'), resolver(domain, 'AAAA')]);
|
|
51
|
+
|
|
52
|
+
// Extract successful records and error details
|
|
53
|
+
const aRecords = aResult.status === 'fulfilled' ? aResult.value : [];
|
|
54
|
+
const aError = aResult.status === 'rejected' ? aResult.reason : null;
|
|
55
|
+
|
|
56
|
+
const aaaaRecords = aaaaResult.status === 'fulfilled' ? aaaaResult.value : [];
|
|
57
|
+
const aaaaError = aaaaResult.status === 'rejected' ? aaaaResult.reason : null;
|
|
58
|
+
|
|
59
|
+
// Classify errors: void (no records exist) vs real (DNS server error)
|
|
60
|
+
// Void errors: ENOTFOUND (no such domain), ENODATA (domain exists but no records)
|
|
61
|
+
const aIsVoid = aError && (aError.code === 'ENOTFOUND' || aError.code === 'ENODATA');
|
|
62
|
+
const aaaaIsVoid = aaaaError && (aaaaError.code === 'ENOTFOUND' || aaaaError.code === 'ENODATA');
|
|
63
|
+
|
|
64
|
+
// Propagate real DNS errors for the record type matching the client's IP family
|
|
65
|
+
// IPv6 client: throw AAAA errors (except void), ignore A errors
|
|
66
|
+
if (isIPv6 && aaaaError && !aaaaIsVoid) {
|
|
67
|
+
throw aaaaError;
|
|
68
|
+
}
|
|
69
|
+
// IPv4 client: throw A errors (except void), ignore AAAA errors
|
|
70
|
+
if (!isIPv6 && aError && !aIsVoid) {
|
|
71
|
+
throw aError;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Only throw void error if BOTH record types are void
|
|
75
|
+
// This prevents single-stack hosts from being counted as void lookups
|
|
76
|
+
if (aIsVoid && aaaaIsVoid) {
|
|
77
|
+
// Prefer the error matching client IP type for better error messages
|
|
78
|
+
let voidError = isIPv6 ? aaaaError || aError : aError || aaaaError;
|
|
79
|
+
throw voidError;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Return only the records matching the client's IP type
|
|
83
|
+
// Empty arrays are valid (host exists but doesn't match client IP type)
|
|
84
|
+
return isIPv6 ? aaaaRecords : aRecords;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates a rate-limited DNS resolver with SPF-specific constraints
|
|
89
|
+
*
|
|
90
|
+
* SPF evaluation must enforce limits to prevent DoS:
|
|
91
|
+
* - Maximum 10 DNS lookups per SPF check (mechanisms that trigger DNS: a, mx, ptr, exists, include, redirect)
|
|
92
|
+
* - Maximum 2 "void" lookups (queries returning no records)
|
|
93
|
+
* Mailauth allows to configure both if different limits are required.
|
|
94
|
+
*
|
|
95
|
+
* @param {Function} resolver - Base DNS resolver function (e.g., dns.promises.resolve)
|
|
96
|
+
* @param {Number} maxResolveCount - Maximum DNS lookups allowed (default: 10)
|
|
97
|
+
* @param {Number} maxVoidCount - Maximum void lookups allowed (default: 2)
|
|
98
|
+
* @param {Boolean} ignoreFirst - If true, don't count the first DNS lookup (used for initial TXT record fetch)
|
|
99
|
+
* @returns {Function} - Rate-limited resolver function with signature: (domain, type, opts) => Promise<Array>
|
|
100
|
+
*/
|
|
23
101
|
let limitedResolver = (resolver, maxResolveCount, maxVoidCount, ignoreFirst) => {
|
|
24
102
|
let resolveCount = 0;
|
|
25
103
|
let voidCount = 0;
|
|
@@ -30,15 +108,16 @@ let limitedResolver = (resolver, maxResolveCount, maxVoidCount, ignoreFirst) =>
|
|
|
30
108
|
maxResolveCount = maxResolveCount || MAX_RESOLVE_COUNT;
|
|
31
109
|
maxVoidCount = maxVoidCount || MAX_VOID_COUNT;
|
|
32
110
|
|
|
33
|
-
let resolverFunc = async (domain, type) => {
|
|
34
|
-
//
|
|
35
|
-
|
|
111
|
+
let resolverFunc = async (domain, type, opts) => {
|
|
112
|
+
// Increment DNS lookup counter
|
|
113
|
+
// Note: Dual-stack queries (A+AAAA) still count as 1 lookup
|
|
36
114
|
if (firstCounted) {
|
|
37
115
|
resolveCount++;
|
|
38
116
|
} else {
|
|
39
117
|
firstCounted = true;
|
|
40
118
|
}
|
|
41
119
|
|
|
120
|
+
// Enforce maximum DNS lookup limit
|
|
42
121
|
if (resolveCount > maxResolveCount) {
|
|
43
122
|
let error = new Error('Too many DNS requests');
|
|
44
123
|
error.spfResult = {
|
|
@@ -48,8 +127,9 @@ let limitedResolver = (resolver, maxResolveCount, maxVoidCount, ignoreFirst) =>
|
|
|
48
127
|
throw error;
|
|
49
128
|
}
|
|
50
129
|
|
|
130
|
+
// Validate domain name format before querying
|
|
131
|
+
// This is a lenient check to pass test suites and prevent obvious invalid queries
|
|
51
132
|
try {
|
|
52
|
-
// domain check is pretty lax, mostly to pass the test suite
|
|
53
133
|
if (!/^([\x20-\x2D\x2f-\x7e]+\.)+[a-z]+[a-z\-0-9]*$/i.test(domain)) {
|
|
54
134
|
throw new Error('Failed to validate domain');
|
|
55
135
|
}
|
|
@@ -61,13 +141,24 @@ let limitedResolver = (resolver, maxResolveCount, maxVoidCount, ignoreFirst) =>
|
|
|
61
141
|
throw err;
|
|
62
142
|
}
|
|
63
143
|
|
|
144
|
+
// Execute DNS query with dual-stack optimization for A/AAAA queries
|
|
64
145
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
146
|
+
// Use dual-stack resolver when:
|
|
147
|
+
// 1. Query type is A or AAAA (address lookups)
|
|
148
|
+
// 2. Client IP type is provided (4 for IPv4, 6 for IPv6)
|
|
149
|
+
// This prevents single-stack hosts from being counted as void lookups
|
|
150
|
+
if (opts?.clientIpType && (type === 'A' || type === 'AAAA')) {
|
|
151
|
+
return await dualStackResolver(resolver, domain, opts);
|
|
152
|
+
} else {
|
|
153
|
+
// Standard single-query resolution for other record types (TXT, MX, PTR, etc.) and A if no client info provided.
|
|
154
|
+
return await resolver(domain, type);
|
|
155
|
+
}
|
|
67
156
|
} catch (err) {
|
|
68
157
|
switch (err.code) {
|
|
69
|
-
case 'ENOTFOUND':
|
|
158
|
+
case 'ENOTFOUND': // Domain does not exist
|
|
70
159
|
case 'ENODATA': {
|
|
160
|
+
// Domain exists but has no records of this type
|
|
161
|
+
// Increment void lookup counter
|
|
71
162
|
voidCount++;
|
|
72
163
|
if (voidCount > maxVoidCount) {
|
|
73
164
|
err.spfResult = {
|
|
@@ -76,10 +167,12 @@ let limitedResolver = (resolver, maxResolveCount, maxVoidCount, ignoreFirst) =>
|
|
|
76
167
|
};
|
|
77
168
|
throw err;
|
|
78
169
|
}
|
|
170
|
+
// Return empty array to continue SPF evaluation
|
|
79
171
|
return [];
|
|
80
172
|
}
|
|
81
173
|
|
|
82
174
|
case 'ETIMEOUT':
|
|
175
|
+
// DNS server timeout - temporary error
|
|
83
176
|
err.spfResult = {
|
|
84
177
|
error: 'temperror',
|
|
85
178
|
text: 'DNS timeout'
|
|
@@ -87,6 +180,7 @@ let limitedResolver = (resolver, maxResolveCount, maxVoidCount, ignoreFirst) =>
|
|
|
87
180
|
throw err;
|
|
88
181
|
|
|
89
182
|
case 'EREFUSED':
|
|
183
|
+
// DNS server refused query - temporary error
|
|
90
184
|
err.spfResult = {
|
|
91
185
|
error: 'temperror',
|
|
92
186
|
text: `DNS request refused by server when resolving ${domain}`
|
|
@@ -94,6 +188,7 @@ let limitedResolver = (resolver, maxResolveCount, maxVoidCount, ignoreFirst) =>
|
|
|
94
188
|
throw err;
|
|
95
189
|
|
|
96
190
|
default:
|
|
191
|
+
// Unknown error - propagate as-is
|
|
97
192
|
throw err;
|
|
98
193
|
}
|
|
99
194
|
}
|
package/lib/spf/spf-verify.js
CHANGED
|
@@ -336,7 +336,9 @@ const spfVerify = async (domain, opts) => {
|
|
|
336
336
|
// ignore punycode conversion errors
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
|
|
339
|
+
// Query A or AAAA based on client IP type, with dual-stack void optimization
|
|
340
|
+
// Pass clientIpType to enable smart void counting (see dualStackResolver in index.js)
|
|
341
|
+
let responses = await resolver(a, net.isIPv6(opts.ip) ? 'AAAA' : 'A', { clientIpType: net.isIPv6(opts.ip) ? 6 : 4 });
|
|
340
342
|
if (responses) {
|
|
341
343
|
for (let ip of responses) {
|
|
342
344
|
if (matchIp(addr, ip + cidr)) {
|
|
@@ -362,13 +364,18 @@ const spfVerify = async (domain, opts) => {
|
|
|
362
364
|
|
|
363
365
|
let mxList = await resolver(mxDomain, 'MX');
|
|
364
366
|
if (mxList) {
|
|
365
|
-
// MX
|
|
367
|
+
// MX mechanism uses a separate resolver with independent DNS lookup counter
|
|
368
|
+
// This prevents MX A/AAAA lookups from consuming the main query limit
|
|
366
369
|
let subResolver = typeof opts.createSubResolver === 'function' ? opts.createSubResolver() : resolver;
|
|
367
370
|
try {
|
|
368
371
|
mxList = mxList.sort((a, b) => a.priority - b.priority);
|
|
369
372
|
for (let mx of mxList) {
|
|
370
373
|
if (mx.exchange) {
|
|
371
|
-
|
|
374
|
+
// Query A or AAAA for each MX host, with dual-stack void optimization
|
|
375
|
+
// Pass clientIpType to enable smart void counting (see dualStackResolver in index.js)
|
|
376
|
+
let responses = await subResolver(mx.exchange, net.isIPv6(opts.ip) ? 'AAAA' : 'A', {
|
|
377
|
+
clientIpType: net.isIPv6(opts.ip) ? 6 : 4
|
|
378
|
+
});
|
|
372
379
|
if (responses) {
|
|
373
380
|
for (let a of responses) {
|
|
374
381
|
if (matchIp(addr, a + cidr)) {
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailauth",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.10.0",
|
|
4
4
|
"description": "Email authentication library for Node.js",
|
|
5
5
|
"main": "lib/mailauth.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "eslint \"lib/**/*.js\" \"test/**/*.js\" && mocha --recursive \"./test/**/*.js\" --reporter spec",
|
|
8
|
+
"format": "prettier --write .",
|
|
8
9
|
"build-source": "rm -rf node_modules package-lock.json && npm install && npm run licenses && rm -rf node_modules package-lock.json && npm install --production && rm -rf package-lock.json",
|
|
9
10
|
"build-dist": "npx pkg --compress Brotli package.json && rm -rf package-lock.json && npm install && node winconf.js",
|
|
10
11
|
"build-dist-fast": "pkg --debug package.json && npm install && node winconf.js",
|
|
@@ -33,14 +34,15 @@
|
|
|
33
34
|
"homepage": "https://github.com/postalsys/mailauth",
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"chai": "4.4.1",
|
|
36
|
-
"eslint": "
|
|
37
|
+
"eslint": "9.38.0",
|
|
37
38
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
|
-
"eslint-config-prettier": "
|
|
39
|
+
"eslint-config-prettier": "10.1.8",
|
|
39
40
|
"js-yaml": "4.1.0",
|
|
40
|
-
"license-report": "6.8.
|
|
41
|
+
"license-report": "6.8.1",
|
|
41
42
|
"mbox-reader": "1.2.0",
|
|
42
|
-
"mocha": "11.7.
|
|
43
|
-
"
|
|
43
|
+
"mocha": "11.7.4",
|
|
44
|
+
"prettier": "^3.6.2",
|
|
45
|
+
"resedit": "^3.0.0"
|
|
44
46
|
},
|
|
45
47
|
"dependencies": {
|
|
46
48
|
"@postalsys/vmc": "1.1.2",
|
|
@@ -48,9 +50,9 @@
|
|
|
48
50
|
"ipaddr.js": "2.2.0",
|
|
49
51
|
"joi": "18.0.1",
|
|
50
52
|
"libmime": "5.3.7",
|
|
51
|
-
"nodemailer": "7.0.
|
|
53
|
+
"nodemailer": "7.0.10",
|
|
52
54
|
"punycode.js": "2.3.1",
|
|
53
|
-
"tldts": "7.0.
|
|
55
|
+
"tldts": "7.0.17",
|
|
54
56
|
"undici": "7.16.0",
|
|
55
57
|
"yargs": "17.7.2"
|
|
56
58
|
},
|