@blamejs/core 0.8.59 → 0.8.64

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.
@@ -0,0 +1,499 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.xmlC14n
4
+ * @nav Crypto
5
+ * @title XML Exclusive Canonicalization
6
+ * @order 560
7
+ * @card RFC 3741 Exclusive XML Canonicalization 1.0 — the
8
+ * canonical form XMLDSig requires, and the missing piece
9
+ * that lets `b.guardXml` defend against XML signature-
10
+ * wrapping attacks.
11
+ *
12
+ * @intro
13
+ * XML signatures cover canonicalized bytes, not the source XML.
14
+ * Two structurally-equivalent XML documents (different attribute
15
+ * ordering, different namespace prefixes, different whitespace)
16
+ * must produce the same canonical bytes; otherwise an attacker
17
+ * could swap a benign signed assertion for a malicious one whose
18
+ * parsed tree is identical but whose serialized bytes differ.
19
+ *
20
+ * This module implements the SAML/XMLDSig-relevant subset of
21
+ * RFC 3741 Exclusive XML Canonicalization 1.0 plus
22
+ * `xml-exc-c14n#WithComments` (controlled via opts).
23
+ *
24
+ * What's covered (the v1-defensible SAML/SP subset):
25
+ *
26
+ * - UTF-8 output with no BOM
27
+ * - Element + attribute serialization with `&`, `<`, `>`, `"`,
28
+ * `\r`, `\t`, `\n` proper escaping per §1.3.2
29
+ * - Attribute ordering: namespace declarations first (alphabetical
30
+ * by namespace prefix, `xmlns` before `xmlns:foo`); regular
31
+ * attributes second (by namespace URI, then local name)
32
+ * - Exclusive namespace propagation: only the namespace prefixes
33
+ * *visibly used* by the canonicalized subtree are emitted
34
+ * - Empty elements expanded (`<a/>` → `<a></a>`)
35
+ * - Whitespace normalization in attribute values
36
+ * - Comments suppressed by default; `withComments: true` keeps
37
+ * them per `xml-exc-c14n#WithComments`
38
+ *
39
+ * What's NOT covered (deferred — open conditions on first
40
+ * operator demand or live SAML interop need):
41
+ *
42
+ * - `InclusiveNamespaces PrefixList` (the `<ec:InclusiveNamespaces
43
+ * PrefixList="..."/>` Transform parameter — we always operate
44
+ * in the strict exclusive mode without an inclusive list).
45
+ * - Inherited XML namespace propagation for `xml:lang`,
46
+ * `xml:space`, `xml:base` past the canonicalization boundary.
47
+ *
48
+ * Surface:
49
+ *
50
+ * b.xmlC14n.canonicalize(xmlString | parsedTree, opts?)
51
+ * → Buffer of canonicalized UTF-8 bytes
52
+ * b.xmlC14n.canonicalizeElementById(xmlString, id, opts?)
53
+ * → Buffer of c14n'd bytes for the element whose `ID="<id>"`
54
+ * attribute matches (used by XMLDSig Reference resolution)
55
+ * b.xmlC14n.parse(xmlString) → DOM tree (used by SAML)
56
+ */
57
+
58
+ var validateOpts = require("./validate-opts");
59
+ var { defineClass } = require("./framework-error");
60
+
61
+ var XmlC14nError = defineClass("XmlC14nError", { alwaysPermanent: true });
62
+ function _xmlErr(code, message) { return new XmlC14nError(code, message); }
63
+
64
+ var MAX_INPUT_BYTES = 8 * 1024 * 1024; // allow:raw-byte-literal — XML doc cap (8 MiB)
65
+ var MAX_DEPTH = 200; // allow:raw-byte-literal — element nesting depth ceiling
66
+
67
+ /**
68
+ * @primitive b.xmlC14n.parse
69
+ * @signature b.xmlC14n.parse(xml)
70
+ * @since 0.8.62
71
+ * @status stable
72
+ * @related b.xmlC14n.canonicalize, b.xmlC14n.canonicalizeElementById
73
+ *
74
+ * Lightweight DOM parser: produces a simple node tree with
75
+ * `{ type, name, attrs, children, parent }`. Node types: "element"
76
+ * / "text" / "comment". The parser is strict about what it refuses
77
+ * (DOCTYPE, ENTITY, malformed entity references); XML c14n is a
78
+ * security primitive and an over-permissive parser undermines the
79
+ * canonicalization guarantees downstream. Operators rarely call
80
+ * this directly — `canonicalize` and `canonicalizeElementById`
81
+ * accept either a string OR a parsed node, so the parsed-tree path
82
+ * is exposed mainly for the SAML primitive's signature-element
83
+ * lookup and operator-side custom traversal.
84
+ *
85
+ * @example
86
+ * var tree = b.xmlC14n.parse("<root><child id=\"x\"/></root>");
87
+ * tree.type; // → "element"
88
+ * tree.name; // → "root"
89
+ * tree.children[0].name;// → "child"
90
+ */
91
+ function parse(xml) {
92
+ if (typeof xml !== "string") {
93
+ if (Buffer.isBuffer(xml)) xml = xml.toString("utf8");
94
+ else throw _xmlErr("xml-c14n/bad-input", "parse: input must be a string or Buffer");
95
+ }
96
+ if (xml.length === 0) throw _xmlErr("xml-c14n/empty", "parse: input is empty");
97
+ if (xml.length > MAX_INPUT_BYTES) {
98
+ throw _xmlErr("xml-c14n/too-large",
99
+ "parse: input exceeds " + MAX_INPUT_BYTES + " bytes");
100
+ }
101
+ // Strip BOM if present
102
+ if (xml.charCodeAt(0) === 0xFEFF) xml = xml.slice(1);
103
+ // Refuse DOCTYPE outright (XXE, billion-laughs class)
104
+ if (/<!DOCTYPE/i.test(xml)) {
105
+ throw _xmlErr("xml-c14n/doctype-refused",
106
+ "parse: <!DOCTYPE> declarations refused for canonicalization input");
107
+ }
108
+ if (/<!ENTITY/i.test(xml)) {
109
+ throw _xmlErr("xml-c14n/entity-refused",
110
+ "parse: <!ENTITY> declarations refused");
111
+ }
112
+
113
+ var pos = 0;
114
+ function err(msg) {
115
+ throw _xmlErr("xml-c14n/parse", "parse: " + msg + " at offset " + pos);
116
+ }
117
+
118
+ function skipWhitespace() {
119
+ while (pos < xml.length && /\s/.test(xml.charAt(pos))) pos += 1;
120
+ }
121
+
122
+ function skipProlog() {
123
+ skipWhitespace();
124
+ while (xml.substr(pos, 5) === "<?xml" ||
125
+ xml.substr(pos, 4) === "<!--" ||
126
+ xml.substr(pos, 2) === "<?") {
127
+ if (xml.substr(pos, 4) === "<!--") {
128
+ var end = xml.indexOf("-->", pos);
129
+ if (end === -1) err("unterminated comment in prolog");
130
+ pos = end + 3;
131
+ } else {
132
+ var endPi = xml.indexOf("?>", pos);
133
+ if (endPi === -1) err("unterminated processing instruction");
134
+ pos = endPi + 2;
135
+ }
136
+ skipWhitespace();
137
+ }
138
+ }
139
+
140
+ function readName() {
141
+ var start = pos;
142
+ if (!/[A-Za-z_:]/.test(xml.charAt(pos))) err("expected name");
143
+ pos += 1;
144
+ while (pos < xml.length && /[A-Za-z0-9._:-]/.test(xml.charAt(pos))) pos += 1;
145
+ return xml.slice(start, pos);
146
+ }
147
+
148
+ function readAttrValue() {
149
+ var quote = xml.charAt(pos);
150
+ if (quote !== "\"" && quote !== "'") err("attribute value must be quoted");
151
+ pos += 1;
152
+ var start = pos;
153
+ while (pos < xml.length && xml.charAt(pos) !== quote) {
154
+ if (xml.charAt(pos) === "<") err("'<' not allowed in attribute value");
155
+ pos += 1;
156
+ }
157
+ if (pos >= xml.length) err("unterminated attribute value");
158
+ var raw = xml.slice(start, pos);
159
+ pos += 1; // closing quote
160
+ return _decodeEntities(raw);
161
+ }
162
+
163
+ function _decodeEntities(s) {
164
+ return s.replace(/&([^;]+);/g, function (match, name) {
165
+ switch (name) {
166
+ case "amp": return "&";
167
+ case "lt": return "<";
168
+ case "gt": return ">";
169
+ case "quot": return "\"";
170
+ case "apos": return "'";
171
+ default:
172
+ if (name.charAt(0) === "#") {
173
+ var code;
174
+ if (name.charAt(1) === "x" || name.charAt(1) === "X") {
175
+ code = parseInt(name.slice(2), 16); // allow:raw-byte-literal — hex radix
176
+ } else {
177
+ code = parseInt(name.slice(1), 10);
178
+ }
179
+ if (Number.isFinite(code) && code >= 0 && code <= 0x10ffff) {
180
+ return String.fromCodePoint(code);
181
+ }
182
+ }
183
+ throw _xmlErr("xml-c14n/unknown-entity",
184
+ "decodeEntities: unsupported entity reference \"&" + name + ";\"");
185
+ }
186
+ });
187
+ }
188
+
189
+ function parseElement() {
190
+ if (xml.charAt(pos) !== "<") err("expected '<'");
191
+ pos += 1;
192
+ var name = readName();
193
+ var attrs = [];
194
+ var selfClosing = false;
195
+ skipWhitespace();
196
+ while (pos < xml.length && xml.charAt(pos) !== ">" && xml.charAt(pos) !== "/") {
197
+ var attrName = readName();
198
+ skipWhitespace();
199
+ if (xml.charAt(pos) !== "=") err("expected '=' after attribute name");
200
+ pos += 1;
201
+ skipWhitespace();
202
+ var value = readAttrValue();
203
+ attrs.push({ name: attrName, value: value });
204
+ skipWhitespace();
205
+ }
206
+ if (xml.charAt(pos) === "/") {
207
+ selfClosing = true;
208
+ pos += 1;
209
+ if (xml.charAt(pos) !== ">") err("expected '>' after '/'");
210
+ }
211
+ if (xml.charAt(pos) !== ">") err("expected '>'");
212
+ pos += 1;
213
+ var node = {
214
+ type: "element",
215
+ name: name,
216
+ attrs: attrs,
217
+ children: [],
218
+ parent: null,
219
+ };
220
+ if (selfClosing) return node;
221
+
222
+ var depth = 0;
223
+ var closeTag = "</" + name + ">";
224
+ while (pos < xml.length) {
225
+ if (xml.substr(pos, 4) === "<!--") {
226
+ var endC = xml.indexOf("-->", pos);
227
+ if (endC === -1) err("unterminated comment");
228
+ node.children.push({ type: "comment", text: xml.slice(pos + 4, endC), parent: node });
229
+ pos = endC + 3;
230
+ } else if (xml.charAt(pos) === "<" && xml.charAt(pos + 1) !== "/") {
231
+ if (xml.substr(pos, 9) === "<![CDATA[") {
232
+ var endCData = xml.indexOf("]]>", pos);
233
+ if (endCData === -1) err("unterminated CDATA");
234
+ node.children.push({ type: "text", text: xml.slice(pos + 9, endCData), parent: node, isCdata: true });
235
+ pos = endCData + 3;
236
+ } else if (xml.charAt(pos + 1) === "?") {
237
+ var endPi = xml.indexOf("?>", pos);
238
+ if (endPi === -1) err("unterminated PI");
239
+ // Skip — c14n drops PIs outside the canonicalized subtree
240
+ // boundary; we can include them but operators don't need
241
+ // them for SAML.
242
+ pos = endPi + 2;
243
+ } else {
244
+ depth += 1;
245
+ if (depth > MAX_DEPTH) err("max nesting depth (" + MAX_DEPTH + ") exceeded");
246
+ var child = parseElement();
247
+ child.parent = node;
248
+ node.children.push(child);
249
+ depth -= 1;
250
+ }
251
+ } else if (xml.charAt(pos) === "<" && xml.charAt(pos + 1) === "/") {
252
+ // Closing tag
253
+ if (xml.substr(pos, closeTag.length) !== closeTag) {
254
+ err("expected </" + name + ">");
255
+ }
256
+ pos += closeTag.length;
257
+ return node;
258
+ } else {
259
+ // Text content
260
+ var textStart = pos;
261
+ while (pos < xml.length && xml.charAt(pos) !== "<") pos += 1;
262
+ var text = _decodeEntities(xml.slice(textStart, pos));
263
+ node.children.push({ type: "text", text: text, parent: node });
264
+ }
265
+ }
266
+ err("unterminated element </" + name + ">");
267
+ }
268
+
269
+ skipProlog();
270
+ if (pos >= xml.length) err("no root element");
271
+ var root = parseElement();
272
+ return root;
273
+ }
274
+
275
+ // Build a namespace map { prefix → uri } for an element by walking
276
+ // from root to it; the empty prefix is the default namespace.
277
+ function _namespacesInScope(node) {
278
+ var stack = [];
279
+ var cur = node;
280
+ while (cur) { stack.unshift(cur); cur = cur.parent; }
281
+ var nsMap = {};
282
+ stack.forEach(function (el) {
283
+ if (!el.attrs) return;
284
+ el.attrs.forEach(function (a) {
285
+ if (a.name === "xmlns") nsMap[""] = a.value;
286
+ else if (a.name.indexOf("xmlns:") === 0) nsMap[a.name.substring(6)] = a.value;
287
+ });
288
+ });
289
+ return nsMap;
290
+ }
291
+
292
+ // Determine the prefixes a given element uses *visibly*: its own
293
+ // element prefix, any attribute prefixes, and the default namespace
294
+ // when the element has no explicit prefix.
295
+ function _prefixesUsedByElement(element) {
296
+ var used = new Set();
297
+ var elementPrefix = "";
298
+ var colon = element.name.indexOf(":");
299
+ if (colon !== -1) elementPrefix = element.name.substring(0, colon);
300
+ used.add(elementPrefix);
301
+ element.attrs.forEach(function (a) {
302
+ if (a.name === "xmlns" || a.name.indexOf("xmlns:") === 0) return;
303
+ var ac = a.name.indexOf(":");
304
+ if (ac !== -1) used.add(a.name.substring(0, ac));
305
+ });
306
+ return used;
307
+ }
308
+
309
+ function _escapeAttrValue(s) {
310
+ // Per RFC 3741 §1.3.2: "&" → "&amp;", "<" → "&lt;", `"` → "&quot;",
311
+ // \r → "&#xD;", \n → "&#xA;", \t → "&#x9;".
312
+ return String(s)
313
+ .replace(/&/g, "&amp;")
314
+ .replace(/</g, "&lt;")
315
+ .replace(/"/g, "&quot;")
316
+ .replace(/\r/g, "&#xD;")
317
+ .replace(/\n/g, "&#xA;")
318
+ .replace(/\t/g, "&#x9;");
319
+ }
320
+
321
+ function _escapeText(s) {
322
+ // Per RFC 3741 §1.3.1: "&" → "&amp;", "<" → "&lt;", ">" → "&gt;",
323
+ // \r → "&#xD;".
324
+ return String(s)
325
+ .replace(/&/g, "&amp;")
326
+ .replace(/</g, "&lt;")
327
+ .replace(/>/g, "&gt;")
328
+ .replace(/\r/g, "&#xD;");
329
+ }
330
+
331
+ function _serializeNode(node, ancestorRendered, withComments) {
332
+ if (node.type === "text") return _escapeText(node.text);
333
+ if (node.type === "comment") return withComments ? "<!--" + node.text + "-->" : "";
334
+ if (node.type !== "element") return "";
335
+
336
+ // Compute the namespace map visible from this element's scope.
337
+ var inScope = _namespacesInScope(node);
338
+
339
+ // Visibly-used prefixes by this element (per Exclusive c14n §2)
340
+ var used = _prefixesUsedByElement(node);
341
+
342
+ // Compute the namespace declarations to RENDER on this element.
343
+ // Exclusive c14n §2: render xmlns:p only if (a) p is in `used` and
344
+ // (b) p's binding wasn't already rendered by an ancestor in the
345
+ // canonicalized subtree.
346
+ var renderedHere = {};
347
+ var renderList = [];
348
+ var prefixes = Object.keys(inScope).sort(function (a, b) {
349
+ if (a === "" && b !== "") return -1;
350
+ if (b === "" && a !== "") return 1;
351
+ return a < b ? -1 : a > b ? 1 : 0;
352
+ });
353
+ prefixes.forEach(function (p) {
354
+ if (!used.has(p)) return;
355
+ var uri = inScope[p];
356
+ if (ancestorRendered[p] === uri) return; // already rendered above
357
+ renderList.push({ prefix: p, uri: uri });
358
+ renderedHere[p] = uri;
359
+ });
360
+
361
+ // Sort attributes: regular attributes per c14n §2.4 order — by
362
+ // namespace URI (no NS first), then by local name.
363
+ var regularAttrs = node.attrs
364
+ .filter(function (a) { return a.name !== "xmlns" && a.name.indexOf("xmlns:") !== 0; })
365
+ .map(function (a) {
366
+ var aColon = a.name.indexOf(":");
367
+ var aPrefix = aColon !== -1 ? a.name.substring(0, aColon) : "";
368
+ var aLocal = aColon !== -1 ? a.name.substring(aColon + 1) : a.name;
369
+ var aUri = aPrefix && inScope[aPrefix] ? inScope[aPrefix] : "";
370
+ return { name: a.name, value: a.value, prefix: aPrefix, local: aLocal, uri: aUri };
371
+ })
372
+ .sort(function (a, b) {
373
+ if (a.uri !== b.uri) return a.uri < b.uri ? -1 : 1;
374
+ return a.local < b.local ? -1 : a.local > b.local ? 1 : 0;
375
+ });
376
+
377
+ var out = "<" + node.name;
378
+
379
+ // Render namespace declarations (sorted; xmlns first, then xmlns:foo
380
+ // alphabetical by prefix)
381
+ renderList.forEach(function (r) {
382
+ if (r.prefix === "") {
383
+ out += " xmlns=\"" + _escapeAttrValue(r.uri) + "\"";
384
+ } else {
385
+ out += " xmlns:" + r.prefix + "=\"" + _escapeAttrValue(r.uri) + "\"";
386
+ }
387
+ });
388
+ // Render regular attributes
389
+ regularAttrs.forEach(function (a) {
390
+ out += " " + a.name + "=\"" + _escapeAttrValue(a.value) + "\"";
391
+ });
392
+ out += ">";
393
+
394
+ // Merge ancestorRendered with renderedHere for child scope.
395
+ var childScope = Object.assign({}, ancestorRendered, renderedHere);
396
+ for (var i = 0; i < node.children.length; i++) {
397
+ out += _serializeNode(node.children[i], childScope, withComments);
398
+ }
399
+
400
+ out += "</" + node.name + ">";
401
+ return out;
402
+ }
403
+
404
+ /**
405
+ * @primitive b.xmlC14n.canonicalize
406
+ * @signature b.xmlC14n.canonicalize(input, opts?)
407
+ * @since 0.8.62
408
+ * @status stable
409
+ * @related b.xmlC14n.canonicalizeElementById, b.guardXml
410
+ *
411
+ * Produce the RFC 3741 Exclusive XML Canonicalization 1.0 byte
412
+ * sequence for an XML document or a parsed DOM node. Returns a
413
+ * Buffer of UTF-8 bytes.
414
+ *
415
+ * @opts
416
+ * {
417
+ * withComments?: boolean, // default false (per xml-exc-c14n)
418
+ * }
419
+ *
420
+ * @example
421
+ * var c = b.xmlC14n.canonicalize("<a:foo xmlns:a='urn:x'><a:bar/></a:foo>");
422
+ * // → Buffer<<a:foo xmlns:a="urn:x"><a:bar></a:bar></a:foo>>
423
+ */
424
+ function canonicalize(input, opts) {
425
+ opts = opts || {};
426
+ var node = (typeof input === "string" || Buffer.isBuffer(input)) ? parse(input) : input;
427
+ if (!node || node.type !== "element") {
428
+ throw _xmlErr("xml-c14n/bad-input",
429
+ "canonicalize: input must be an XML string or a parsed element node");
430
+ }
431
+ var bytes = _serializeNode(node, {}, opts.withComments === true);
432
+ return Buffer.from(bytes, "utf8");
433
+ }
434
+
435
+ /**
436
+ * @primitive b.xmlC14n.canonicalizeElementById
437
+ * @signature b.xmlC14n.canonicalizeElementById(xml, id, opts?)
438
+ * @since 0.8.62
439
+ * @status stable
440
+ * @related b.xmlC14n.canonicalize, b.guardXml
441
+ *
442
+ * Find the element whose `ID` (or operator-specified attribute name)
443
+ * matches the supplied id, then return its canonical-form bytes.
444
+ * Throws if zero or more than one element matches — this single-
445
+ * match invariant is the core defense against XML signature-wrapping
446
+ * attacks where an attacker injects a sibling assertion with the
447
+ * same ID hoping the verifier picks the wrong one.
448
+ *
449
+ * @opts
450
+ * {
451
+ * attrName?: string, // default "ID"
452
+ * withComments?: boolean,
453
+ * }
454
+ *
455
+ * @example
456
+ * var bytes = b.xmlC14n.canonicalizeElementById(
457
+ * "<root><a ID=\"sig\">payload</a></root>",
458
+ * "sig"
459
+ * );
460
+ * // → Buffer<<a ID="sig">payload</a>>
461
+ */
462
+ function canonicalizeElementById(xml, id, opts) {
463
+ validateOpts.requireNonEmptyString(id, "canonicalizeElementById: id", XmlC14nError, "xml-c14n/no-id");
464
+ opts = opts || {};
465
+ var root = parse(xml);
466
+ var attrName = opts.attrName || "ID";
467
+ var matches = [];
468
+ function walk(node) {
469
+ if (node.type !== "element") return;
470
+ if (node.attrs) {
471
+ for (var i = 0; i < node.attrs.length; i++) {
472
+ if (node.attrs[i].name === attrName && node.attrs[i].value === id) {
473
+ matches.push(node);
474
+ break;
475
+ }
476
+ }
477
+ }
478
+ for (var ci = 0; ci < node.children.length; ci++) walk(node.children[ci]);
479
+ }
480
+ walk(root);
481
+ if (matches.length === 0) {
482
+ throw _xmlErr("xml-c14n/no-match",
483
+ "canonicalizeElementById: no element with " + attrName + "=\"" + id + "\"");
484
+ }
485
+ if (matches.length > 1) {
486
+ throw _xmlErr("xml-c14n/duplicate-id",
487
+ "canonicalizeElementById: " + matches.length + " elements share " + attrName +
488
+ "=\"" + id + "\" — refusing (signature-wrapping defense)");
489
+ }
490
+ var bytes = _serializeNode(matches[0], {}, opts.withComments === true);
491
+ return Buffer.from(bytes, "utf8");
492
+ }
493
+
494
+ module.exports = {
495
+ parse: parse,
496
+ canonicalize: canonicalize,
497
+ canonicalizeElementById: canonicalizeElementById,
498
+ XmlC14nError: XmlC14nError,
499
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.8.59",
3
+ "version": "0.8.64",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
@@ -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:a3040651-04d6-4118-8443-de5bae0b5ef3",
5
+ "serialNumber": "urn:uuid:23c254f3-eb31-48e9-84e5-285c0cd30ffe",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-10T00:04:02.160Z",
8
+ "timestamp": "2026-05-10T06:15:43.727Z",
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.8.59",
22
+ "bom-ref": "@blamejs/core@0.8.64",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.8.59",
25
+ "version": "0.8.64",
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.8.59",
29
+ "purl": "pkg:npm/%40blamejs/core@0.8.64",
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.8.59",
57
+ "ref": "@blamejs/core@0.8.64",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]