@blamejs/core 0.12.52 → 0.12.53
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/README.md +1 -1
- package/index.js +2 -0
- package/lib/content-digest.js +189 -0
- 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.12.x
|
|
10
10
|
|
|
11
|
+
- v0.12.53 (2026-05-25) — **`b.contentDigest` — HTTP Content-Digest / Repr-Digest fields (RFC 9530).** Emit and verify the Content-Digest / Repr-Digest HTTP fields so a recipient can detect a corrupted or tampered message body. b.contentDigest.create builds the RFC 8941 dictionary value (sha-256=:base64:, sha-512=:base64:) over a body; b.contentDigest.verify recomputes each modern digest over the body and compares it in constant time. Only SHA-256 and SHA-512 are computed — the legacy algorithms RFC 9530 §6 marks insecure (MD5, SHA-1, the unix checksums) are ignored on verify, and a field carrying no modern digest is refused, so an attacker cannot downgrade integrity to an MD5-only digest. Content-Digest is the integrity companion to HTTP Message Signatures (b.httpSig, RFC 9421): sign the digest rather than the whole body. Verified against the RFC 9530 Appendix D worked examples. **Added:** *`b.contentDigest.create(body, opts?)` / `b.contentDigest.verify(fieldValue, body, opts?)`* — `create` returns a Content-Digest / Repr-Digest field value over the body — SHA-256 by default, or any subset of `["sha-256","sha-512"]` via `opts.algorithms` — and refuses insecure or unknown algorithms. `verify` parses the field, recomputes each SHA-256 / SHA-512 entry over the body, and compares constant-time; it throws `content-digest/mismatch` on any mismatch, ignores legacy / unknown entries, throws `content-digest/no-modern-digest` if the field has no SHA-256 / SHA-512 entry at all, and honours `opts.required` to force specific algorithms to be present and match. Composes the framework's structured-field helpers and constant-time compare; Repr-Digest is the same machinery over the selected representation (RFC 9110).
|
|
12
|
+
|
|
11
13
|
- v0.12.52 (2026-05-25) — **`b.privacyPass` — Privacy Pass origin-side token verification (RFC 9577 / 9578).** Anonymous, publicly verifiable authorization: an origin issues a WWW-Authenticate: PrivateToken challenge and verifies a presented token cryptographically, without learning who the client is and without a callback to the issuer. b.privacyPass implements the publicly verifiable token type 0x0002 (Blind RSA, 2048-bit): the token's authenticator is an RSA Blind Signature (RFC 9474) checked as RSASSA-PSS (SHA-384, 48-byte salt) over token_input = token_type ‖ nonce ‖ challenge_digest ‖ token_key_id, using only the issuer's public key. The token is bound to that key (token_key_id) and, optionally, to the challenge it answers, so a token minted for another origin is refused. Blind RSA is the algorithm Privacy Pass defines on the wire — like the DNSSEC / DANE verifiers it validates an external protocol's signatures rather than introducing classical crypto as a framework default. Verified against the RFC 9578 §8.2 test vector. **Added:** *`b.privacyPass.verifyToken(opts)` / `parseToken` / `buildChallenge`* — `buildChallenge` builds a TokenChallenge (RFC 9577 §2.1) and the matching `WWW-Authenticate: PrivateToken challenge=…, token-key=…` header an origin returns to request a token, scoped to an issuer (and optionally an origin and a 32-byte redemption context). `parseToken` splits a token into its fields (type / nonce / challenge_digest / token_key_id / authenticator). `verifyToken` verifies a type 0x0002 (Blind RSA) token: it confirms the token's `token_key_id` is the SHA-256 of the supplied issuer public key, optionally that its `challenge_digest` matches `opts.challenge`, and that the authenticator is a valid RSASSA-PSS signature over the token input. Refuses unknown / privately verifiable token types (the VOPRF type 0x0001 needs the issuer secret and is an issuer-side operation), key-id and challenge mismatches, and tampered authenticators. Marked experimental while the issuance protocols see deployment.
|
|
12
14
|
|
|
13
15
|
- v0.12.51 (2026-05-25) — **`b.network.dns.dane.matchCertificate` — DANE / TLSA certificate matching (RFC 6698 / 7671).** Pin a service's certificate through DNS instead of a public CA. matchCertificate checks a server certificate against a set of TLSA records: the selected data — the full certificate (selector 0) or its subjectPublicKeyInfo (selector 1) — is hashed per the matching type (exact / SHA-256 / SHA-512) and compared in constant time to the record's association data. For a DANE-EE (usage 3) record a match is self-authenticating — the TLSA pins the key, so no public-CA path is needed (the common SMTP-DANE case, RFC 7672); for the PKIX usages a match is reported as necessary-but-not-sufficient so the caller still runs PKIX. This is the payoff of the DNSSEC verifier: verify the TLSA RRset with b.network.dns.dnssec, then match the certificate. Verified against a live DNSSEC-signed TLSA record and the matching server certificate. **Added:** *`b.network.dns.dane.matchCertificate(opts)`* — Matches a leaf certificate (and optional `chain`) against a TLSA RRset (`{ usage, selector, matchingType, data }`). Selector 0 hashes the full certificate DER, selector 1 the subjectPublicKeyInfo; matching type 0 is an exact comparison, 1 SHA-256, 2 SHA-512 (SHA-1 and any other type are refused, not guessed). End-entity usages (PKIX-EE 1, DANE-EE 3) match the leaf; trust-anchor usages (PKIX-TA 0, DANE-TA 2) match the leaf or any supplied chain certificate. Returns `{ ok, matched, daneAuthenticated, trustAnchorMatch, pkixRequired, matchedCertIndex }` — `daneAuthenticated` is true only for a DANE-EE match (the key is pinned, no CA needed); `pkixRequired` flags the PKIX usages. Throws `dane/no-match` when nothing matches, and refuses unknown usage / selector / matching values and unparseable certificates. Verify the TLSA RRset with `b.network.dns.dnssec` first — an unauthenticated TLSA record proves nothing.
|
package/README.md
CHANGED
|
@@ -97,7 +97,7 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
|
|
|
97
97
|
- **Field-level + crypto-shred** — `b.cryptoField.eraseRow`; per-column data residency tagging + per-row keys (`K_row = HKDF(K_table, rowId)`) so erasing the per-row key makes WAL / replica residuals undecryptable (`b.cryptoField.declareColumnResidency`, `b.cryptoField.declarePerRowKey`)
|
|
98
98
|
- **AAD-bound sealed columns** — AEAD tag tied to `(table, rowId, column, schemaVersion)`; copy-paste between rows or schema-version replay surfaces as refused decrypt (`b.vault.aad`)
|
|
99
99
|
- **Signed webhooks + API encryption** — SLH-DSA-SHAKE-256f default; ML-DSA-65 opt-in; ECIES API encryption (`b.webhook`, `b.crypto`)
|
|
100
|
-
- **HPKE / HTTP signatures** — RFC 9180 HPKE with ML-KEM-1024 + HKDF-SHA3-512 + ChaCha20-Poly1305 (`b.crypto.hpke`); RFC 9421 HTTP Message Signatures with derived components and ed25519 / ML-DSA-65 (`b.crypto.httpSig`)
|
|
100
|
+
- **HPKE / HTTP signatures** — RFC 9180 HPKE with ML-KEM-1024 + HKDF-SHA3-512 + ChaCha20-Poly1305 (`b.crypto.hpke`); RFC 9421 HTTP Message Signatures with derived components and ed25519 / ML-DSA-65 (`b.crypto.httpSig`); RFC 9530 Content-Digest / Repr-Digest body-integrity fields (SHA-256 / SHA-512, legacy algorithms refused — `b.contentDigest`) to sign the digest rather than the whole body
|
|
101
101
|
- **CMS codec** — RFC 5652 Cryptographic Message Syntax encoder + decoder with PQC signers (ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f; RFC 9909 + 9881) and KEMRecipientInfo recipients (ML-KEM-1024; RFC 9629 + 9936); ChaCha20-Poly1305 content encryption (RFC 8103) so Efail-class malleability cannot apply (`b.cms`)
|
|
102
102
|
- **Stream throttle** — shared token-bucket bandwidth limiter (RFC 2697 srTCM shape); N concurrent `node:stream` pipelines draw from one operator-configured `bytesPerSec` budget (`b.streamThrottle`)
|
|
103
103
|
- **TLS-RPT receiver** — RFC 8460 inbound aggregate-report ingest; HTTPS POST handler + §4.4 schema parser with gzip-bomb / ratio-bomb / depth-bomb defenses (`b.mail.deploy.parseTlsRptReport` / `b.mail.deploy.tlsRptIngestHttp`)
|
package/index.js
CHANGED
|
@@ -395,6 +395,7 @@ var fedcm = require("./lib/fedcm");
|
|
|
395
395
|
var dbsc = require("./lib/dbsc");
|
|
396
396
|
var importmapIntegrity = require("./lib/importmap-integrity");
|
|
397
397
|
var privacyPass = require("./lib/privacy-pass");
|
|
398
|
+
var contentDigest = require("./lib/content-digest");
|
|
398
399
|
var standardWebhooks = require("./lib/standard-webhooks");
|
|
399
400
|
var lro = require("./lib/lro");
|
|
400
401
|
var jsonApi = require("./lib/jsonapi");
|
|
@@ -411,6 +412,7 @@ module.exports = {
|
|
|
411
412
|
dbsc: dbsc,
|
|
412
413
|
importmapIntegrity: importmapIntegrity,
|
|
413
414
|
privacyPass: privacyPass,
|
|
415
|
+
contentDigest: contentDigest,
|
|
414
416
|
standardWebhooks: standardWebhooks,
|
|
415
417
|
lro: lro,
|
|
416
418
|
jsonApi: jsonApi,
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.contentDigest
|
|
4
|
+
* @nav HTTP
|
|
5
|
+
* @title Content-Digest
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* HTTP Digest Fields (RFC 9530) — emit and verify the
|
|
9
|
+
* <code>Content-Digest</code> / <code>Repr-Digest</code> fields that
|
|
10
|
+
* carry a hash of a message body so a recipient can detect corruption
|
|
11
|
+
* or tampering in transit. The field is an RFC 8941 dictionary of
|
|
12
|
+
* <code>algorithm=:base64-digest:</code> entries; this module computes
|
|
13
|
+
* and checks the modern algorithms (SHA-256, SHA-512) and ignores the
|
|
14
|
+
* legacy ones (MD5, SHA-1, the unix checksums) that RFC 9530 §6 marks
|
|
15
|
+
* insecure — refusing to accept a body whose only digest is a legacy
|
|
16
|
+
* algorithm.
|
|
17
|
+
*
|
|
18
|
+
* Content-Digest is the integrity companion to HTTP Message Signatures
|
|
19
|
+
* (<code>b.httpSig</code>, RFC 9421): rather than signing a whole body,
|
|
20
|
+
* sign its <code>Content-Digest</code> and let this module bind the
|
|
21
|
+
* digest to the bytes.
|
|
22
|
+
*
|
|
23
|
+
* @card
|
|
24
|
+
* HTTP Content-Digest / Repr-Digest (RFC 9530). Emit and verify a
|
|
25
|
+
* SHA-256 / SHA-512 digest of a message body; legacy algorithms are
|
|
26
|
+
* ignored and a body with no modern digest is refused. Pairs with
|
|
27
|
+
* <code>b.httpSig</code> — sign the digest, not the bytes.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
var nodeCrypto = require("node:crypto");
|
|
31
|
+
var bCrypto = require("./crypto");
|
|
32
|
+
var structuredFields = require("./structured-fields");
|
|
33
|
+
var validateOpts = require("./validate-opts");
|
|
34
|
+
var { defineClass } = require("./framework-error");
|
|
35
|
+
|
|
36
|
+
var ContentDigestError = defineClass("ContentDigestError", { alwaysPermanent: true });
|
|
37
|
+
|
|
38
|
+
// RFC 9530 IANA "Hash Algorithms for HTTP Digest Fields": Active vs
|
|
39
|
+
// (insecure) deprecated. Active algorithms map to a Node hash name.
|
|
40
|
+
var ACTIVE = { "sha-256": "sha256", "sha-512": "sha512" };
|
|
41
|
+
var DEPRECATED = { "md5": 1, "sha": 1, "unixsum": 1, "unixcksum": 1, "adler": 1, "crc32c": 1 };
|
|
42
|
+
|
|
43
|
+
// Decode an RFC 8941 Byte Sequence payload as STRICT, canonical base64.
|
|
44
|
+
// Node's base64 decoder silently drops invalid characters and tolerates
|
|
45
|
+
// bad padding, so `:<digest>!!!!:` or non-canonical padding would decode
|
|
46
|
+
// to the same bytes and wrongly verify — a real risk when the
|
|
47
|
+
// Content-Digest field is itself covered by an HTTP Message Signature.
|
|
48
|
+
// Decoding then re-encoding and requiring the exact input back rejects
|
|
49
|
+
// stray characters, whitespace, wrong padding, and non-zero trailing
|
|
50
|
+
// bits in one canonical check (Node always re-emits canonical base64).
|
|
51
|
+
function _strictBase64(s, what) {
|
|
52
|
+
if (typeof s !== "string" || s.length === 0) {
|
|
53
|
+
throw new ContentDigestError("content-digest/bad-field", "contentDigest: " + what + " is empty");
|
|
54
|
+
}
|
|
55
|
+
var buf = Buffer.from(s, "base64");
|
|
56
|
+
if (buf.length === 0 || buf.toString("base64") !== s) {
|
|
57
|
+
throw new ContentDigestError("content-digest/bad-field", "contentDigest: " + what + " is not canonical base64");
|
|
58
|
+
}
|
|
59
|
+
return buf;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function _bodyBytes(body, what) {
|
|
63
|
+
if (Buffer.isBuffer(body)) return body;
|
|
64
|
+
if (body instanceof Uint8Array) return Buffer.from(body);
|
|
65
|
+
if (typeof body === "string") return Buffer.from(body, "utf8");
|
|
66
|
+
throw new ContentDigestError("content-digest/bad-body", "contentDigest: " + what + " must be a Buffer / Uint8Array / string");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @primitive b.contentDigest.create
|
|
71
|
+
* @signature b.contentDigest.create(body, opts?)
|
|
72
|
+
* @since 0.12.53
|
|
73
|
+
* @status stable
|
|
74
|
+
* @compliance soc2
|
|
75
|
+
* @related b.contentDigest.verify, b.httpSig
|
|
76
|
+
*
|
|
77
|
+
* Build a <code>Content-Digest</code> (or <code>Repr-Digest</code>) field
|
|
78
|
+
* value over a message body (RFC 9530 §2): an RFC 8941 dictionary of
|
|
79
|
+
* <code>algorithm=:base64(digest):</code> members. Defaults to SHA-256;
|
|
80
|
+
* pass <code>algorithms</code> to emit several. Only the modern
|
|
81
|
+
* algorithms are offered — the digest is over the exact body bytes.
|
|
82
|
+
*
|
|
83
|
+
* @opts
|
|
84
|
+
* {
|
|
85
|
+
* algorithms: string[], // subset of ["sha-256","sha-512"]; default ["sha-256"]
|
|
86
|
+
* }
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* b.contentDigest.create('{"hello": "world"}');
|
|
90
|
+
* // → "sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:"
|
|
91
|
+
*/
|
|
92
|
+
function create(body, opts) {
|
|
93
|
+
opts = opts || {};
|
|
94
|
+
validateOpts.requireObject(opts, "contentDigest.create", ContentDigestError);
|
|
95
|
+
validateOpts(opts, ["algorithms"], "contentDigest.create");
|
|
96
|
+
var bytes = _bodyBytes(body, "body");
|
|
97
|
+
var algos = opts.algorithms === undefined ? ["sha-256"] : opts.algorithms;
|
|
98
|
+
if (!Array.isArray(algos) || algos.length === 0) throw new ContentDigestError("content-digest/bad-arg", "contentDigest.create: opts.algorithms must be a non-empty array");
|
|
99
|
+
var members = algos.map(function (a) {
|
|
100
|
+
var name = String(a).toLowerCase();
|
|
101
|
+
var nodeAlg = ACTIVE[name];
|
|
102
|
+
if (!nodeAlg) {
|
|
103
|
+
if (DEPRECATED[name]) throw new ContentDigestError("content-digest/insecure-algorithm", "contentDigest.create: '" + name + "' is a deprecated/insecure digest algorithm (RFC 9530 §6); use sha-256 or sha-512");
|
|
104
|
+
throw new ContentDigestError("content-digest/unsupported-algorithm", "contentDigest.create: unsupported digest algorithm '" + name + "'");
|
|
105
|
+
}
|
|
106
|
+
var digest = nodeCrypto.createHash(nodeAlg).update(bytes).digest("base64");
|
|
107
|
+
return name + "=:" + digest + ":"; // RFC 8941 Byte Sequence value
|
|
108
|
+
});
|
|
109
|
+
return members.join(", ");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @primitive b.contentDigest.verify
|
|
114
|
+
* @signature b.contentDigest.verify(fieldValue, body, opts?)
|
|
115
|
+
* @since 0.12.53
|
|
116
|
+
* @status stable
|
|
117
|
+
* @compliance soc2
|
|
118
|
+
* @related b.contentDigest.create, b.httpSig
|
|
119
|
+
*
|
|
120
|
+
* Verify a <code>Content-Digest</code> / <code>Repr-Digest</code> field
|
|
121
|
+
* value against a body (RFC 9530). Every modern (SHA-256 / SHA-512) entry
|
|
122
|
+
* is recomputed over the body and compared in constant time; a mismatch
|
|
123
|
+
* is refused. Legacy / unknown algorithms are ignored, but a field that
|
|
124
|
+
* carries <em>no</em> modern digest is refused (so an attacker cannot
|
|
125
|
+
* downgrade to an MD5-only digest). <code>opts.required</code> forces
|
|
126
|
+
* specific algorithms to be present and to match.
|
|
127
|
+
*
|
|
128
|
+
* @opts
|
|
129
|
+
* {
|
|
130
|
+
* required: string[], // algorithms that MUST be present and match (e.g. ["sha-256"])
|
|
131
|
+
* }
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* b.contentDigest.verify("sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:", '{"hello": "world"}');
|
|
135
|
+
* // → { ok: true, verified: ["sha-256"] }
|
|
136
|
+
*/
|
|
137
|
+
function verify(fieldValue, body, opts) {
|
|
138
|
+
opts = opts || {};
|
|
139
|
+
validateOpts.requireObject(opts, "contentDigest.verify", ContentDigestError);
|
|
140
|
+
validateOpts(opts, ["required"], "contentDigest.verify");
|
|
141
|
+
if (typeof fieldValue !== "string" || fieldValue.trim() === "") throw new ContentDigestError("content-digest/bad-field", "contentDigest.verify: fieldValue must be a non-empty string");
|
|
142
|
+
structuredFields.refuseControlBytes(fieldValue, { ErrorClass: ContentDigestError, code: "content-digest/bad-field", label: "contentDigest fieldValue" });
|
|
143
|
+
var bytes = _bodyBytes(body, "body");
|
|
144
|
+
|
|
145
|
+
var members = structuredFields.splitTopLevel(fieldValue, ",");
|
|
146
|
+
var seen = Object.create(null);
|
|
147
|
+
var verified = [];
|
|
148
|
+
for (var i = 0; i < members.length; i++) {
|
|
149
|
+
var m = members[i].trim();
|
|
150
|
+
if (m === "") continue;
|
|
151
|
+
var eq = m.indexOf("=");
|
|
152
|
+
if (eq < 1) throw new ContentDigestError("content-digest/bad-field", "contentDigest.verify: malformed dictionary member");
|
|
153
|
+
var name = m.slice(0, eq).trim().toLowerCase();
|
|
154
|
+
var raw = m.slice(eq + 1).trim();
|
|
155
|
+
var nodeAlg = ACTIVE[name];
|
|
156
|
+
if (!nodeAlg) continue; // ignore legacy / unknown entries
|
|
157
|
+
if (raw.length < 2 || raw.charAt(0) !== ":" || raw.charAt(raw.length - 1) !== ":") {
|
|
158
|
+
throw new ContentDigestError("content-digest/bad-field", "contentDigest.verify: '" + name + "' value is not an RFC 8941 byte sequence (:base64:)");
|
|
159
|
+
}
|
|
160
|
+
var claimed = _strictBase64(raw.slice(1, -1), name + " digest");
|
|
161
|
+
var actual = nodeCrypto.createHash(nodeAlg).update(bytes).digest();
|
|
162
|
+
if (!bCrypto.timingSafeEqual(actual, claimed)) {
|
|
163
|
+
throw new ContentDigestError("content-digest/mismatch", "contentDigest.verify: " + name + " digest does not match the body");
|
|
164
|
+
}
|
|
165
|
+
seen[name] = 1;
|
|
166
|
+
verified.push(name);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (opts.required !== undefined && opts.required !== null) {
|
|
170
|
+
if (!Array.isArray(opts.required)) throw new ContentDigestError("content-digest/bad-arg", "contentDigest.verify: opts.required must be an array");
|
|
171
|
+
for (var r = 0; r < opts.required.length; r++) {
|
|
172
|
+
var req = String(opts.required[r]).toLowerCase();
|
|
173
|
+
if (!ACTIVE[req]) throw new ContentDigestError("content-digest/unsupported-algorithm", "contentDigest.verify: required algorithm '" + req + "' is not a modern digest");
|
|
174
|
+
if (!seen[req]) throw new ContentDigestError("content-digest/missing-algorithm", "contentDigest.verify: required digest '" + req + "' is not present");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (verified.length === 0) {
|
|
179
|
+
throw new ContentDigestError("content-digest/no-modern-digest", "contentDigest.verify: no modern (sha-256 / sha-512) digest present — refusing to trust a legacy-only digest");
|
|
180
|
+
}
|
|
181
|
+
return { ok: true, verified: verified };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
create: create,
|
|
186
|
+
verify: verify,
|
|
187
|
+
ACTIVE_ALGORITHMS: Object.keys(ACTIVE),
|
|
188
|
+
ContentDigestError: ContentDigestError,
|
|
189
|
+
};
|
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:20868c28-f68f-42c3-a926-c9e46216c41e",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-25T18:23:33.227Z",
|
|
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.12.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.12.53",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.12.
|
|
25
|
+
"version": "0.12.53",
|
|
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.12.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.12.53",
|
|
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.12.
|
|
57
|
+
"ref": "@blamejs/core@0.12.53",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|