@blamejs/core 0.18.11 → 0.18.13
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 +4 -0
- package/NOTICE +1 -1
- package/lib/db-collection.js +25 -1
- package/lib/network-tls.js +198 -56
- package/lib/vendor/MANIFEST.json +12 -12
- package/lib/vendor/blamejs-pki.cjs +667 -33
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.18.x
|
|
10
10
|
|
|
11
|
+
- v0.18.13 (2026-08-06) — **`b.db.collection` no longer corrupts a BLOB overflow column (its bytes were spread as numeric keys, destroying the JSON payload); broader offline test coverage across db-collection, TLS, external-db residency, notify, and BIMI.** A schemaless collection with a BLOB overflow column read that column back as a byte array. Because a byte array is a JavaScript object, the collection treated it as an already-parsed overflow object and spread its bytes as numeric keys on read, on update, and on insert — silently destroying the stored JSON payload. The overflow value is now decoded as JSON at every one of those points. The release also closes offline test-coverage gaps and adds an explicit test that the external-database residency boundary is enforced. **Changed:** *Broader offline test coverage and an explicit external-database residency-enforcement test* — b.db.collection (BLOB overflow round-trips, introspection-error wrapping, overflow-as-jsonColumn), b.network.tls.connectWithEch (the connect success, timeout, unreachable-peer, and synchronous-throw paths driven against local servers), b.externalDb (the boot-time data-residency check now has an explicit test that a personal-data backend outside the declared region is refused and compliant backends are admitted), b.notify (the default HTTP client is bound at send time), and b.mail.bimi (a certificate chain deeper than the maximum is rejected) now have tests for these boundary paths. **Fixed:** *b.db.collection no longer corrupts a BLOB overflow column* — A collection configured with an overflow column (unknown fields folded into a JSON column) reads that column back as a byte array when the column is declared BLOB. A byte array's typeof is "object", so the collection took it to be an already-parsed overflow object and spread its raw bytes as numeric-index keys — on read (surfacing { "0": …, "1": … } onto the row), on a $set/$unset update (rewriting the column to the byte spread and destroying the original keys), and on an insert that supplied a Buffer overflow value. The overflow value is now decoded from its bytes as JSON at all three points, so the stored payload survives; a BLOB holding non-JSON bytes falls back to an empty object instead of corrupting, matching the existing non-JSON-string behaviour.
|
|
12
|
+
|
|
13
|
+
- v0.18.12 (2026-08-05) — **Certificate Transparency SCT verification now works for real certificates (it previously rejected every embedded SCT); b.network.tls.ct.verifyScts / requireScts now require the issuing CA; broader Redis-client and TLS-verification test coverage.** b.network.tls.ct.verifyScts reconstructed the wrong RFC 6962 signed input for the SCTs embedded in a certificate — it built an x509_entry over the bare TBSCertificate instead of the precert_entry (issuer_key_hash + TBSCertificate) the log actually signed, and separately read the SCT's signature algorithm from a field the parser does not emit. Either alone made verification impossible: verifyScts and requireScts rejected every real embedded SCT. Verification now builds the RFC 6962 §3.2 precert_entry and requires the issuing CA so it can bind issuer_key_hash. This is a fail-closed fix — nothing invalid was ever trusted — but SCT enforcement now needs the issuer supplied. **Changed:** *b.network.tls.ct.verifyScts and requireScts now require the issuing CA* — Because an embedded SCT binds the issuing CA's key, verifying one needs that key. Pass the issuer to verifyScts as issuerCertDer (the issuer certificate DER), issuerSpkiDer (its SubjectPublicKeyInfo DER), or issuerKeyHash (the precomputed 32-byte SHA-256). Without it, verifyScts returns { ok: false, reason: "issuer-key-required" } and does not verify. requireScts derives the issuer from the peer's certificate chain automatically and accepts the same options; supply one explicitly only when the issuer is not present in the chain. verifyInclusion takes the same issuer options for embedded (precert_entry) leaves. · *Vendored @blamejs/pki refreshed to 0.3.32* — The vendored @blamejs/pki bundle behind the mutual-TLS CA engine is refreshed to 0.3.32, which adds C509 (CBOR-encoded certificate) extended-key-usage and extension registries (RFC 7633 TLS Feature, RFC 4556 PKINIT, and SSH/CMC/Bundle-Security purposes). No blamejs public API changes. · *Error- and edge-path tests for the Redis client and TLS-verification primitives* — b.redisClient (RESP framing and protocol-error edges, split replies, AUTH / SELECT on connect, push-message demux, connect/command timeouts, socket-error and close teardown) and b.network.tls (OCSP and Certificate-Transparency parse and verification branches, ECH config framing, and PKIX host/SAN matching) now have explicit tests for their boundary branches, driven through local fake servers and crafted certificates. **Fixed:** *Certificate Transparency SCT verification now works for certificate-embedded SCTs* — For an SCT embedded in a certificate's X.509v3 extension, the log computed its signature over the RFC 6962 §3.2 precert_entry — issuer_key_hash (SHA-256 of the issuing CA's SubjectPublicKeyInfo) followed by the TBSCertificate with the SCT extension removed. b.network.tls.ct.verifyScts instead reconstructed an x509_entry over just the stripped TBSCertificate, with no issuer binding, so the reconstructed input never matched what the log signed and no real embedded SCT could verify. It additionally read the SCT's signature-algorithm from a field name the parser does not emit, so the algorithm cross-check failed before the signature was even reached. verifyScts now builds the precert_entry and reads the parsed algorithm field, so a valid SCT verifies and a genuine mismatch is still refused. b.network.tls.ct.verifyInclusion builds the RFC 9162 §4.6 leaf the same way. The prior behaviour failed closed (it rejected rather than accepted), so no invalid certificate was ever trusted. **Security:** *SCT verification now binds each log key to the SCT's log ID* — An SCT's log ID is the SHA-256 of the log's public key (RFC 6962 §3.2). b.network.tls.ct.verifyScts now recomputes that hash from the configured key and refuses the SCT (reason "log-id-key-mismatch") when it does not equal the log ID the SCT names, before checking the signature. This prevents a misconfigured logKeys map from letting a key impersonate an approved log. · *SCT verification counts distinct logs, refuses future timestamps, and validates the minimum* — verifyScts now counts SCTs from distinct logs, so duplicate SCTs from a single log can no longer satisfy a "minimum 2 SCTs" diversity policy (verifiedCount reflects distinct logs). It refuses an SCT whose timestamp is in the future (RFC 6962 §5.2; opts.now / opts.clockSkewMs override the clock and the default five-minute skew). A minScts that is not a positive integer is rejected at the entry point rather than silently accepting an unverified certificate. Parsing an SCT extension from a peer certificate now fails closed on malformed DER — it returns no SCT instead of throwing, so a hostile certificate cannot raise an uncaught error out of verifyScts, parseScts, or the requireScts predicate.
|
|
14
|
+
|
|
11
15
|
- v0.18.11 (2026-08-03) — **A static file that is a symlink at the final path component now fails closed with a clean 404 instead of an uncaught error; broader DNS, field-crypto, and static-server coverage.** The static server refuses a symlink at the final path component (its O_NOFOLLOW TOCTOU defense) — but that refusal now falls back to a clean 404 / NOT_FOUND rather than propagating an uncaught error that hung the request or surfaced a raw ELOOP from staticServe.integrity(). Alongside the fix, error and edge branches of b.network.dns, b.cryptoField, and b.staticServe come under explicit test. **Changed:** *Error- and edge-path tests for the DNS, field-crypto, and static-server primitives* — b.network.dns (DoH oversized-body handling, DoT mid-query connection reset and socket reuse, cache expiry, system-resolver error paths), b.cryptoField (tampered / relocated / downgraded cell fail-closed nulling, key-row AAD and rowId resolution, unseal rate-cap configuration, on-read upgrade branches), and b.staticServe (integrity and confinement edge cases, idle-timeout teardown, the final-component symlink refusal above) now have explicit tests for their boundary branches. An unused internal date-formatting helper was removed. **Fixed:** *A final-component symlink (or a mid-hash read error) on a static file fails closed with a clean 404* — When computing a served file's SRI and ETag, the static server opens it with O_NOFOLLOW so a symlink swapped in at the final path component after the in-root check cannot be followed (a TOCTOU / CWE-22 / CWE-367 defense). On POSIX that open raises ELOOP for a symlinked target — the common atomic-deploy pattern where a stable name links to a hashed file (latest.js pointing at app.<hash>.js). That error, and any read error mid-hash, previously propagated uncaught: the middleware request hung with no response, and staticServe.integrity() threw a raw ELOOP instead of its documented NOT_FOUND. It now falls back exactly as a read error does — the middleware returns 404 (falls through) and integrity() throws NOT_FOUND. The symlink is still not served; the refusal is just clean. Windows is unaffected (O_NOFOLLOW is a no-op there).
|
|
12
16
|
|
|
13
17
|
- v0.18.10 (2026-08-03) — **Encrypted-at-rest boot fails closed on a truncated db.enc instead of silently starting fresh over it; broader SAML and database test coverage.** A database running encrypted at rest whose db.enc snapshot is truncated below a valid envelope (an interrupted external copy or backup restore, a full tmpfs, filesystem corruption) no longer boots onto a fresh empty database that the next flush would persist over the corrupt copy — it now fails closed so the operator can restore from backup. Alongside the fix, adversarial and edge branches of b.auth.saml and b.db come under explicit test, and the vendored @blamejs/pki toolkit is refreshed. **Changed:** *Verification- and edge-path tests for the SAML SP and database primitives* — b.auth.saml (the XML-signature Reference handling with no URI or no Transforms, a foreign-namespace Conditions element failing closed to no-audience-restriction, RSA-OAEP SHA-384/SHA-512 key transport with AES-128-GCM assertion decryption, ed25519 sign/verify dispatch, and the single-logout build/parse paths) and b.db (encrypted-boot key and envelope fallbacks, the truncated-snapshot refusal above, flush-to-disk after close, the audit-chain rollback check against a non-empty log, and the boot-time NTP check thresholds) now have explicit tests for their boundary branches. · *Vendored @blamejs/pki refreshed to 0.3.29* — The vendored @blamejs/pki toolkit that backs the mutual-TLS CA engine (X.509 / CRL / PKCS#12 signing) is refreshed from 0.3.28 to 0.3.29. The upstream change is in that toolkit's ACME client and Retry-After parser, which the CA engine does not call, so there is no behaviour change to the certificate-authority surface — this is a supply-chain currency refresh. **Fixed:** *Encrypted boot fails closed on a truncated db.enc rather than starting fresh over it* — When a database is encrypted at rest and its durable db.enc snapshot exists but is too short to be a valid AEAD envelope — a truncation from an interrupted external copy or backup restore, a full tmpfs, or filesystem corruption — boot silently opened a fresh empty database. The next flush or close then persisted that empty database over the corrupt-but-present snapshot, masking the corruption and destroying the operator's chance to recover. Boot now fails closed with db/enc-truncated; restore db.enc from backup, or remove it to intentionally start empty. A valid snapshot is always written atomically and is never this short, so no healthy deployment is affected. **Detectors:** *Truncated db.enc must fail closed, not start fresh* — A source guard keeps the encrypted-boot path throwing on an under-length db.enc rather than returning to open a fresh database — so a truncated durable snapshot can never again be silently overwritten by the next flush.
|
package/NOTICE
CHANGED
|
@@ -60,7 +60,7 @@ Used for: WebAuthn / passkey registration + authentication response
|
|
|
60
60
|
server.cjs).
|
|
61
61
|
--------------------------------------------------------------------------------
|
|
62
62
|
Component: @blamejs/pki
|
|
63
|
-
Version: 0.3.
|
|
63
|
+
Version: 0.3.32
|
|
64
64
|
Source: https://github.com/blamejs/pki
|
|
65
65
|
License: Apache-2.0
|
|
66
66
|
Copyright: Copyright (c) blamejs contributors
|
package/lib/db-collection.js
CHANGED
|
@@ -216,6 +216,22 @@ function _splitUpdateOperators(update) {
|
|
|
216
216
|
* users.find({ dept: "eng" });
|
|
217
217
|
* // → JSON_EXTRACT(data, '$.dept') = 'eng'
|
|
218
218
|
*/
|
|
219
|
+
|
|
220
|
+
// Decode a BLOB overflow value as JSON. A BLOB column reads back as a
|
|
221
|
+
// Uint8Array/Buffer (typeof "object"); its bytes are JSON text and must be
|
|
222
|
+
// parsed — Object.assign over the raw view would spread the bytes as numeric
|
|
223
|
+
// keys and destroy the payload. Returns the parsed plain object, or null when
|
|
224
|
+
// the bytes are not a JSON object (unparseable / a non-object / an array) —
|
|
225
|
+
// mirroring the string branches: the read path leaves an un-decodable value
|
|
226
|
+
// on the row as-is, write/update coalesce a null to {}. Callers reach this
|
|
227
|
+
// only after an ArrayBuffer.isView guard.
|
|
228
|
+
function _blobToObject(view) {
|
|
229
|
+
try {
|
|
230
|
+
var parsed = safeJson.parse(Buffer.from(view.buffer, view.byteOffset, view.byteLength).toString("utf8"));
|
|
231
|
+
return (parsed && typeof parsed === "object" && !Array.isArray(parsed)) ? parsed : null;
|
|
232
|
+
} catch (_e) { return null; }
|
|
233
|
+
}
|
|
234
|
+
|
|
219
235
|
function collection(name, opts) {
|
|
220
236
|
validateOpts.requireNonEmptyString(name, "collection(name): name", TypeError, "db-collection/bad-name");
|
|
221
237
|
opts = opts || {};
|
|
@@ -297,10 +313,14 @@ function collection(name, opts) {
|
|
|
297
313
|
});
|
|
298
314
|
if (hasExtras) {
|
|
299
315
|
var existing = null;
|
|
300
|
-
if (
|
|
316
|
+
if (ArrayBuffer.isView(writeDoc[overflow])) { // BLOB/Buffer overflow value -> decode as JSON, don't spread bytes
|
|
317
|
+
existing = _blobToObject(writeDoc[overflow]);
|
|
318
|
+
} else if (typeof writeDoc[overflow] === "object" && writeDoc[overflow] !== null && !Array.isArray(writeDoc[overflow])) {
|
|
301
319
|
existing = writeDoc[overflow];
|
|
302
320
|
}
|
|
303
321
|
writeDoc[overflow] = JSON.stringify(Object.assign({}, existing || {}, extras));
|
|
322
|
+
} else if (writeDoc[overflow] !== undefined && writeDoc[overflow] !== null && ArrayBuffer.isView(writeDoc[overflow])) {
|
|
323
|
+
writeDoc[overflow] = JSON.stringify(_blobToObject(writeDoc[overflow]) || {});
|
|
304
324
|
} else if (writeDoc[overflow] !== undefined && writeDoc[overflow] !== null && typeof writeDoc[overflow] === "object") {
|
|
305
325
|
writeDoc[overflow] = JSON.stringify(writeDoc[overflow]);
|
|
306
326
|
}
|
|
@@ -321,6 +341,8 @@ function collection(name, opts) {
|
|
|
321
341
|
var extra = null;
|
|
322
342
|
if (typeof out[overflow] === "string" && out[overflow].length > 0) {
|
|
323
343
|
try { extra = safeJson.parse(out[overflow]); } catch (_e) { extra = null; }
|
|
344
|
+
} else if (ArrayBuffer.isView(out[overflow])) { // BLOB/Buffer overflow -> decode JSON, don't spread bytes onto the row
|
|
345
|
+
extra = _blobToObject(out[overflow]);
|
|
324
346
|
} else if (typeof out[overflow] === "object" && !Array.isArray(out[overflow])) {
|
|
325
347
|
extra = out[overflow];
|
|
326
348
|
}
|
|
@@ -537,6 +559,8 @@ function collection(name, opts) {
|
|
|
537
559
|
if (rows[ri][overflow] !== undefined && rows[ri][overflow] !== null) {
|
|
538
560
|
if (typeof rows[ri][overflow] === "string" && rows[ri][overflow].length > 0) {
|
|
539
561
|
try { existing = safeJson.parse(rows[ri][overflow]) || {}; } catch (_e) { existing = {}; }
|
|
562
|
+
} else if (ArrayBuffer.isView(rows[ri][overflow])) { // BLOB/Buffer overflow -> decode JSON before the read-modify-write
|
|
563
|
+
existing = _blobToObject(rows[ri][overflow]) || {};
|
|
540
564
|
} else if (typeof rows[ri][overflow] === "object") {
|
|
541
565
|
existing = rows[ri][overflow] || {};
|
|
542
566
|
}
|
package/lib/network-tls.js
CHANGED
|
@@ -1710,36 +1710,40 @@ function _extractSctExtensionFromCert(certDer) {
|
|
|
1710
1710
|
var tbsChildren;
|
|
1711
1711
|
try { tbsChildren = asn1.readSequence(tbs.value); }
|
|
1712
1712
|
catch (_e) { return { sctListRaw: null }; }
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1713
|
+
// The extensions walk is fully guarded: a hostile peer controls this cert,
|
|
1714
|
+
// so any malformed node fails closed to "no SCT extension" (the caller then
|
|
1715
|
+
// refuses) rather than throwing out of verifyScts / parseScts / requireScts.
|
|
1716
|
+
try {
|
|
1717
|
+
var extensionsNode = null;
|
|
1718
|
+
for (var i = 0; i < tbsChildren.length; i += 1) {
|
|
1719
|
+
var ch = tbsChildren[i];
|
|
1720
|
+
if (ch.tagClass === asn1.TAG_CLASS.CONTEXT_SPECIFIC && ch.tag === 3) { // X.509 [3] EXPLICIT extensions tag
|
|
1721
|
+
extensionsNode = asn1.readNode(ch.value, 0);
|
|
1722
|
+
break;
|
|
1723
|
+
}
|
|
1719
1724
|
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1725
|
+
if (!extensionsNode || extensionsNode.tag !== asn1.TAG.SEQUENCE) {
|
|
1726
|
+
return { sctListRaw: null };
|
|
1727
|
+
}
|
|
1728
|
+
var extensions = asn1.readSequence(extensionsNode.value);
|
|
1729
|
+
for (var e = 0; e < extensions.length; e += 1) {
|
|
1730
|
+
var ext = extensions[e]; // Extension ::= SEQUENCE { extnID OID, critical BOOL OPTIONAL, extnValue OCTET STRING }
|
|
1731
|
+
if (ext.tag !== asn1.TAG.SEQUENCE) continue;
|
|
1732
|
+
var extChildren = asn1.readSequence(ext.value);
|
|
1733
|
+
if (extChildren.length === 0) continue;
|
|
1734
|
+
var extOid = asn1.readOid(extChildren[0]);
|
|
1735
|
+
if (extOid !== OID_CT_SCT_LIST) continue;
|
|
1736
|
+
// The last child is the OCTET STRING extnValue. Per RFC 6962 §3.3
|
|
1737
|
+
// that OCTET STRING wraps a SECOND OCTET STRING which contains the
|
|
1738
|
+
// raw SignedCertificateTimestampList (TLS-encoded).
|
|
1739
|
+
var extnValueOuter = asn1.readOctetString(extChildren[extChildren.length - 1]);
|
|
1740
|
+
var inner = asn1.readNode(extnValueOuter);
|
|
1741
|
+
if (inner.tag !== asn1.TAG.OCTET_STRING) return { sctListRaw: null }; // malformed SCT ext → fail closed
|
|
1742
|
+
return { sctListRaw: inner.value };
|
|
1743
|
+
}
|
|
1744
|
+
} catch (_e) {
|
|
1722
1745
|
return { sctListRaw: null };
|
|
1723
1746
|
}
|
|
1724
|
-
var extensions = asn1.readSequence(extensionsNode.value);
|
|
1725
|
-
for (var e = 0; e < extensions.length; e += 1) {
|
|
1726
|
-
var ext = extensions[e]; // Extension ::= SEQUENCE { extnID OID, critical BOOL OPTIONAL, extnValue OCTET STRING }
|
|
1727
|
-
if (ext.tag !== asn1.TAG.SEQUENCE) continue;
|
|
1728
|
-
var extChildren = asn1.readSequence(ext.value);
|
|
1729
|
-
if (extChildren.length === 0) continue;
|
|
1730
|
-
var extOid = asn1.readOid(extChildren[0]);
|
|
1731
|
-
if (extOid !== OID_CT_SCT_LIST) continue;
|
|
1732
|
-
// The last child is the OCTET STRING extnValue. Per RFC 6962 §3.3
|
|
1733
|
-
// that OCTET STRING wraps a SECOND OCTET STRING which contains the
|
|
1734
|
-
// raw SignedCertificateTimestampList (TLS-encoded).
|
|
1735
|
-
var extnValueOuter = asn1.readOctetString(extChildren[extChildren.length - 1]);
|
|
1736
|
-
var inner = asn1.readNode(extnValueOuter);
|
|
1737
|
-
if (inner.tag !== asn1.TAG.OCTET_STRING) {
|
|
1738
|
-
throw new TlsTrustError("tls/ct-bad-extension",
|
|
1739
|
-
"SCT extension extnValue does not wrap a second OCTET STRING");
|
|
1740
|
-
}
|
|
1741
|
-
return { sctListRaw: inner.value };
|
|
1742
|
-
}
|
|
1743
1747
|
return { sctListRaw: null };
|
|
1744
1748
|
}
|
|
1745
1749
|
|
|
@@ -1887,27 +1891,67 @@ function _parseSct(sctBuf) {
|
|
|
1887
1891
|
};
|
|
1888
1892
|
}
|
|
1889
1893
|
|
|
1890
|
-
//
|
|
1891
|
-
//
|
|
1894
|
+
// Serialize the RFC 6962 §3.2 / RFC 9162 §4.6 entry_type + signed_entry
|
|
1895
|
+
// pair. An SCT embedded in the leaf's X.509v3 extension was signed by the
|
|
1896
|
+
// log over the PRECERTIFICATE, so it is a precert_entry — signed_entry is
|
|
1897
|
+
// PreCert { opaque issuer_key_hash[32]; TBSCertificate tbs_certificate; }
|
|
1898
|
+
// = issuer_key_hash (32 raw bytes) || tbs_certificate (24-bit length ||
|
|
1899
|
+
// DER TBSCertificate with the SCT extension removed). A directly-logged
|
|
1900
|
+
// final certificate is an x509_entry: ASN.1Cert (24-bit length || DER).
|
|
1901
|
+
// A 32-byte issuerKeyHash selects precert_entry; its absence selects
|
|
1902
|
+
// x509_entry. issuer_key_hash is SHA-256 over the DER SubjectPublicKeyInfo
|
|
1903
|
+
// of the issuing CA's public key (RFC 6962 §3.2).
|
|
1904
|
+
function _ctSignedEntry(tbsOrCertDer, issuerKeyHash) {
|
|
1905
|
+
var lenBytes = Buffer.alloc(3); // RFC 6962 24-bit length prefix
|
|
1906
|
+
lenBytes.writeUIntBE(tbsOrCertDer.length, 0, 3);
|
|
1907
|
+
if (Buffer.isBuffer(issuerKeyHash)) {
|
|
1908
|
+
if (issuerKeyHash.length !== 32) { // RFC 6962 opaque issuer_key_hash[32]
|
|
1909
|
+
throw new TlsTrustError("tls/ct-bad-issuer-key-hash",
|
|
1910
|
+
"issuer_key_hash must be 32 bytes (SHA-256 of issuer SubjectPublicKeyInfo), got " +
|
|
1911
|
+
issuerKeyHash.length);
|
|
1912
|
+
}
|
|
1913
|
+
return { entryType: 1, // precert_entry
|
|
1914
|
+
signedEntry: Buffer.concat([issuerKeyHash, lenBytes, tbsOrCertDer]) };
|
|
1915
|
+
}
|
|
1916
|
+
return { entryType: 0, // x509_entry
|
|
1917
|
+
signedEntry: Buffer.concat([lenBytes, tbsOrCertDer]) };
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
// Resolve the RFC 6962 issuer_key_hash from operator-supplied opts, in
|
|
1921
|
+
// precedence order: issuerKeyHash (precomputed 32-byte Buffer) |
|
|
1922
|
+
// issuerSpkiDer (DER SubjectPublicKeyInfo, hashed) | issuerCertDer (issuing
|
|
1923
|
+
// CA cert; its SPKI is extracted and hashed). Returns a 32-byte Buffer, or
|
|
1924
|
+
// null when no issuer material was supplied.
|
|
1925
|
+
function _resolveIssuerKeyHash(opts) {
|
|
1926
|
+
if (Buffer.isBuffer(opts.issuerKeyHash)) return opts.issuerKeyHash;
|
|
1927
|
+
if (Buffer.isBuffer(opts.issuerSpkiDer)) {
|
|
1928
|
+
return nodeCrypto.createHash("sha256").update(opts.issuerSpkiDer).digest(); // RFC 6962 SHA-256 of SPKI
|
|
1929
|
+
}
|
|
1930
|
+
if (Buffer.isBuffer(opts.issuerCertDer)) {
|
|
1931
|
+
var spkiDer = new nodeCrypto.X509Certificate(opts.issuerCertDer)
|
|
1932
|
+
.publicKey.export({ type: "spki", format: "der" });
|
|
1933
|
+
return nodeCrypto.createHash("sha256").update(spkiDer).digest();
|
|
1934
|
+
}
|
|
1935
|
+
return null;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
// Build the canonical signed-entry per RFC 6962 §3.2. For an embedded SCT
|
|
1939
|
+
// (issuerKeyHash present) this is a precert_entry:
|
|
1892
1940
|
// sct_version (1) || signature_type (1=certificate_timestamp) ||
|
|
1893
|
-
// timestamp (8) || entry_type (
|
|
1894
|
-
//
|
|
1895
|
-
//
|
|
1896
|
-
function _buildSctSignedEntry(certWithoutSctDer, sct) {
|
|
1941
|
+
// timestamp (8) || entry_type (1=precert_entry) ||
|
|
1942
|
+
// issuer_key_hash (32) || tbs_certificate (3-byte length || TBS without
|
|
1943
|
+
// the SCT extension) || ct_extensions (2-byte length || N)
|
|
1944
|
+
function _buildSctSignedEntry(certWithoutSctDer, sct, issuerKeyHash) {
|
|
1945
|
+
var entry = _ctSignedEntry(certWithoutSctDer, issuerKeyHash);
|
|
1897
1946
|
var head = Buffer.alloc(1 + 1 + 8 + 2); // fixed-shape header bytes
|
|
1898
1947
|
head[0] = sct.version;
|
|
1899
1948
|
head[1] = 0; // signature_type = certificate_timestamp
|
|
1900
1949
|
head.writeBigUInt64BE(BigInt(sct.timestamp), 2); // past version+sig-type
|
|
1901
|
-
head.writeUInt16BE(
|
|
1902
|
-
// signed_entry: 3-byte length prefix + cert DER.
|
|
1903
|
-
var lenBytes = Buffer.alloc(3); // RFC 6962 24-bit length prefix
|
|
1904
|
-
lenBytes[0] = (certWithoutSctDer.length >> 16) & 0xff; // base-256 length high byte
|
|
1905
|
-
lenBytes[1] = (certWithoutSctDer.length >> 8) & 0xff; // base-256 length mid byte
|
|
1906
|
-
lenBytes[2] = certWithoutSctDer.length & 0xff; // base-256 length low byte
|
|
1950
|
+
head.writeUInt16BE(entry.entryType, 10); // entry_type (precert_entry=1 / x509_entry=0)
|
|
1907
1951
|
// ct_extensions: 2-byte length + bytes.
|
|
1908
1952
|
var extHead = Buffer.alloc(2); // RFC 6962 2-byte ct_extensions length prefix
|
|
1909
1953
|
extHead.writeUInt16BE(sct.extensions.length, 0);
|
|
1910
|
-
return Buffer.concat([head,
|
|
1954
|
+
return Buffer.concat([head, entry.signedEntry, extHead, sct.extensions]);
|
|
1911
1955
|
}
|
|
1912
1956
|
|
|
1913
1957
|
// Strip the SCT extension from a DER cert + return the rebuilt cert
|
|
@@ -2029,6 +2073,15 @@ function _encodeAsn1FromNode(node) {
|
|
|
2029
2073
|
// CT log list (https://www.gstatic.com/ct/log_list/v3/log_list.json
|
|
2030
2074
|
// or equivalent) — log keys rotate, so the framework does NOT bake
|
|
2031
2075
|
// them in; that drift is the operator's to manage.
|
|
2076
|
+
//
|
|
2077
|
+
// An SCT embedded in the certificate was signed by the log over the
|
|
2078
|
+
// PRECERTIFICATE (RFC 6962 §3.2 precert_entry), whose signed-entry binds
|
|
2079
|
+
// the issuing CA via issuer_key_hash = SHA-256(issuer SubjectPublicKeyInfo).
|
|
2080
|
+
// The issuing CA is therefore REQUIRED to verify: supply it as
|
|
2081
|
+
// opts.issuerCertDer (issuer cert DER), opts.issuerSpkiDer (DER
|
|
2082
|
+
// SubjectPublicKeyInfo), or opts.issuerKeyHash (precomputed 32-byte hash).
|
|
2083
|
+
// Without it verification cannot proceed and returns reason
|
|
2084
|
+
// "issuer-key-required" (fails closed).
|
|
2032
2085
|
function verifyScts(certDer, opts) {
|
|
2033
2086
|
opts = opts || {};
|
|
2034
2087
|
if (!Buffer.isBuffer(certDer)) {
|
|
@@ -2036,6 +2089,12 @@ function verifyScts(certDer, opts) {
|
|
|
2036
2089
|
"verifyScts: certDer must be a Buffer");
|
|
2037
2090
|
}
|
|
2038
2091
|
var logKeys = opts.logKeys || {};
|
|
2092
|
+
if (opts.minScts !== undefined &&
|
|
2093
|
+
(typeof opts.minScts !== "number" || !isFinite(opts.minScts) ||
|
|
2094
|
+
opts.minScts < 1 || Math.floor(opts.minScts) !== opts.minScts)) {
|
|
2095
|
+
throw new TlsTrustError("tls/ct-bad-input",
|
|
2096
|
+
"verifyScts: minScts must be a positive integer (a policy of 0 would accept unverified certs)");
|
|
2097
|
+
}
|
|
2039
2098
|
var minScts = typeof opts.minScts === "number" ? opts.minScts : 2; // Chrome CT policy min-2-SCTs
|
|
2040
2099
|
var ext = _extractSctExtensionFromCert(certDer);
|
|
2041
2100
|
if (!ext.sctListRaw) {
|
|
@@ -2047,6 +2106,19 @@ function verifyScts(certDer, opts) {
|
|
|
2047
2106
|
return { ok: false, reason: "parse-error",
|
|
2048
2107
|
error: (e && e.message) || String(e), scts: [] };
|
|
2049
2108
|
}
|
|
2109
|
+
// Resolve the issuing-CA key hash — embedded SCTs are precert_entry and
|
|
2110
|
+
// cannot be verified without it (RFC 6962 §3.2). Fail closed when absent.
|
|
2111
|
+
var issuerKeyHash;
|
|
2112
|
+
try { issuerKeyHash = _resolveIssuerKeyHash(opts); }
|
|
2113
|
+
catch (e) {
|
|
2114
|
+
return { ok: false, reason: "bad-issuer-key",
|
|
2115
|
+
error: (e && e.message) || String(e), scts: scts,
|
|
2116
|
+
minScts: minScts, verifiedCount: 0, totalScts: scts.length };
|
|
2117
|
+
}
|
|
2118
|
+
if (!issuerKeyHash) {
|
|
2119
|
+
return { ok: false, reason: "issuer-key-required", scts: [],
|
|
2120
|
+
minScts: minScts, verifiedCount: 0, totalScts: scts.length };
|
|
2121
|
+
}
|
|
2050
2122
|
// Strip the SCT extension to compute the signed-entry per §3.2.
|
|
2051
2123
|
var stripped;
|
|
2052
2124
|
try { stripped = _stripSctExtensionFromCert(certDer); }
|
|
@@ -2054,10 +2126,24 @@ function verifyScts(certDer, opts) {
|
|
|
2054
2126
|
return { ok: false, reason: "strip-failed",
|
|
2055
2127
|
error: (e && e.message) || String(e), scts: scts };
|
|
2056
2128
|
}
|
|
2057
|
-
|
|
2129
|
+
// Chrome CT policy counts SCTs from DISTINCT logs (diversity), so a single
|
|
2130
|
+
// log — in the trivial case the same SCT duplicated — cannot alone satisfy a
|
|
2131
|
+
// minScts >= 2 gate. Track the set of logs that produced a verified SCT.
|
|
2132
|
+
var verifiedLogIds = Object.create(null);
|
|
2133
|
+
var now = (typeof opts.now === "number" && isFinite(opts.now)) ? opts.now : Date.now();
|
|
2134
|
+
var futureSkewMs = (typeof opts.clockSkewMs === "number" && isFinite(opts.clockSkewMs) &&
|
|
2135
|
+
opts.clockSkewMs >= 0) ? opts.clockSkewMs : C.TIME.minutes(5);
|
|
2058
2136
|
var perSctResults = [];
|
|
2059
2137
|
for (var s = 0; s < scts.length; s += 1) {
|
|
2060
2138
|
var sct = scts[s];
|
|
2139
|
+
// RFC 6962 §5.2 — a future SCT timestamp signals log misbehaviour
|
|
2140
|
+
// (out-of-order issuance or a log clock fault); refuse it, allowing a
|
|
2141
|
+
// small skew for honest clock drift.
|
|
2142
|
+
if (sct.timestamp > now + futureSkewMs) {
|
|
2143
|
+
perSctResults.push({ logIdHex: sct.logIdHex, verified: false,
|
|
2144
|
+
reason: "timestamp-in-future", timestamp: sct.timestamp });
|
|
2145
|
+
continue;
|
|
2146
|
+
}
|
|
2061
2147
|
var pem = logKeys[sct.logIdHex];
|
|
2062
2148
|
if (!pem) {
|
|
2063
2149
|
perSctResults.push({ logIdHex: sct.logIdHex, verified: false,
|
|
@@ -2065,7 +2151,7 @@ function verifyScts(certDer, opts) {
|
|
|
2065
2151
|
continue;
|
|
2066
2152
|
}
|
|
2067
2153
|
var signedEntry;
|
|
2068
|
-
try { signedEntry = _buildSctSignedEntry(stripped, sct); }
|
|
2154
|
+
try { signedEntry = _buildSctSignedEntry(stripped, sct, issuerKeyHash); }
|
|
2069
2155
|
catch (e) {
|
|
2070
2156
|
perSctResults.push({ logIdHex: sct.logIdHex, verified: false,
|
|
2071
2157
|
reason: "build-entry-failed",
|
|
@@ -2089,13 +2175,25 @@ function verifyScts(certDer, opts) {
|
|
|
2089
2175
|
error: (e && e.message) || String(e) });
|
|
2090
2176
|
continue;
|
|
2091
2177
|
}
|
|
2178
|
+
// RFC 6962 §3.2 — the SCT's log_id is the SHA-256 of the log's public key
|
|
2179
|
+
// (DER SubjectPublicKeyInfo). Enforce that the configured key actually
|
|
2180
|
+
// hashes to the claimed log_id before trusting its signature, so a
|
|
2181
|
+
// misconfigured logKeys map cannot let a key impersonate an approved log.
|
|
2182
|
+
var configuredLogId = nodeCrypto.createHash("sha256")
|
|
2183
|
+
.update(keyObj.export({ type: "spki", format: "der" })).digest();
|
|
2184
|
+
if (!configuredLogId.equals(sct.logId)) {
|
|
2185
|
+
perSctResults.push({ logIdHex: sct.logIdHex, verified: false,
|
|
2186
|
+
reason: "log-id-key-mismatch",
|
|
2187
|
+
configuredKeyId: configuredLogId.toString("hex") });
|
|
2188
|
+
continue;
|
|
2189
|
+
}
|
|
2092
2190
|
// RFC 6962 §2.1.4 — log-key SignatureAndHashAlgorithm pair must
|
|
2093
2191
|
// match the SCT's signatureAlgorithm. signatureAlgo enum 1=RSA,
|
|
2094
2192
|
// 3=ECDSA. Cross-check against the actual log-key type so a
|
|
2095
2193
|
// malformed log-keys map can't silently accept SCTs signed
|
|
2096
2194
|
// under one algorithm against a key registered under another.
|
|
2097
2195
|
var keyType = keyObj.asymmetricKeyType;
|
|
2098
|
-
var sctSigAlgo = sct.
|
|
2196
|
+
var sctSigAlgo = sct.sigAlgo; // the field _parseSct emits (RFC 5246 SignatureAlgorithm enum)
|
|
2099
2197
|
var algoOk = (sctSigAlgo === 1 && keyType === "rsa") || // TLS 1.2 SignatureAlgorithm rsa
|
|
2100
2198
|
(sctSigAlgo === 3 && (keyType === "ec" || keyType === "ecdsa")); // TLS 1.2 SignatureAlgorithm ecdsa
|
|
2101
2199
|
if (!algoOk) {
|
|
@@ -2113,8 +2211,9 @@ function verifyScts(certDer, opts) {
|
|
|
2113
2211
|
continue;
|
|
2114
2212
|
}
|
|
2115
2213
|
perSctResults.push({ logIdHex: sct.logIdHex, verified: verified });
|
|
2116
|
-
if (verified)
|
|
2214
|
+
if (verified) verifiedLogIds[sct.logIdHex] = true;
|
|
2117
2215
|
}
|
|
2216
|
+
var verifiedCount = Object.keys(verifiedLogIds).length; // distinct verified logs (Chrome CT diversity)
|
|
2118
2217
|
return {
|
|
2119
2218
|
ok: verifiedCount >= minScts,
|
|
2120
2219
|
reason: verifiedCount >= minScts ? null : "insufficient-verified",
|
|
@@ -2340,7 +2439,15 @@ var ct = Object.freeze({
|
|
|
2340
2439
|
},
|
|
2341
2440
|
// verifyScts — full RFC 6962 verification. opts.logKeys maps
|
|
2342
2441
|
// log_id (hex SHA-256 of the log's pubkey) → PEM public key.
|
|
2343
|
-
// Operators populate from the Chrome CT log list.
|
|
2442
|
+
// Operators populate from the Chrome CT log list. Embedded SCTs are
|
|
2443
|
+
// precert_entry, so the issuing CA is required — pass opts.issuerCertDer
|
|
2444
|
+
// (issuer cert DER), opts.issuerSpkiDer, or opts.issuerKeyHash (32-byte
|
|
2445
|
+
// SHA-256 of the issuer SubjectPublicKeyInfo); without it verification
|
|
2446
|
+
// fails closed with reason "issuer-key-required". verifiedCount counts
|
|
2447
|
+
// DISTINCT logs (Chrome CT diversity), so duplicate SCTs from one log do
|
|
2448
|
+
// not inflate it. opts.minScts (default 2) must be a positive integer.
|
|
2449
|
+
// Future-dated SCTs are refused (RFC 6962 §5.2; opts.now / opts.clockSkewMs
|
|
2450
|
+
// override the wall-clock and the default 5-minute skew). Returns
|
|
2344
2451
|
// { ok, verifiedCount, totalScts, scts: [{ logIdHex, verified, ... }] }.
|
|
2345
2452
|
verifyScts: verifyScts,
|
|
2346
2453
|
// Operator middleware predicate: refuse a peer cert lacking SCT
|
|
@@ -2354,6 +2461,10 @@ var ct = Object.freeze({
|
|
|
2354
2461
|
// opts: {
|
|
2355
2462
|
// sct: { logIdHex, timestamp, signedEntryDer? } — from parseScts
|
|
2356
2463
|
// leafCertificate: Buffer — leaf cert DER (the entry hashed at the leaf)
|
|
2464
|
+
// issuerCertDer / issuerSpkiDer / issuerKeyHash: the issuing CA — an
|
|
2465
|
+
// embedded SCT is a precert_entry, so the leaf binds
|
|
2466
|
+
// issuer_key_hash; omit only for a directly-logged
|
|
2467
|
+
// (x509_entry) certificate
|
|
2357
2468
|
// leafIndex: integer — position in the tree (from RFC 9162 §6.7 get-proof-by-hash)
|
|
2358
2469
|
// auditPath: [Buffer] — the inclusion-proof siblings, bottom-up
|
|
2359
2470
|
// sthFromLog: { treeSize, rootHash[, sha256RootHash] }
|
|
@@ -2389,11 +2500,20 @@ var ct = Object.freeze({
|
|
|
2389
2500
|
return { valid: false, reason: "bad-audit-path" };
|
|
2390
2501
|
}
|
|
2391
2502
|
|
|
2392
|
-
// Build the leaf bytes per RFC 9162 §4.6 — TimestampedEntry.
|
|
2393
|
-
//
|
|
2394
|
-
//
|
|
2503
|
+
// Build the leaf bytes per RFC 9162 §4.6 — TimestampedEntry. An SCT
|
|
2504
|
+
// embedded in the leaf's X.509v3 extension is a precert_entry: supply
|
|
2505
|
+
// the issuing CA (opts.issuerCertDer / issuerSpkiDer / issuerKeyHash)
|
|
2506
|
+
// and the leaf binds issuer_key_hash || tbs, matching what the log
|
|
2507
|
+
// hashed. Without an issuer the leaf is an x509_entry over the cert
|
|
2508
|
+
// bytes. Operators may pass a pre-built signedEntryDer when the SCT was
|
|
2395
2509
|
// already extracted via parseScts() + the framework has the
|
|
2396
2510
|
// pre-issuance cert; otherwise we strip the SCT extension here.
|
|
2511
|
+
var issuerKeyHash;
|
|
2512
|
+
try { issuerKeyHash = _resolveIssuerKeyHash(opts); }
|
|
2513
|
+
catch (e) {
|
|
2514
|
+
return { valid: false, reason: "bad-issuer-key",
|
|
2515
|
+
error: (e && e.message) || String(e) };
|
|
2516
|
+
}
|
|
2397
2517
|
var signedEntryDer = opts.sct.signedEntryDer;
|
|
2398
2518
|
if (!Buffer.isBuffer(signedEntryDer)) {
|
|
2399
2519
|
try { signedEntryDer = _stripSctExtensionFromCert(opts.leafCertificate); }
|
|
@@ -2404,9 +2524,9 @@ var ct = Object.freeze({
|
|
|
2404
2524
|
}
|
|
2405
2525
|
|
|
2406
2526
|
// RFC 9162 §4.6 MerkleTreeLeaf — version (1) + leaf_type (0) +
|
|
2407
|
-
// timestamp (uint64) + entry_type (uint16) + signed_entry (
|
|
2408
|
-
//
|
|
2409
|
-
//
|
|
2527
|
+
// timestamp (uint64) + entry_type (uint16) + signed_entry (issuer_key_
|
|
2528
|
+
// hash for precert_entry, then a 24-bit-length cert/TBS) + extensions
|
|
2529
|
+
// (16-bit length prefix, empty here).
|
|
2410
2530
|
var ts = opts.sct.timestamp;
|
|
2411
2531
|
if (typeof ts !== "number" && typeof ts !== "bigint") {
|
|
2412
2532
|
return { valid: false, reason: "bad-sct-timestamp" };
|
|
@@ -2414,16 +2534,21 @@ var ct = Object.freeze({
|
|
|
2414
2534
|
var tsBuf = Buffer.alloc(8); // TLS uint64 width
|
|
2415
2535
|
var tsBig = typeof ts === "bigint" ? ts : BigInt(Math.floor(ts));
|
|
2416
2536
|
tsBuf.writeBigUInt64BE(tsBig);
|
|
2417
|
-
var
|
|
2418
|
-
|
|
2419
|
-
|
|
2537
|
+
var leafEntry;
|
|
2538
|
+
try { leafEntry = _ctSignedEntry(signedEntryDer, issuerKeyHash); }
|
|
2539
|
+
catch (e) {
|
|
2540
|
+
return { valid: false, reason: "bad-issuer-key",
|
|
2541
|
+
error: (e && e.message) || String(e) };
|
|
2542
|
+
}
|
|
2543
|
+
var entryTypeBuf = Buffer.from([(leafEntry.entryType >> 8) & 0xff, // entry_type uint16
|
|
2544
|
+
leafEntry.entryType & 0xff]);
|
|
2420
2545
|
var extensionsBuf = Buffer.from([0x00, 0x00]); // empty extensions vector
|
|
2421
2546
|
var leafBytes = Buffer.concat([
|
|
2422
2547
|
Buffer.from([0x00]), // version v1
|
|
2423
2548
|
Buffer.from([0x00]), // leaf_type timestamped_entry
|
|
2424
2549
|
tsBuf,
|
|
2425
2550
|
entryTypeBuf,
|
|
2426
|
-
|
|
2551
|
+
leafEntry.signedEntry,
|
|
2427
2552
|
extensionsBuf,
|
|
2428
2553
|
]);
|
|
2429
2554
|
|
|
@@ -2533,7 +2658,22 @@ var ct = Object.freeze({
|
|
|
2533
2658
|
return new TlsTrustError("tls/ct-no-cert",
|
|
2534
2659
|
"requireScts: peer cert.raw missing");
|
|
2535
2660
|
}
|
|
2536
|
-
|
|
2661
|
+
// Embedded SCTs are precert_entry and need the issuing CA key
|
|
2662
|
+
// (RFC 6962 §3.2). Derive it from the peer's issuer in the chain when
|
|
2663
|
+
// the operator did not supply issuer material explicitly. Node sets
|
|
2664
|
+
// issuerCertificate to the cert itself at the self-signed root, so
|
|
2665
|
+
// guard against binding a cert to its own key.
|
|
2666
|
+
var effOpts = opts;
|
|
2667
|
+
if (!Buffer.isBuffer(opts.issuerKeyHash) &&
|
|
2668
|
+
!Buffer.isBuffer(opts.issuerSpkiDer) &&
|
|
2669
|
+
!Buffer.isBuffer(opts.issuerCertDer) &&
|
|
2670
|
+
peerCert.issuerCertificate &&
|
|
2671
|
+
peerCert.issuerCertificate !== peerCert &&
|
|
2672
|
+
Buffer.isBuffer(peerCert.issuerCertificate.raw)) {
|
|
2673
|
+
effOpts = Object.assign({}, opts,
|
|
2674
|
+
{ issuerCertDer: peerCert.issuerCertificate.raw });
|
|
2675
|
+
}
|
|
2676
|
+
var rv = verifyScts(peerCert.raw, effOpts);
|
|
2537
2677
|
if (!rv.ok) {
|
|
2538
2678
|
// Map verifier reason → operator-facing error code so call
|
|
2539
2679
|
// sites can distinguish "no SCT extension at all" from
|
|
@@ -3434,4 +3574,6 @@ module.exports = {
|
|
|
3434
3574
|
NetworkTlsError: NetworkTlsError,
|
|
3435
3575
|
_resetForTest: _resetForTest,
|
|
3436
3576
|
_checkServerIdentityStrict: _checkServerIdentityStrict,
|
|
3577
|
+
_stripSctExtensionFromCert: _stripSctExtensionFromCert,
|
|
3578
|
+
_buildSctSignedEntry: _buildSctSignedEntry,
|
|
3437
3579
|
};
|
package/lib/vendor/MANIFEST.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"hashes": {
|
|
19
19
|
"server": "sha256:2b30a26f728c5349f4c4b47834f862a4f77393b1224fc12b22abe3ce2cfab78f"
|
|
20
20
|
},
|
|
21
|
-
"refreshedAt": "2026-08-
|
|
21
|
+
"refreshedAt": "2026-08-06T00:01:44.784Z"
|
|
22
22
|
},
|
|
23
23
|
"@noble/curves": {
|
|
24
24
|
"version": "2.2.0",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"hashes": {
|
|
41
41
|
"server": "sha256:2880c288b1285ef51d356d057bee6f0c8a00de36638cf47b47617e8c1faf10d5"
|
|
42
42
|
},
|
|
43
|
-
"refreshedAt": "2026-08-
|
|
43
|
+
"refreshedAt": "2026-08-06T00:01:44.784Z"
|
|
44
44
|
},
|
|
45
45
|
"@noble/post-quantum": {
|
|
46
46
|
"version": "0.6.1",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"hashes": {
|
|
72
72
|
"server": "sha256:f9c94094b3c10fe73dac5343289da582454ea6053494fab2bf66099d9103d6c3"
|
|
73
73
|
},
|
|
74
|
-
"refreshedAt": "2026-08-
|
|
74
|
+
"refreshedAt": "2026-08-06T00:01:44.784Z"
|
|
75
75
|
},
|
|
76
76
|
"@simplewebauthn/server": {
|
|
77
77
|
"version": "13.3.2",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"hashes": {
|
|
95
95
|
"server": "sha256:e83195dc9f189385da9c856ef38843f4466f93ea8f3d7fc2efcb1e1b18da6f20"
|
|
96
96
|
},
|
|
97
|
-
"refreshedAt": "2026-08-
|
|
97
|
+
"refreshedAt": "2026-08-06T00:01:44.784Z"
|
|
98
98
|
},
|
|
99
99
|
"SecLists-common-passwords-top-10000": {
|
|
100
100
|
"version": "10k-most-common (master)",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
},
|
|
115
115
|
"runtime_artifact": "lib/vendor/common-passwords-top-10000.data.js",
|
|
116
116
|
"integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
|
|
117
|
-
"refreshedAt": "2026-08-
|
|
117
|
+
"refreshedAt": "2026-08-06T00:01:44.784Z"
|
|
118
118
|
},
|
|
119
119
|
"bimi-trust-anchors": {
|
|
120
120
|
"version": "operator-managed",
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
},
|
|
140
140
|
"runtime_artifact": "lib/vendor/bimi-trust-anchors.data.js",
|
|
141
141
|
"integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
|
|
142
|
-
"refreshedAt": "2026-08-
|
|
142
|
+
"refreshedAt": "2026-08-06T00:01:44.784Z"
|
|
143
143
|
},
|
|
144
144
|
"publicsuffix-list": {
|
|
145
145
|
"version": "master",
|
|
@@ -159,10 +159,10 @@
|
|
|
159
159
|
},
|
|
160
160
|
"runtime_artifact": "lib/vendor/public-suffix-list.data.js",
|
|
161
161
|
"integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
|
|
162
|
-
"refreshedAt": "2026-08-
|
|
162
|
+
"refreshedAt": "2026-08-06T00:01:44.784Z"
|
|
163
163
|
},
|
|
164
164
|
"@blamejs/pki": {
|
|
165
|
-
"version": "0.3.
|
|
165
|
+
"version": "0.3.32",
|
|
166
166
|
"license": "Apache-2.0",
|
|
167
167
|
"author": "blamejs",
|
|
168
168
|
"source": "https://github.com/blamejs/pki",
|
|
@@ -182,12 +182,12 @@
|
|
|
182
182
|
"server": "lib/vendor/blamejs-pki.cjs"
|
|
183
183
|
},
|
|
184
184
|
"bundler": "esbuild --format=cjs --platform=node --external:crypto --external:node:crypto",
|
|
185
|
-
"bundledAt": "2026-08-
|
|
186
|
-
"cpe": "cpe:2.3:a:blamejs:pki:0.3.
|
|
185
|
+
"bundledAt": "2026-08-05T00:00:00Z",
|
|
186
|
+
"cpe": "cpe:2.3:a:blamejs:pki:0.3.32:*:*:*:*:node.js:*:*",
|
|
187
187
|
"hashes": {
|
|
188
|
-
"server": "sha256:
|
|
188
|
+
"server": "sha256:275a0ef678a74e9ce035658ae95b18c05dcf69200bf934f609f149d1c8e739e3"
|
|
189
189
|
},
|
|
190
|
-
"refreshedAt": "2026-08-
|
|
190
|
+
"refreshedAt": "2026-08-06T00:01:44.784Z"
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
}
|