@blamejs/core 0.17.16 → 0.17.17
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/oauth.js +10 -0
- package/lib/mail-auth.js +24 -0
- package/lib/mail-dkim.js +37 -16
- package/lib/tsa.js +32 -11
- 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.17.x
|
|
10
10
|
|
|
11
|
+
- v0.17.17 (2026-07-23) — **Three verifier hardening fixes: timestamp trust anchors are required by default, OIDC id_token verification requires a configured issuer, and ed25519 DKIM uses the RFC 8463 raw-key format.** An audit of the signature-verifier surface found three verifiers that accepted or rejected the wrong thing. b.tsa.verifyToken authenticated a timestamp against a certificate embedded in the token itself and only ran the trust-anchor chain when the caller supplied one, so a self-signed token carrying the timestamping EKU was accepted as a valid timestamp; trust anchors are now required by default. b.auth.oauth's verifyIdToken skipped the CVE-2026-23552 cross-realm iss check entirely when the client was created without an issuer, so any OIDC id_token verified regardless of its issuer; an OIDC client must now be configured with an issuer to verify id_tokens. And the ed25519 DKIM verifier could not read the RFC 8463 raw 32-byte key format that every conformant sender publishes -- while the framework's own bootstrap published the non-standard SPKI form -- so ed25519, the default DKIM algorithm, was non-interoperable in both directions. **Fixed:** *ed25519 DKIM uses the RFC 8463 raw-key format on both sign and verify* — RFC 8463 §3-4 publishes an ed25519 DKIM public key as the raw 32-byte key, base64'd -- the form every conformant sender uses -- but the verifier wrapped that raw key in SubjectPublicKeyInfo PEM markers, which is not valid SPKI, so createPublicKey threw and a valid ed25519 signature returned permerror. Compounding it, b.mail.dkim.bootstrap published the key as SPKI DER (60 base64 chars) instead of the raw form (44 chars), so the framework's own default-algorithm signatures would not verify at a conformant receiver. The verifier now accepts a raw 32-byte key (wrapping it in the ed25519 SPKI header) and bootstrap publishes the raw key; RSA is unchanged. The same raw-key handling is applied on the ARC (b.mail.arc.verify) message-signature path. **Security:** *Timestamp verification requires a trust anchor by default* — b.tsa.verifyToken verified an RFC 3161 timestamp against the signer certificate embedded in the token -- which is attacker-controlled -- and ran the certificate-chain / trust-anchor check only when the caller passed trustAnchorsPem. With it omitted, a token whose self-signed leaf carried a critical, sole id-kp-timeStamping EKU was accepted as a valid timestamp: a forged proof that data existed at an arbitrary time (RFC 3161 §2.4.2 requires a trusted TSA). trustAnchorsPem is now required by default; an operator who deliberately wants trust-anchor-free verification sets allowUntrustedIssuer:true and the result carries issuerTrusted:false so the unauthenticated posture is visible (mirrors b.mdoc.verifyIssuerSigned). · *OIDC id_token verification requires a configured issuer* — b.auth.oauth.verifyIdToken wrapped the expected-issuer comparison -- the CVE-2026-23552 cross-realm / cross-issuer defense -- in a check that ran only when the client was configured with an issuer. An OIDC client (the default) created without an issuer therefore accepted an id_token with any iss, or none, as long as the signature, aud, and exp were valid: exactly the cross-realm acceptance the verifier exists to close, dangerous against a shared-signing-key multi-tenant OP. verifyIdToken now refuses to verify an OIDC id_token when no expected issuer is configured (OIDC Core §3.1.3.7); pass issuer to b.auth.oauth.create().
|
|
12
|
+
|
|
11
13
|
- v0.17.16 (2026-07-23) — **A shared path-containment primitive backs both the strict resolver and static file serving.** b.safePath.resolve and b.staticServe each hand-rolled the same lexical traversal-containment check -- resolve a request path against a base and confirm it stays strictly inside. That core is now one primitive, b.safePath.confineToBase, which both compose: resolve layers its user-input strictness (reserved names, NTFS ADS markers, bidi, control chars) on top, while static serving composes only the bare containment and keeps its own separate basename gate. Static serving keeps that separation deliberately -- its containment barrier and its per-file guardFilename validation are distinct concerns -- and inherits the resolver's cross-platform-aware containment, which the runtime path module missed for a backslash traversal on a POSIX host. Static serving's basename policy is unchanged. **Added:** *b.safePath.confineToBase -- the lexical traversal-containment core* — b.safePath.confineToBase(base, rel, opts?) resolves rel against base using the target platform's path semantics and returns the confined absolute path, or null if it escapes. It is the containment barrier b.safePath.resolve layers its user-input strictness on top of, exposed for a consumer that wants ONLY traversal containment and applies its own, differently-calibrated filename validation -- so it does not reject a reserved name, NTFS ADS marker, or trailing dot the way resolve does for untrusted input. opts.platform forces windows path semantics on any host. **Changed:** *Static file serving composes the shared containment primitive* — b.staticServe's path-traversal barrier now composes b.safePath.confineToBase for its final lexical containment instead of a hand-rolled join + base-prefix check, so both the strict resolver and static serving route through one implementation. Static serving gains the cross-platform-aware containment (the runtime path module treats the other platform's separator as an ordinary filename character and missed a backslash traversal on a POSIX host). Serve behavior is unchanged: containment and the separate per-file guardFilename basename gate remain distinct steps, and static composes only the containment core rather than resolve so it does not fuse resolve's all-segment user-input strictness into the barrier (which would reject a legitimate colon-named intermediate directory that the basename gate permits). The basename policy itself is untouched.
|
|
12
14
|
|
|
13
15
|
- v0.17.15 (2026-07-23) — **The standalone multipart parser honors storage: memory and every other documented upload knob.** b.parsers.multipart(req, opts) is documented as a thin wrapper over the same engine b.middleware.bodyParser drives, but its option resolver carried a hand-maintained passthrough list that had drifted: storage ("disk" | "memory") and filenameCharsets were silently dropped, so a serverless / read-only-filesystem handler passing storage: "memory" got disk mode instead and threw when the parser tried to open a temp file. The passthrough is now derived from the parser's own defaults, so every documented knob reaches the standalone path and a future knob cannot silently vanish, and an invalid storage value throws at the call the same way the middleware does instead of falling through to disk. **Fixed:** *b.parsers.multipart honors storage: memory and filenameCharsets* — The standalone multipart parser's option resolver passed through a hand-maintained subset of the multipart knobs, omitting storage and filenameCharsets: b.parsers.multipart(req, { storage: "memory" }) silently used disk mode and then threw on a read-only / serverless filesystem, and a filename*=ISO-8859-1'' part could not be opted in via filenameCharsets. The passthrough is now derived from the parser's DEFAULTS so every documented knob reaches the standalone path (only the maxBytes/maxFiles aliases and the dispatch-only contentTypes are excluded), and an invalid storage value throws a TypeError at the call rather than falling back to disk -- matching b.middleware.bodyParser.
|
package/lib/auth/oauth.js
CHANGED
|
@@ -2027,6 +2027,16 @@ function create(opts) {
|
|
|
2027
2027
|
if (typeof payload.nbf === "number" && payload.nbf - skewSec > now) {
|
|
2028
2028
|
throw new OAuthError("auth-oauth/nbf-future", "ID token nbf is in the future");
|
|
2029
2029
|
}
|
|
2030
|
+
if (isOidc && !issuer) {
|
|
2031
|
+
// An OIDC id_token carries an `iss` that MUST be validated against a
|
|
2032
|
+
// configured expected issuer (OIDC Core §3.1.3.7). With no issuer set the
|
|
2033
|
+
// iss comparison below would be skipped entirely — the CVE-2026-23552
|
|
2034
|
+
// cross-realm acceptance this verifier exists to close. Fail closed
|
|
2035
|
+
// rather than accept an id_token whose issuer can't be authenticated.
|
|
2036
|
+
throw new OAuthError("auth-oauth/issuer-required",
|
|
2037
|
+
"verifyIdToken: an OIDC client must be configured with `issuer` to validate the " +
|
|
2038
|
+
"id_token's iss (OIDC Core 3.1.3.7 / cross-realm-JWT defense); pass issuer to b.auth.oauth.create()");
|
|
2039
|
+
}
|
|
2030
2040
|
if (issuer) {
|
|
2031
2041
|
// CVE-2026-23552 — cross-realm / cross-issuer JWT acceptance. The
|
|
2032
2042
|
// expected issuer is operator-supplied; payload.iss is attacker-
|
package/lib/mail-auth.js
CHANGED
|
@@ -1849,6 +1849,19 @@ async function _verifyArc(rfc822, hop, allHops, kind, dnsLookup, dkim) {
|
|
|
1849
1849
|
var asUnsigned = dkim._stripBTagValue(sigValue);
|
|
1850
1850
|
canonicalized += _canonRelaxedHeader("ARC-Seal", asUnsigned).replace(/\r\n$/, "");
|
|
1851
1851
|
|
|
1852
|
+
// RFC 6376 §3.6.1 / RFC 8617 — the ARC key record's k= (DEFAULT "rsa" when
|
|
1853
|
+
// absent) must match the seal's a= family. Without this, a record
|
|
1854
|
+
// `p=<raw-32-byte-key>` with no k= is an RSA record that would be misread as
|
|
1855
|
+
// Ed25519 and validate an a=ed25519-sha256 seal the record never authorized
|
|
1856
|
+
// (a key-family confusion that marks a forged chain as cryptographically
|
|
1857
|
+
// passing).
|
|
1858
|
+
var asKFamily = keyTags.k !== undefined ? String(keyTags.k).toLowerCase() : "rsa";
|
|
1859
|
+
var asSigFamily = String(tags.a || "").toLowerCase().split("-")[0];
|
|
1860
|
+
if (asKFamily !== asSigFamily) {
|
|
1861
|
+
return { result: "permerror",
|
|
1862
|
+
errors: [kind + ": key k=" + asKFamily + " does not match seal a=" + tags.a + " (RFC 6376 §3.6.1)"] };
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1852
1865
|
// Verify the AS signature.
|
|
1853
1866
|
return _runVerify(canonicalized, tags.b, tags.a, keyTags.p, "as");
|
|
1854
1867
|
}
|
|
@@ -1932,7 +1945,18 @@ function _canonRelaxedHeader(name, value) {
|
|
|
1932
1945
|
return dkim.canonHeaderRelaxed(name, value);
|
|
1933
1946
|
}
|
|
1934
1947
|
|
|
1948
|
+
// RFC 8410 Ed25519 SubjectPublicKeyInfo header prepended to a raw 32-byte key.
|
|
1949
|
+
var ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
1950
|
+
|
|
1935
1951
|
function _pemFromB64KeyMaterial(b64) {
|
|
1952
|
+
// RFC 8463 §3-4 publishes an ed25519 DKIM key as the RAW 32-byte key
|
|
1953
|
+
// (base64'd), which is not valid SPKI DER; wrap it in the ed25519 SPKI header
|
|
1954
|
+
// first. Already-SPKI ed25519 (44 bytes) / RSA SPKI (larger) pass through.
|
|
1955
|
+
var raw = null;
|
|
1956
|
+
try { raw = Buffer.from(b64, "base64"); } catch (_e) { raw = null; }
|
|
1957
|
+
if (raw && raw.length === 32) {
|
|
1958
|
+
b64 = Buffer.concat([ED25519_SPKI_PREFIX, raw]).toString("base64");
|
|
1959
|
+
}
|
|
1936
1960
|
var pem = "-----BEGIN PUBLIC KEY-----\n";
|
|
1937
1961
|
for (var i = 0; i < b64.length; i += 64) { // PEM wrap width
|
|
1938
1962
|
pem += b64.slice(i, i + 64) + "\n"; // PEM wrap width
|
package/lib/mail-dkim.js
CHANGED
|
@@ -645,10 +645,21 @@ async function _safeResolveTxt(qname, operatorLookup) {
|
|
|
645
645
|
|
|
646
646
|
function _resetDkimKeyCacheForTest() { DKIM_KEY_CACHE.clear(); }
|
|
647
647
|
|
|
648
|
+
// RFC 8410 Ed25519 SubjectPublicKeyInfo header (algorithm id + BIT STRING
|
|
649
|
+
// wrapper) prepended to a raw 32-byte key to form valid SPKI DER.
|
|
650
|
+
var ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
651
|
+
|
|
648
652
|
function _pemFromB64KeyMaterial(b64) {
|
|
649
|
-
// RSA: SubjectPublicKeyInfo DER in base64. Ed25519:
|
|
650
|
-
//
|
|
651
|
-
//
|
|
653
|
+
// RSA: SubjectPublicKeyInfo DER in base64. Ed25519: RFC 8463 §3-4 publishes
|
|
654
|
+
// the RAW 32-byte key (base64'd) — the canonical form every conformant
|
|
655
|
+
// ed25519 sender uses — which is NOT valid SPKI DER; wrap it in the ed25519
|
|
656
|
+
// SPKI header first. An already-SPKI ed25519 key (44 bytes) or an RSA SPKI
|
|
657
|
+
// key (larger) is passed through unchanged.
|
|
658
|
+
var raw = null;
|
|
659
|
+
try { raw = Buffer.from(b64, "base64"); } catch (_e) { raw = null; }
|
|
660
|
+
if (raw && raw.length === 32) {
|
|
661
|
+
b64 = Buffer.concat([ED25519_SPKI_PREFIX, raw]).toString("base64");
|
|
662
|
+
}
|
|
652
663
|
var pem = "-----BEGIN PUBLIC KEY-----\n";
|
|
653
664
|
// 64-char wrap (PEM convention).
|
|
654
665
|
for (var i = 0; i < b64.length; i += 64) { // PEM wrap width
|
|
@@ -1113,18 +1124,18 @@ async function verify(rfc822, opts) {
|
|
|
1113
1124
|
errors: ["DKIM key record missing p="] });
|
|
1114
1125
|
continue;
|
|
1115
1126
|
}
|
|
1116
|
-
// RFC 6376 §3.6.1 — k= tag declares the key's algorithm family
|
|
1117
|
-
//
|
|
1118
|
-
//
|
|
1119
|
-
//
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1127
|
+
// RFC 6376 §3.6.1 — k= tag declares the key's algorithm family, DEFAULT
|
|
1128
|
+
// "rsa" when absent. If it disagrees with the signature's a= family the
|
|
1129
|
+
// operator who published the key intends a different algorithm; refuse
|
|
1130
|
+
// rather than guess. The default matters: a record `p=<raw-32-byte-key>`
|
|
1131
|
+
// with no k= is an RSA record, and must NOT be read as an Ed25519 key just
|
|
1132
|
+
// because the bytes are 32 long (a key-family confusion).
|
|
1133
|
+
var kFamily = keyTags.k !== undefined ? String(keyTags.k).toLowerCase() : "rsa";
|
|
1134
|
+
var sigFamily = String(alg || "").toLowerCase().split("-")[0];
|
|
1135
|
+
if (kFamily !== sigFamily) {
|
|
1136
|
+
results.push({ d: d, s: s, alg: alg, result: "permerror",
|
|
1137
|
+
errors: ["DKIM key k=" + kFamily + " does not match signature a=" + alg + " (RFC 6376 §3.6.1)"] });
|
|
1138
|
+
continue;
|
|
1128
1139
|
}
|
|
1129
1140
|
var rv = _verifySingleSignature(rfc822, parsedHeaders, sigHeaders[i], keyTags, sigTags, verifyOpts);
|
|
1130
1141
|
results.push(Object.assign({ d: d, s: s, alg: alg }, rv));
|
|
@@ -1310,7 +1321,17 @@ function _bootstrapSingle(algorithm, domain, selector, rsaBits) {
|
|
|
1310
1321
|
}
|
|
1311
1322
|
var publicKeyPemObj = nodeCrypto.createPublicKey({ key: keyPair.publicKey, type: "spki", format: "der" });
|
|
1312
1323
|
var publicKeyPem = publicKeyPemObj.export({ type: "spki", format: "pem" });
|
|
1313
|
-
|
|
1324
|
+
// RFC 8463 §3-4: an ed25519 DKIM key MUST be published as the RAW 32-byte key
|
|
1325
|
+
// (base64'd), not SPKI DER — the SPKI form (60 base64 chars) does not verify
|
|
1326
|
+
// at a conformant receiver. The raw key is the trailing 32 bytes of the SPKI
|
|
1327
|
+
// encoding. RSA keeps the SPKI DER form (RFC 6376).
|
|
1328
|
+
var pBase64;
|
|
1329
|
+
if (k === "ed25519") {
|
|
1330
|
+
var spkiDer = Buffer.from(keyPair.publicKey);
|
|
1331
|
+
pBase64 = spkiDer.subarray(spkiDer.length - 32).toString("base64");
|
|
1332
|
+
} else {
|
|
1333
|
+
pBase64 = Buffer.from(keyPair.publicKey).toString("base64");
|
|
1334
|
+
}
|
|
1314
1335
|
var dnsName = selector + "._domainkey." + domain;
|
|
1315
1336
|
// RFC 6376 §3.6.1 record syntax: v=DKIM1; k=<alg>; p=<base64>
|
|
1316
1337
|
// The optional t/s/g/n/h/k tags omitted (operator can re-edit
|
package/lib/tsa.js
CHANGED
|
@@ -567,9 +567,13 @@ function _assertValidAt(cert, atMs) {
|
|
|
567
567
|
* <code>id-ct-TSTInfo</code>, the message imprint equals the hash of
|
|
568
568
|
* <code>opts.data</code> (or <code>opts.hash</code>), a sent nonce
|
|
569
569
|
* round-trips, the signer cert's extendedKeyUsage is a critical, sole
|
|
570
|
-
* <code>id-kp-timeStamping</code>, and the CMS signature verifies.
|
|
571
|
-
*
|
|
572
|
-
*
|
|
570
|
+
* <code>id-kp-timeStamping</code>, and the CMS signature verifies. The
|
|
571
|
+
* signer certificate is embedded in the token (and therefore
|
|
572
|
+
* attacker-controlled), so <code>opts.trustAnchorsPem</code> is
|
|
573
|
+
* <strong>required by default</strong> — without it a self-signed token
|
|
574
|
+
* would pass on its self-contained signature alone. To accept an
|
|
575
|
+
* unauthenticated timestamp, set <code>opts.allowUntrustedIssuer:true</code>;
|
|
576
|
+
* the result then carries <code>issuerTrusted:false</code>.
|
|
573
577
|
*
|
|
574
578
|
* @opts
|
|
575
579
|
* {
|
|
@@ -577,17 +581,18 @@ function _assertValidAt(cert, atMs) {
|
|
|
577
581
|
* hash: Buffer, // OR a pre-computed digest (with hashAlg)
|
|
578
582
|
* hashAlg: string, // default "SHA-512" — must match the imprint
|
|
579
583
|
* nonce: Buffer, // require the token nonce to match (from buildRequest)
|
|
580
|
-
* trustAnchorsPem: string|string[], // PEM root(s)
|
|
584
|
+
* trustAnchorsPem: string|string[], // PEM trust root(s) to authenticate the TSA — REQUIRED unless allowUntrustedIssuer
|
|
585
|
+
* allowUntrustedIssuer: boolean, // accept a token with no trust anchor (result carries issuerTrusted:false)
|
|
581
586
|
* at: Date, // validity instant for chain check (default: genTime); must be a valid Date
|
|
582
587
|
* }
|
|
583
588
|
*
|
|
584
589
|
* @example
|
|
585
|
-
* var out = b.tsa.verifyToken(resp.token, { data: tarball, hashAlg: "SHA-512", nonce: req.nonce });
|
|
586
|
-
* // → { genTime, policy, serialHex, accuracy, hashAlg, signerCertPem }
|
|
590
|
+
* var out = b.tsa.verifyToken(resp.token, { data: tarball, hashAlg: "SHA-512", nonce: req.nonce, trustAnchorsPem: caPem });
|
|
591
|
+
* // → { genTime, policy, serialHex, accuracy, hashAlg, signerCertPem, issuerTrusted: true }
|
|
587
592
|
*/
|
|
588
593
|
function verifyToken(token, opts) {
|
|
589
594
|
validateOpts.requireObject(opts, "tsa.verifyToken", TsaError);
|
|
590
|
-
validateOpts(opts, ["data", "hash", "hashAlg", "nonce", "trustAnchorsPem", "at"], "tsa.verifyToken");
|
|
595
|
+
validateOpts(opts, ["data", "hash", "hashAlg", "nonce", "trustAnchorsPem", "allowUntrustedIssuer", "at"], "tsa.verifyToken");
|
|
591
596
|
if (opts.data == null && opts.hash == null) {
|
|
592
597
|
throw new TsaError("tsa/no-data", "tsa.verifyToken: pass opts.data or opts.hash to bind the token");
|
|
593
598
|
}
|
|
@@ -654,10 +659,25 @@ function verifyToken(token, opts) {
|
|
|
654
659
|
"tsa.verifyToken: no certificate in the token both carries the timestamping EKU and verifies the signature");
|
|
655
660
|
}
|
|
656
661
|
|
|
657
|
-
// (7)
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
|
|
662
|
+
// (7) Trust anchors REQUIRED by default. The signer certificate is embedded
|
|
663
|
+
// in the token (sd.certificates) and attacker-controlled, so verifying only
|
|
664
|
+
// the self-contained CMS signature + the timestamping EKU proves the token is
|
|
665
|
+
// self-consistent — a forged timestamp (attacker keypair, self-signed leaf
|
|
666
|
+
// with the timeStamping EKU) passes unless the chain is anchored to a
|
|
667
|
+
// configured trust root (RFC 3161 §2.4.2). Require trustAnchorsPem by default;
|
|
668
|
+
// an operator who genuinely wants trust-anchor-free verification must opt in
|
|
669
|
+
// EXPLICITLY (and gets issuerTrusted:false so the unauthenticated posture is
|
|
670
|
+
// visible). Mirrors b.mdoc.verifyIssuerSigned; fail closed.
|
|
671
|
+
var issuerTrusted = opts.trustAnchorsPem !== undefined && opts.trustAnchorsPem !== null;
|
|
672
|
+
if (!issuerTrusted && opts.allowUntrustedIssuer !== true) {
|
|
673
|
+
throw new TsaError("tsa/trust-anchors-required",
|
|
674
|
+
"tsa.verifyToken: opts.trustAnchorsPem is required to authenticate the timestamping TSA " +
|
|
675
|
+
"(the signer certificate is embedded in the token and attacker-controlled); pass trustAnchorsPem, " +
|
|
676
|
+
"or set allowUntrustedIssuer:true to explicitly accept an unauthenticated timestamp (issuerTrusted:false)");
|
|
677
|
+
}
|
|
678
|
+
// Accept a single PEM string or an array — never silently skip chain
|
|
679
|
+
// verification when the caller supplied an anchor in an unexpected shape.
|
|
680
|
+
if (issuerTrusted) {
|
|
661
681
|
var anchors = typeof opts.trustAnchorsPem === "string" ? [opts.trustAnchorsPem] : opts.trustAnchorsPem;
|
|
662
682
|
if (!Array.isArray(anchors) || anchors.length === 0 ||
|
|
663
683
|
!anchors.every(function (a) { return typeof a === "string" && a.length > 0; })) {
|
|
@@ -678,6 +698,7 @@ function verifyToken(token, opts) {
|
|
|
678
698
|
accuracy: tst.accuracy,
|
|
679
699
|
hashAlg: imp.hashName,
|
|
680
700
|
signerCertPem: new nodeCrypto.X509Certificate(signerCertDer).toString(),
|
|
701
|
+
issuerTrusted: issuerTrusted,
|
|
681
702
|
};
|
|
682
703
|
}
|
|
683
704
|
|
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:7533261e-9d68-4674-b24d-8a05ae89f2aa",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-24T06:49:37.276Z",
|
|
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.17.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.17.17",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.17.
|
|
25
|
+
"version": "0.17.17",
|
|
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.17.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.17.17",
|
|
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.17.
|
|
57
|
+
"ref": "@blamejs/core@0.17.17",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|