@blamejs/core 0.15.45 → 0.15.46
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 +2 -0
- package/lib/auth/fido-mds3.js +9 -7
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.15.x
|
|
10
10
|
|
|
11
|
+
- v0.15.46 (2026-06-28) — **`b.auth.fidoMds3` now enforces basicConstraints cA:TRUE on every intermediate link of the MDS3 x5c chain, closing a bypass where a non-CA certificate spliced in as an intermediate could sign a forged FIDO metadata BLOB that fido-mds3 accepted.** fido-mds3's x5c chain validation checked each intermediate link with X509Certificate.checkIssued(), which validates the issuer/subject linkage and signature but does NOT enforce the basicConstraints CA bit; only the final tail-to-root anchor used the framework's cA-enforcing helper. An attacker holding a non-CA (cA:FALSE) end-entity certificate that legitimately chains to a trusted MDS3 root (and its private key) could splice it in as an intermediate, sign a forged leaf with it, and have fido-mds3 accept an attacker-authored MDS3 metadata BLOB as authentic — the classic basicConstraints bypass (CVE-2002-0862 class). Every intermediate link now routes through x509Chain.issuerValidlyIssued, which enforces basicConstraints cA:TRUE in addition to the issuance and signature linkage, matching the mdoc / tsa / bimi / content-credentials / S-MIME chain walkers. A codebase guard now fails the build if any chain walker uses a bare issuance checkIssued() instead. **Security:** *fido-mds3 x5c intermediate links enforce the CA bit (basicConstraints bypass closed)* — b.auth.fidoMds3 validated each intermediate x5c link with X509Certificate.checkIssued(), which does not enforce basicConstraints cA:TRUE, so a non-CA certificate inserted as an intermediate was accepted as an issuer. An attacker with a cA:FALSE end-entity cert chaining to a trusted MDS3 root could sign a forged leaf and have a forged FIDO metadata BLOB accepted as authentic (CVE-2002-0862 class). Each intermediate link now routes through x509Chain.issuerValidlyIssued (cA:TRUE + issuance + signature), the same hardened test the framework's other certificate-chain walkers already use. The default GlobalSign MDS3 root and operator-supplied caCertificate roots are both covered. **Detectors:** *Build guard: a cert-chain issuance link must use issuerValidlyIssued, not bare checkIssued* — A codebase guard now fails the build if a certificate-chain walker validates an issuance link with a bare X509Certificate.checkIssued() (which omits the basicConstraints CA check) instead of x509Chain.issuerValidlyIssued, so the basicConstraints bypass cannot reappear at a new walker. A self-signed-root check (cert.checkIssued(cert)) is not an issuance link and is unaffected.
|
|
12
|
+
|
|
11
13
|
- v0.15.45 (2026-06-28) — **`b.selfUpdate.swap` now re-hashes the asset against the hash `verify` returned and refuses to install on a mismatch, closing the window where an asset swapped between verify and swap could be installed after the signature check passed.** selfUpdate.verify (signature-checks the asset and returns its hash) and selfUpdate.swap (renames the new asset into place) were two separate path-keyed operations with nothing binding the bytes swap installs to the bytes verify checked. An attacker able to write the asset path in the window between the two calls — or who pointed verify at a different inode via a symlink — could have the signature-verified bytes replaced before swap renamed the file into place, installing unverified code. swap now requires an expectedHash and re-hashes the from bytes against it immediately before the install, refusing with selfupdate/swap-hash-mismatch on any difference; the re-check runs right before the rename so the residual is a sub-millisecond window rather than the operator-controlled gap between verify and swap. Because swap now requires expectedHash, callers must pass the hash that verify returned (await verify(...) then swap({ ..., expectedHash: result.hash })). **Security:** *self-update install is bound to the signature-verified bytes (verify→swap TOCTOU closed)* — selfUpdate.swap renamed the new asset into place with no integrity re-check, so the bytes it installed were not bound to the bytes selfUpdate.verify had signature-checked: an attacker who could write the asset path between verify and swap (or repoint a symlink) could install unverified code. swap now re-hashes the from bytes against a required expectedHash (the hash verify returns) immediately before the install and refuses a mismatch (selfupdate/swap-hash-mismatch), with the check positioned right before the rename to minimize the residual window. A symlinked or differing-inode asset is caught the same way, because the install source is what gets re-hashed. **Migration:** *selfUpdate.swap requires expectedHash* — swap now requires an expectedHash option (refused at the call with selfupdate/bad-expected-hash if absent). Pass the hash that selfUpdate.verify returns: const v = await selfUpdate.verify({ assetPath, signaturePath, pubkeyPem }); await selfUpdate.swap({ from, to, backupTo, expectedHash: v.hash }). An optional hashAlgo (default sha3-512, matching verify's default) selects the digest when verify used a non-default algorithm.
|
|
12
14
|
|
|
13
15
|
- v0.15.44 (2026-06-28) — **`b.fileUpload` now rejects the bare path tokens "." and ".." as upload IDs, closing a path traversal where a ".." id resolved to the staging directory's parent and a cancel or cleanup could recursively delete it.** b.fileUpload validated the uploadId with a character-class regex that permits the dot character, so the bare path tokens "." and ".." passed validation even though they are not ordinary identifiers. The uploadId is joined under the configured staging directory to locate an upload's files, so "." resolved to the staging directory itself and ".." to its parent: an operation keyed on a ".." uploadId acted OUTSIDE the staging directory. The most damaging path is cancelUpload (and finalize/expiry cleanup), which removes the upload directory with a recursive rmSync — keyed on ".." that would recursively delete the staging directory's parent; chunk writes for a ".." id also land outside staging. The validator now rejects "." and ".." before any filesystem operation. Every public method validates the uploadId there, so init, acceptChunk, finalize, status, and cancelUpload are all covered, and legitimate dotted IDs (for example "build.v2") are unaffected. **Security:** *Upload IDs of "." and ".." are refused (path-traversal / parent-directory deletion)* — fileUpload's uploadId regex allows the dot character, so the bare tokens "." and ".." passed validation and, joined under the staging directory, resolved to the staging directory or its parent. An operation keyed on a ".." id acted outside staging — most seriously cancelUpload / cleanup, whose recursive rmSync would have deleted the staging directory's parent, and chunk writes that would land outside staging. The validator now rejects "." and ".." up front; because every public method validates the id, init / acceptChunk / finalize / status / cancelUpload are all covered. Legitimate dotted IDs are unaffected. Operators who never pass caller-controlled upload IDs were not exposed.
|
package/lib/auth/fido-mds3.js
CHANGED
|
@@ -234,14 +234,16 @@ function _validateChain(x5c, rootPems) {
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
for (var c = 0; c < chain.length - 1; c++) {
|
|
237
|
-
|
|
237
|
+
// issuerValidlyIssued enforces basicConstraints cA:TRUE on the issuing cert
|
|
238
|
+
// (chain[c+1]) in addition to the issuance + signature linkage — a non-CA
|
|
239
|
+
// cert spliced in as an intermediate cannot issue the next link (the classic
|
|
240
|
+
// basicConstraints bypass, CVE-2002-0862 class). Mirrors mdoc / tsa / bimi /
|
|
241
|
+
// content-credentials, which enforce cA:TRUE on every hop; only the
|
|
242
|
+
// tail-to-root anchor below previously did so.
|
|
243
|
+
if (!x509Chain.issuerValidlyIssued(chain[c + 1], chain[c])) {
|
|
238
244
|
throw new FidoMds3Error("fido-mds3/chain-broken",
|
|
239
|
-
"x5c[" + c + "] not issued by x5c[" + (c + 1) +
|
|
240
|
-
|
|
241
|
-
var issuerKey = chain[c + 1].publicKey;
|
|
242
|
-
if (!chain[c].verify(issuerKey)) {
|
|
243
|
-
throw new FidoMds3Error("fido-mds3/chain-bad-signature",
|
|
244
|
-
"x5c[" + c + "] signature does not verify against x5c[" + (c + 1) + "] public key");
|
|
245
|
+
"x5c[" + c + "] not validly issued by x5c[" + (c + 1) +
|
|
246
|
+
"] (issuer must be a CA whose signature over the subject verifies)");
|
|
245
247
|
}
|
|
246
248
|
}
|
|
247
249
|
var tail = chain[chain.length - 1];
|
package/package.json
CHANGED
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:305da097-2160-4fd5-b1ce-47f87e378030",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-06-
|
|
8
|
+
"timestamp": "2026-06-28T15:04:50.786Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.15.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.15.46",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.15.
|
|
25
|
+
"version": "0.15.46",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.15.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.15.46",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.15.
|
|
57
|
+
"ref": "@blamejs/core@0.15.46",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|