@blamejs/core 0.15.44 → 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 +4 -0
- package/lib/auth/fido-mds3.js +9 -7
- package/lib/self-update.js +88 -56
- 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.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
|
+
|
|
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.
|
|
14
|
+
|
|
11
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.
|
|
12
16
|
|
|
13
17
|
- v0.15.43 (2026-06-28) — **`b.auth.lockout`'s failed-attempt counter now accumulates with an atomic compare-and-set, so a brute-force attacker spreading failed logins across multiple nodes can no longer lose increments and stay under the lockout threshold.** b.auth.lockout tracked failed attempts with a cache read-modify-write: read the counter, increment it, write it back. On a multi-node deployment two concurrent failures for the same account on different nodes both read the same value, each added one, and one write clobbered the other — a lost update that let an attacker spreading attempts across nodes exceed maxAttempts without ever triggering the lockout, weakening brute-force protection on a cluster. The counter now runs the whole decision (window decay, increment, lockout-ladder) through the cache's atomic update() (compare-and-set, retried on the cluster backend under contention), so every failure is counted regardless of which node records it. The lockout's documented fail-open posture is preserved — a genuinely unreachable cache still allows the attempt and signals the error — but a cache backend that cannot perform an atomic update now surfaces loud at first use instead of silently disabling the lockout. **Security:** *Lockout failure counter is atomic across nodes (no lost increments)* — b.auth.lockout accumulated failed attempts with a non-atomic cache get -> increment -> set, so concurrent failures for one account across a multi-node deployment lost increments and an attacker could exceed maxAttempts without engaging the lockout. The counter now uses the cache's atomic compare-and-set update(), counting every failure across nodes, with the existing exponential lockout ladder and window-decay logic unchanged. The cache backing a lockout must support atomic update() (b.cache does; the create() check now requires it), and a backend that can't commit an atomic update surfaces loud at first use rather than silently disabling brute-force protection. A genuinely unreachable cache still fails open by design.
|
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/lib/self-update.js
CHANGED
|
@@ -28,9 +28,11 @@
|
|
|
28
28
|
* P-384 from the supplied PEM) and reports the bytes' hash for
|
|
29
29
|
* SBOM correlation. A mismatched signature throws and the swap
|
|
30
30
|
* never runs.
|
|
31
|
-
* 4. `b.selfUpdate.swap({ from, to, backupTo })` performs
|
|
32
|
-
* atomic install:
|
|
33
|
-
* `
|
|
31
|
+
* 4. `b.selfUpdate.swap({ from, to, backupTo, expectedHash })` performs
|
|
32
|
+
* the atomic install: re-hash `from` and refuse unless it matches
|
|
33
|
+
* `expectedHash` (the hash step 3 returned — binding the installed
|
|
34
|
+
* bytes to the verified bytes), copy the current `to` to `backupTo`,
|
|
35
|
+
* rename `from` → `to`, fsync both directories. Cross-device renames
|
|
34
36
|
* fall back to copy + unlink. Any failure rolls back from the
|
|
35
37
|
* backup. `b.selfUpdate.rollback({ to, backupTo })` restores
|
|
36
38
|
* the backup post-swap when a healthcheck reports the new
|
|
@@ -614,6 +616,18 @@ function _validateSwapOpts(opts, label) {
|
|
|
614
616
|
if (label === "swap") {
|
|
615
617
|
schema.from = { rule: "required-string", code: "selfupdate/bad-from",
|
|
616
618
|
label: "selfUpdate.swap: opts.from" };
|
|
619
|
+
// The bytes about to be installed are re-hashed and checked against the
|
|
620
|
+
// hash selfUpdate.verify returned, closing the verify -> swap window. The
|
|
621
|
+
// binding is mandatory — an optional integrity check is opt-in security.
|
|
622
|
+
schema.expectedHash = { rule: "required-string", code: "selfupdate/bad-expected-hash",
|
|
623
|
+
label: "selfUpdate.swap: opts.expectedHash (the hash selfUpdate.verify returned)" };
|
|
624
|
+
schema.hashAlgo = function (value) {
|
|
625
|
+
if (value !== undefined &&
|
|
626
|
+
(typeof value !== "string" || ALLOWED_HASH_ALGS.indexOf(value) === -1)) {
|
|
627
|
+
throw new SelfUpdateError("selfupdate/bad-hash-algo",
|
|
628
|
+
"selfUpdate.swap: opts.hashAlgo must be one of " + ALLOWED_HASH_ALGS.join(", "));
|
|
629
|
+
}
|
|
630
|
+
};
|
|
617
631
|
}
|
|
618
632
|
schema.to = { rule: "required-string", code: "selfupdate/bad-to",
|
|
619
633
|
label: "selfUpdate." + label + ": opts.to" };
|
|
@@ -664,24 +678,31 @@ async function _safeRollback(backupTo, to, hadOriginal) {
|
|
|
664
678
|
* @since 0.6.0
|
|
665
679
|
* @related b.selfUpdate.verify, b.selfUpdate.rollback, b.atomicFile.copy
|
|
666
680
|
*
|
|
667
|
-
* Atomic install:
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
*
|
|
681
|
+
* Atomic install: re-hash `from` and refuse unless it matches `expectedHash`
|
|
682
|
+
* (the hash selfUpdate.verify returned — this binds the installed bytes to the
|
|
683
|
+
* signature-verified bytes and closes the verify→swap window), copy the
|
|
684
|
+
* existing `to` to `backupTo`, rename `from` → `to`, then fsync both
|
|
685
|
+
* directories. Cross-device renames fall back to copy + unlink on the
|
|
686
|
+
* destination filesystem. On any failure the original `to` is restored from
|
|
687
|
+
* `backupTo`. Throws SelfUpdateError on a missing `from`, an
|
|
688
|
+
* expectedHash mismatch, backup-copy failure, cross-device install failure,
|
|
672
689
|
* or rename failure.
|
|
673
690
|
*
|
|
674
691
|
* @opts
|
|
675
|
-
* from:
|
|
676
|
-
* to:
|
|
677
|
-
* backupTo:
|
|
692
|
+
* from: string, // required — newly-installed asset path
|
|
693
|
+
* to: string, // required — target install path
|
|
694
|
+
* backupTo: string, // required — backup path for the existing `to`
|
|
695
|
+
* expectedHash: string, // required — the hash selfUpdate.verify returned
|
|
696
|
+
* hashAlgo: string, // sha3-512 (default) | sha-256 | sha-512 | shake256
|
|
678
697
|
*
|
|
679
698
|
* @example
|
|
699
|
+
* var v = await b.selfUpdate.verify({ assetPath, signaturePath, pubkeyPem });
|
|
680
700
|
* try {
|
|
681
701
|
* await b.selfUpdate.swap({
|
|
682
|
-
* from:
|
|
683
|
-
* to:
|
|
684
|
-
* backupTo:
|
|
702
|
+
* from: "/tmp/blamejs-doc-missing.bin",
|
|
703
|
+
* to: "/tmp/blamejs-doc-target.bin",
|
|
704
|
+
* backupTo: "/tmp/blamejs-doc-backup.bin",
|
|
705
|
+
* expectedHash: v.hash,
|
|
685
706
|
* });
|
|
686
707
|
* } catch (e) {
|
|
687
708
|
* e.code; // → "selfupdate/missing-from"
|
|
@@ -698,6 +719,37 @@ async function swap(opts) {
|
|
|
698
719
|
"selfUpdate.swap: from path does not exist: " + from);
|
|
699
720
|
}
|
|
700
721
|
|
|
722
|
+
// Bind the installed object to the signature-verified bytes. Read `from` with
|
|
723
|
+
// O_NOFOLLOW (refuseSymlink) so a symlinked source is refused AT OPEN rather
|
|
724
|
+
// than followed — otherwise the bytes hashed (the link target) would differ
|
|
725
|
+
// from the object a by-path rename installs (the link itself). The verified
|
|
726
|
+
// bytes are then installed FROM MEMORY below, so the installed object is
|
|
727
|
+
// exactly what was hashed: no symlink-install surface and no time-of-check /
|
|
728
|
+
// time-of-use window between the hash and the install (which a by-path rename
|
|
729
|
+
// or re-read would reopen).
|
|
730
|
+
var swapAlg = opts.hashAlgo || DEFAULT_HASH_ALG;
|
|
731
|
+
var fromMode;
|
|
732
|
+
try { fromMode = (nodeFs.statSync(from).mode & 0o777); } catch (_m) { fromMode = 0o600; }
|
|
733
|
+
var fromBytes;
|
|
734
|
+
try {
|
|
735
|
+
fromBytes = atomicFile.fdSafeReadSync(from, {
|
|
736
|
+
maxBytes: typeof opts.maxBytes === "number" ? opts.maxBytes : C.BYTES.gib(1),
|
|
737
|
+
refuseSymlink: true,
|
|
738
|
+
});
|
|
739
|
+
} catch (e) {
|
|
740
|
+
throw new SelfUpdateError("selfupdate/swap-read-failed",
|
|
741
|
+
"selfUpdate.swap: failed to read from for the integrity re-check (a symlinked source is refused): " +
|
|
742
|
+
((e && e.message) || String(e)));
|
|
743
|
+
}
|
|
744
|
+
var actualHash = nodeCrypto.createHash(swapAlg).update(fromBytes).digest("hex");
|
|
745
|
+
if (actualHash !== opts.expectedHash) {
|
|
746
|
+
_safeAuditEmit("selfupdate.swap.hash_mismatch", "denied", {
|
|
747
|
+
from: from, to: to, alg: swapAlg, expected: opts.expectedHash, actual: actualHash,
|
|
748
|
+
});
|
|
749
|
+
throw new SelfUpdateError("selfupdate/swap-hash-mismatch",
|
|
750
|
+
"selfUpdate.swap: from bytes do not match expectedHash (asset changed after verify?) — refusing to install");
|
|
751
|
+
}
|
|
752
|
+
|
|
701
753
|
var toDir = nodePath.dirname(to);
|
|
702
754
|
var backupDir = nodePath.dirname(backupTo);
|
|
703
755
|
atomicFile.ensureDir(toDir);
|
|
@@ -716,53 +768,33 @@ async function swap(opts) {
|
|
|
716
768
|
}
|
|
717
769
|
}
|
|
718
770
|
|
|
719
|
-
// Step 3 — install
|
|
720
|
-
//
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
//
|
|
771
|
+
// Step 3 — install the verified in-memory bytes via an atomic temp+fsync+
|
|
772
|
+
// rename on the DESTINATION filesystem (atomicFile.write), so the installed
|
|
773
|
+
// object is exactly the bytes just hashed: cross-device is handled (the temp
|
|
774
|
+
// is created on the dest FS), there is no by-path re-read to race, and a
|
|
775
|
+
// symlinked source can't be moved into place. Roll back from the backup on
|
|
776
|
+
// failure; a rollback failure surfaces as a DISTINCT error class + audit
|
|
777
|
+
// event so operators don't silently lose both binaries (SSDF RV.1).
|
|
724
778
|
try {
|
|
725
|
-
atomicFile.
|
|
779
|
+
await atomicFile.write(to, fromBytes, { fileMode: fromMode, overwrite: true });
|
|
726
780
|
} catch (e) {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
// Roll back from backup if we have one. Rollback failure here
|
|
735
|
-
// is catastrophic — both the new asset (corrupt cross-device
|
|
736
|
-
// copy) AND the original (overwritten partial) are now
|
|
737
|
-
// unreachable. Surface to operator with a dedicated error.
|
|
738
|
-
var rbErrXdev = await _safeRollback(backupTo, to, hadOriginal);
|
|
739
|
-
if (rbErrXdev) {
|
|
740
|
-
throw new SelfUpdateError("selfupdate/swap-rollback-failed",
|
|
741
|
-
"selfUpdate.swap: cross-device install failed AND rollback ALSO " +
|
|
742
|
-
"failed — operator must manually restore from backupTo=" + backupTo +
|
|
743
|
-
". install-error=" + ((ce && ce.message) || String(ce)) +
|
|
744
|
-
"; rollback-error=" + rbErrXdev.message);
|
|
745
|
-
}
|
|
746
|
-
throw new SelfUpdateError("selfupdate/cross-device",
|
|
747
|
-
"selfUpdate.swap: cross-device install failed: " + ((ce && ce.message) || String(ce)));
|
|
748
|
-
}
|
|
749
|
-
} else {
|
|
750
|
-
// Other rename failure — try to roll back. Same rollback-failure
|
|
751
|
-
// semantics as the cross-device branch.
|
|
752
|
-
var rbErr = await _safeRollback(backupTo, to, hadOriginal);
|
|
753
|
-
if (rbErr) {
|
|
754
|
-
throw new SelfUpdateError("selfupdate/swap-rollback-failed",
|
|
755
|
-
"selfUpdate.swap: rename " + from + " -> " + to + " failed AND " +
|
|
756
|
-
"rollback ALSO failed — operator must manually restore from " +
|
|
757
|
-
"backupTo=" + backupTo + ". rename-error=" + e.message +
|
|
758
|
-
"; rollback-error=" + rbErr.message);
|
|
759
|
-
}
|
|
760
|
-
throw new SelfUpdateError("selfupdate/swap-failed",
|
|
761
|
-
"selfUpdate.swap: rename " + from + " -> " + to + " failed: " + e.message);
|
|
781
|
+
var rbErr = await _safeRollback(backupTo, to, hadOriginal);
|
|
782
|
+
if (rbErr) {
|
|
783
|
+
throw new SelfUpdateError("selfupdate/swap-rollback-failed",
|
|
784
|
+
"selfUpdate.swap: install of " + to + " failed AND rollback ALSO failed — " +
|
|
785
|
+
"operator must manually restore from backupTo=" + backupTo +
|
|
786
|
+
". install-error=" + ((e && e.message) || String(e)) +
|
|
787
|
+
"; rollback-error=" + rbErr.message);
|
|
762
788
|
}
|
|
789
|
+
throw new SelfUpdateError("selfupdate/swap-failed",
|
|
790
|
+
"selfUpdate.swap: install of " + to + " failed: " + ((e && e.message) || String(e)));
|
|
763
791
|
}
|
|
792
|
+
// Consume the source asset now that the verified bytes are installed
|
|
793
|
+
// (best-effort — the install already succeeded; a leftover temp is
|
|
794
|
+
// operator-cleanable).
|
|
795
|
+
try { nodeFs.unlinkSync(from); } catch (_u) { /* tmp source leak — operator-cleanable */ }
|
|
764
796
|
|
|
765
|
-
// Step 4 — fsync directories so the
|
|
797
|
+
// Step 4 — fsync directories so the install is durable.
|
|
766
798
|
atomicFile.fsyncDir(toDir);
|
|
767
799
|
if (backupDir !== toDir) atomicFile.fsyncDir(backupDir);
|
|
768
800
|
|
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
|
]
|