@blamejs/pki 0.9.7 → 0.9.8
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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/lib/guard-compress.js +36 -1
- package/lib/guard-identifier.js +3 -3
- package/lib/guard-intrinsic.js +1 -1
- package/lib/http-transport.js +1 -1
- package/lib/tls-cert-compress.js +1 -1
- package/lib/vendor/MANIFEST.json +1 -1
- package/lib/vendor/README.md +1 -1
- package/package.json +2 -2
- package/sbom.cdx.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to `@blamejs/pki` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## v0.9.8 — 2026-10-27
|
|
8
|
+
|
|
9
|
+
Require Node 24.22.1, which fixes the Brotli decoder memory corruption in CVE-2026-111497.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- `engines.node` is `>=24.22.1`. On a lower version npm reports the mismatch as EBADENGINE, and refuses the install where engine-strict is set. Node 24.22.1 ships OpenSSL 3.5.9.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- A zstd-compressed input to `pki.tls.decompressCertificate`, or to any toolkit decompression, must be exactly one Zstandard frame, and anything else is refused with `tls/decompress-failed`. The zstd decoder in Node 24.22.1 and later decodes every concatenated frame and skips skippable frames (RFC 8878 sec. 3.1.2). An empty frame or a skippable frame of arbitrary bytes before or after the real one added no output and passed the declared-length check, so one certificate chain had unboundedly many compressed encodings. A skippable frame on its own decoded to empty output.
|
|
18
|
+
|
|
19
|
+
### Security
|
|
20
|
+
|
|
21
|
+
- The supported Node floor moves to 24.22.1. Node 24.22.0 and earlier bundle a Brotli decoder in which malformed input can corrupt process memory, which can crash the process and may allow code execution (CVE-2026-111497). `pki.tls` passes a peer's Brotli-compressed certificate message (RFC 8879, algorithm 2) to that decoder, and the size cap the toolkit applies to the output does not protect against a fault inside the decoder. The fix is in the Node runtime, and updating an npm package does not replace it. Node 26 releases before 26.12.1 carry the same decoder and still satisfy `>=24.22.1`, so on Node 26 run 26.12.1 or later.
|
|
22
|
+
|
|
7
23
|
## v0.9.7 — 2026-10-21
|
|
8
24
|
|
|
9
25
|
Refuse a key whose private key is public wherever it appears, with no option to turn the refusal off.
|
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ Crypto API with its limits on streaming, opaque keys, and algorithm reach.
|
|
|
64
64
|
npm i @blamejs/pki
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
Requires Node.js 24.
|
|
67
|
+
Requires Node.js 24.22.1+ and runs on the shipped runtime, with no build step and
|
|
68
68
|
no transpilation.
|
|
69
69
|
|
|
70
70
|
```js
|
package/lib/guard-compress.js
CHANGED
|
@@ -27,6 +27,37 @@ var _PROBE_COMPRESS = _intrinsic.assign(_intrinsic.create(null), {
|
|
|
27
27
|
zstd: zlib.zstdCompressSync,
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
/** @internal The byte length of the one Zstandard frame at the start of `s` (RFC 8878 sec. 3.1.1),
|
|
31
|
+
* or -1 when `s` does not open with a complete one. A decoder that follows RFC 8878 decodes every
|
|
32
|
+
* concatenated frame and skips skippable frames, so the consumed-length check cannot see an empty
|
|
33
|
+
* frame or a skippable frame next to the real one; this walk is what holds zstd to one frame. Only
|
|
34
|
+
* the frame magic 0xFD2FB528 starts a frame here, so a skippable frame anywhere is refused. */
|
|
35
|
+
var _ZSTD_DID_BYTES = [0, 1, 2, 4];
|
|
36
|
+
var _ZSTD_FCS_BYTES = [0, 2, 4, 8];
|
|
37
|
+
function _zstdFrameLength(s) {
|
|
38
|
+
var n = _sizeOf(s);
|
|
39
|
+
if (n < 6 || s[0] !== 0x28 || s[1] !== 0xb5 || s[2] !== 0x2f || s[3] !== 0xfd) return -1;
|
|
40
|
+
var fhd = s[4];
|
|
41
|
+
if ((fhd & 0x08) !== 0) return -1;
|
|
42
|
+
var single = (fhd & 0x20) !== 0;
|
|
43
|
+
var fcs = _ZSTD_FCS_BYTES[fhd >>> 6];
|
|
44
|
+
if (fcs === 0 && single) fcs = 1;
|
|
45
|
+
var p = 5 + (single ? 0 : 1) + _ZSTD_DID_BYTES[fhd & 0x03] + fcs;
|
|
46
|
+
for (;;) {
|
|
47
|
+
if (p + 3 > n) return -1;
|
|
48
|
+
var h = s[p] | (s[p + 1] << 8) | (s[p + 2] << 16);
|
|
49
|
+
var type = (h >>> 1) & 0x03;
|
|
50
|
+
if (type === 3) return -1;
|
|
51
|
+
p += 3 + (type === 1 ? 1 : (h >>> 3));
|
|
52
|
+
if (p > n) return -1;
|
|
53
|
+
if ((h & 0x01) !== 0) break;
|
|
54
|
+
}
|
|
55
|
+
if ((fhd & 0x04) !== 0) p += 4;
|
|
56
|
+
return p > n ? -1 : p;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
var FRAME_LENGTH = _intrinsic.assign(_intrinsic.create(null), { zstd: _zstdFrameLength });
|
|
60
|
+
|
|
30
61
|
function _reportsTruncation(name) {
|
|
31
62
|
var compress = _PROBE_COMPRESS[name];
|
|
32
63
|
var decompress = DECOMPRESS[name];
|
|
@@ -51,7 +82,7 @@ var SAFE = _intrinsic.create(null);
|
|
|
51
82
|
_intrinsic.forEach(_objectKeys(DECOMPRESS), function (n) { if (_reportsTruncation(n)) SAFE[n] = true; });
|
|
52
83
|
|
|
53
84
|
// @enforced-by guard-shape-reinlined
|
|
54
|
-
// @guard-shape \bzlib\.(inflate|brotliDecompress|zstdDecompress)Sync
|
|
85
|
+
// @guard-shape \bzlib\.(?:inflate|inflateRaw|gunzip|unzip|brotliDecompress|zstdDecompress|createInflate|createInflateRaw|createGunzip|createUnzip|createBrotliDecompress|createZstdDecompress)(?:Sync)?\b
|
|
55
86
|
// @guard-via \bguard\.compress\.bounded\s*\(
|
|
56
87
|
function bounded(algorithm, stream, cap, E, codes, label) {
|
|
57
88
|
var decompress = DECOMPRESS[algorithm];
|
|
@@ -65,6 +96,10 @@ function bounded(algorithm, stream, cap, E, codes, label) {
|
|
|
65
96
|
throw new TypeError("guard.compress.bounded: cap must be a positive integer");
|
|
66
97
|
}
|
|
67
98
|
var what = label || "the compressed input";
|
|
99
|
+
var frameLength = FRAME_LENGTH[algorithm];
|
|
100
|
+
if (frameLength && frameLength(stream) !== _sizeOf(stream)) {
|
|
101
|
+
throw E(codes.failed, what + " is not exactly one " + algorithm + " frame");
|
|
102
|
+
}
|
|
68
103
|
var res;
|
|
69
104
|
try {
|
|
70
105
|
res = decompress(stream, { maxOutputLength: cap, info: true });
|
package/lib/guard-identifier.js
CHANGED
|
@@ -101,7 +101,7 @@ function assertKnownKeys(obj, known, E, code, message) {
|
|
|
101
101
|
* Which of the two a given name is depends on the function and the engine. A STRICT function, which
|
|
102
102
|
* is every function in an ES module or a `"use strict"` file, owns neither `arguments` nor `caller`,
|
|
103
103
|
* and `Function.prototype` carries both as the restricted accessors, so a chain walk finds them
|
|
104
|
-
* there. On the Node 24.
|
|
104
|
+
* there. On the Node 24.22.1 floor a non-strict function owns both outright. Measured before this
|
|
105
105
|
* covered the inherited side: a plain strict callable descriptor was refused for `arguments` by
|
|
106
106
|
* `pki.cms.sign` and `pki.cms.countersign`, while the minted floor shape passed, which is the shape
|
|
107
107
|
* the vectors were written on.
|
|
@@ -372,7 +372,7 @@ function _kindMembers(v) {
|
|
|
372
372
|
/** @internal The names a callable carries before a caller assigns a field, asked of the engine
|
|
373
373
|
* rather than listed here: a function's own names, plus `Function.prototype`'s, which is where the
|
|
374
374
|
* restricted `arguments` and `caller` accessors live. Which of them a given function makes OWN
|
|
375
|
-
* depends on the engine. Measured: on Node 24.
|
|
375
|
+
* depends on the engine. Measured: on Node 24.22.1, the version `engines.node` floors at, every
|
|
376
376
|
* non-strict function carries own `arguments` and `caller`; on 26.9.0 none does, for a declaration
|
|
377
377
|
* and for `new Function` alike. A list written against one of those refuses a legitimate callable
|
|
378
378
|
* descriptor on the other, and a caller's non-strict CommonJS module is where the shape comes
|
|
@@ -418,7 +418,7 @@ function isOwnMachineryName(v, k) {
|
|
|
418
418
|
if (v === null || v === undefined) return false;
|
|
419
419
|
var d = _getOwnPropertyDescriptor(v, k);
|
|
420
420
|
/** @internal A DATA property, because that is what the machinery is: measured on both Node
|
|
421
|
-
* 24.
|
|
421
|
+
* 24.22.1 and 26.9.0, every one of these names a function owns is a non-enumerable data
|
|
422
422
|
* property. An accessor a caller installed under one of those names is therefore theirs, and it
|
|
423
423
|
* reaches the unknown-field refusal by its name rather than being exempted and ignored. Asking
|
|
424
424
|
* the descriptor rather than reading the property leaves the getter uninvoked either way. */
|
package/lib/guard-intrinsic.js
CHANGED
|
@@ -267,7 +267,7 @@ var _strSliceOwn = uncurry(String.prototype.slice);
|
|
|
267
267
|
* `String.prototype.split` is specified to look the separator's `Symbol.split` method up and call
|
|
268
268
|
* it (ES2015 21.1.3.19 step 2), so the separator's prototype chain is part of the operation and a
|
|
269
269
|
* capture of `split` does not close it. Hard rule 11 bans `split` in `lib/` for exactly that
|
|
270
|
-
* reason. MEASURED on Node 24.
|
|
270
|
+
* reason. MEASURED on Node 24.22.1, the supported runtime: a captured split with a string separator
|
|
271
271
|
* DOES dispatch to an installed `String.prototype[Symbol.split]`, and that made
|
|
272
272
|
* `isBlockedIp("127.0.0.1")` answer false, so a request to loopback proceeded under
|
|
273
273
|
* `blockPrivateAddresses`. Node 26.9 fast-paths a primitive-string separator past the lookup, which
|
package/lib/http-transport.js
CHANGED
|
@@ -581,7 +581,7 @@ function _isBlockedIp(ip) {
|
|
|
581
581
|
/** @internal The literal is scanned on the delimiter CHARACTER rather than split on a separator
|
|
582
582
|
* string. `String.prototype.split` is specified to look the separator's `Symbol.split` method up
|
|
583
583
|
* and call it, so the separator's prototype chain is part of the operation and capturing `split`
|
|
584
|
-
* does not close it; hard rule 11 bans `split` in lib/ for that reason. MEASURED on Node 24.
|
|
584
|
+
* does not close it; hard rule 11 bans `split` in lib/ for that reason. MEASURED on Node 24.22.1,
|
|
585
585
|
* the supported runtime: a hook answering with a public address's octets made this refusal clear
|
|
586
586
|
* for `127.0.0.1` and a request to loopback proceeded. */
|
|
587
587
|
var o = intrinsic.splitChar(ip, 46), a = +o[0], b = +o[1], c = +o[2];
|
package/lib/tls-cert-compress.js
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
* all rather than faulting is dropped, because a runtime that accepts a cut
|
|
41
41
|
* frame lets a peer strip a frame's tail and have the receiver read a prefix as
|
|
42
42
|
* the whole message. A dropped algorithm is then neither compressed to nor
|
|
43
|
-
* accepted. At the Node 24.
|
|
43
|
+
* accepted. At the Node 24.22.1 engine floor all three qualify; on a runtime
|
|
44
44
|
* where one does not, it is dropped.
|
|
45
45
|
*
|
|
46
46
|
* @card
|
package/lib/vendor/MANIFEST.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "Vendored dependencies: none currently. @blamejs/pki's cryptography runs entirely on Node's built-in node:crypto: the classical algorithm set (RSA, ECDSA, EdDSA, ECDH, AES, HMAC, HKDF, PBKDF2, the SHA family) AND the FIPS 203/204/205 post-quantum algorithms (ML-KEM, ML-DSA, SLH-DSA) via the platform OpenSSL 3.5 that the Node engine floor (>=24.
|
|
2
|
+
"_comment": "Vendored dependencies: none currently. @blamejs/pki's cryptography runs entirely on Node's built-in node:crypto: the classical algorithm set (RSA, ECDSA, EdDSA, ECDH, AES, HMAC, HKDF, PBKDF2, the SHA family) AND the FIPS 203/204/205 post-quantum algorithms (ML-KEM, ML-DSA, SLH-DSA) via the platform OpenSSL 3.5 that the Node engine floor (>=24.22.1) ships. A built-in ships zero bytes and is OpenSSL-interoperable by construction, so no crypto bundle is vendored. A package is added here ONLY when a specific operation is confirmed missing from the engine floor; see lib/vendor/README.md for the policy.",
|
|
3
3
|
"packages": {}
|
|
4
4
|
}
|
package/lib/vendor/README.md
CHANGED
|
@@ -6,7 +6,7 @@ nothing. This directory holds only the manifest.
|
|
|
6
6
|
## Native-first crypto
|
|
7
7
|
|
|
8
8
|
The toolkit's cryptography runs entirely on Node's built-in `node:crypto`. The
|
|
9
|
-
engine floor (Node `>=24.
|
|
9
|
+
engine floor (Node `>=24.22.1`) links OpenSSL 3.5, which provides:
|
|
10
10
|
|
|
11
11
|
- the full classical set: RSA (PKCS#1 v1.5, PSS, OAEP), ECDSA, EdDSA
|
|
12
12
|
(Ed25519/Ed448), ECDH including X25519 and X448, AES (GCM/CBC/CTR/KW), HMAC,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/pki",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "blamejs contributors",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"owns-its-stack"
|
|
52
52
|
],
|
|
53
53
|
"engines": {
|
|
54
|
-
"node": ">=24.
|
|
54
|
+
"node": ">=24.22.1"
|
|
55
55
|
},
|
|
56
56
|
"files": [
|
|
57
57
|
"index.js",
|
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:28a2dc7f-a34d-48fb-911a-b3c269f6e1bc",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-10-
|
|
8
|
+
"timestamp": "2026-10-27T17:35:33.081Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -15,18 +15,18 @@
|
|
|
15
15
|
{
|
|
16
16
|
"vendor": "npm",
|
|
17
17
|
"name": "cli",
|
|
18
|
-
"version": "11.19.
|
|
18
|
+
"version": "11.19.1"
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.9.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.9.8",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.9.
|
|
25
|
+
"version": "0.9.8",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.9.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.9.8",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.9.
|
|
57
|
+
"ref": "@blamejs/pki@0.9.8",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|