@blamejs/core 0.8.9 → 0.8.11

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,141 @@
1
+ "use strict";
2
+ /**
3
+ * ageGate middleware — request-level age classification + high-privacy
4
+ * default headers for routes that need stricter handling for users
5
+ * below an operator-configured age threshold.
6
+ *
7
+ * COPPA (US, 13 and under), UK Children's Code (16 and under),
8
+ * California AADC (18 and under), and similar regimes require
9
+ * operators to apply heightened privacy protections when serving
10
+ * users below a regulatory age. The middleware:
11
+ *
12
+ * 1. Reads the operator's `getAge(req)` predicate to classify the
13
+ * request as "above-threshold" / "below-threshold" / "unknown"
14
+ * 2. Sets high-privacy defaults on below-threshold + unknown
15
+ * responses:
16
+ * - `Cache-Control: private, no-store`
17
+ * - `Referrer-Policy: no-referrer`
18
+ * - `X-Privacy-Posture: below-threshold`
19
+ * 3. Refuses with 451 (Unavailable For Legal Reasons) when the
20
+ * operator-supplied requireAge: 18 is set and the request is
21
+ * below threshold without a parental-consent record
22
+ * 4. Audits the classification decision
23
+ *
24
+ * var gate = b.middleware.ageGate({
25
+ * getAge: function (req) {
26
+ * if (req.user && typeof req.user.age === "number") return req.user.age;
27
+ * return null; // unknown
28
+ * },
29
+ * requireAge: null, // null = don't gate, just headers
30
+ * consentRequired: 18, // require parental consent below this
31
+ * hasParentalConsent: function (req) {
32
+ * return req.user && req.user.parentalConsent === true;
33
+ * },
34
+ * });
35
+ * router.use(gate);
36
+ */
37
+
38
+ var defineClass = require("../framework-error").defineClass;
39
+ var lazyRequire = require("../lazy-require");
40
+ var validateOpts = require("../validate-opts");
41
+
42
+ var audit = lazyRequire(function () { return require("../audit"); });
43
+
44
+ var AgeGateError = defineClass("AgeGateError", { alwaysPermanent: true });
45
+
46
+ function create(opts) {
47
+ opts = opts || {};
48
+ validateOpts(opts, [
49
+ "audit", "getAge", "requireAge", "consentRequired",
50
+ "hasParentalConsent", "skipPaths", "errorMessage",
51
+ ], "middleware.ageGate");
52
+
53
+ if (typeof opts.getAge !== "function") {
54
+ throw new AgeGateError("age-gate/bad-get-age",
55
+ "middleware.ageGate: opts.getAge must be a function (req) -> number | null");
56
+ }
57
+ var getAge = opts.getAge;
58
+ var requireAge = (typeof opts.requireAge === "number" && opts.requireAge > 0) // allow:numeric-opt-Infinity — age is operator domain, not a bytes/time-shaped opt
59
+ ? opts.requireAge : null;
60
+ var consentRequired = (typeof opts.consentRequired === "number" && opts.consentRequired > 0) // allow:numeric-opt-Infinity — age threshold, not a bytes/time-shaped opt
61
+ ? opts.consentRequired : null;
62
+ var hasParentalConsent = typeof opts.hasParentalConsent === "function" ? opts.hasParentalConsent : null;
63
+ var skipPaths = Array.isArray(opts.skipPaths) ? opts.skipPaths.slice() : [];
64
+ var auditOn = opts.audit !== false;
65
+ var errorMessage = typeof opts.errorMessage === "string" && opts.errorMessage.length > 0
66
+ ? opts.errorMessage : "service unavailable without parental consent";
67
+
68
+ function _shouldSkip(req) {
69
+ if (skipPaths.length === 0) return false;
70
+ var p = req.url || "";
71
+ var qpos = p.indexOf("?");
72
+ if (qpos !== -1) p = p.slice(0, qpos);
73
+ for (var i = 0; i < skipPaths.length; i++) {
74
+ var s = skipPaths[i];
75
+ if (typeof s === "string" && (p === s || p.indexOf(s + "/") === 0)) return true;
76
+ if (s instanceof RegExp && s.test(p)) return true;
77
+ }
78
+ return false;
79
+ }
80
+
81
+ function _emitAudit(action, outcome, metadata) {
82
+ if (!auditOn) return;
83
+ try {
84
+ audit().safeEmit({
85
+ action: "middleware.age_gate." + action,
86
+ outcome: outcome,
87
+ metadata: metadata || {},
88
+ });
89
+ } catch (_e) { /* drop-silent */ }
90
+ }
91
+
92
+ return function ageGateMiddleware(req, res, next) {
93
+ if (_shouldSkip(req)) return next();
94
+
95
+ var age;
96
+ try { age = getAge(req); }
97
+ catch (e) {
98
+ _emitAudit("get_age_failed", "failure", { error: (e && e.message) || String(e) });
99
+ age = null;
100
+ }
101
+
102
+ var classification;
103
+ if (age === null || typeof age !== "number") classification = "unknown";
104
+ else if (consentRequired !== null && age < consentRequired) classification = "below-threshold";
105
+ else classification = "above-threshold";
106
+
107
+ if (classification !== "above-threshold") {
108
+ if (typeof res.setHeader === "function") {
109
+ res.setHeader("Cache-Control", "private, no-store");
110
+ res.setHeader("Referrer-Policy", "no-referrer");
111
+ res.setHeader("X-Privacy-Posture", classification);
112
+ }
113
+ }
114
+
115
+ if (requireAge !== null && classification === "below-threshold" && (age === null || age < requireAge)) {
116
+ var hasConsent = hasParentalConsent ? !!hasParentalConsent(req) : false;
117
+ if (!hasConsent) {
118
+ _emitAudit("refused", "denied", { age: age, classification: classification, requireAge: requireAge });
119
+ if (!res.writableEnded && typeof res.writeHead === "function") {
120
+ res.writeHead(451, { // allow:raw-byte-literal — HTTP 451 Unavailable For Legal Reasons
121
+ "Content-Type": "application/json; charset=utf-8",
122
+ "Cache-Control": "no-store, private",
123
+ });
124
+ res.end(JSON.stringify({ error: errorMessage, requireAge: requireAge, parentalConsent: false }));
125
+ }
126
+ return;
127
+ }
128
+ }
129
+
130
+ if (req.locals && typeof req.locals === "object") {
131
+ req.locals.ageGateClassification = classification;
132
+ }
133
+ _emitAudit("classified", "success", { classification: classification, age: age });
134
+ return next();
135
+ };
136
+ }
137
+
138
+ module.exports = {
139
+ create: create,
140
+ AgeGateError: AgeGateError,
141
+ };
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ /**
3
+ * botDisclose middleware — California SB 1001 bot-disclosure.
4
+ *
5
+ * Cal. Bus. & Prof. Code §17941 (SB 1001, effective 2019) requires
6
+ * that any "bot" used to communicate with persons in California for
7
+ * the purpose of incentivizing a sale or influencing an election
8
+ * disclose its non-human nature. Operators serving an automated
9
+ * conversation surface (LLM-backed chat / IVR / SMS) wire this
10
+ * middleware to:
11
+ *
12
+ * 1. Inject a disclosure banner into HTML responses (server-rendered)
13
+ * 2. Set an X-Bot-Disclosure header for API consumers
14
+ * 3. Emit an audit event for every conversation-initiating request
15
+ *
16
+ * var bot = b.middleware.botDisclose({
17
+ * audit: b.audit,
18
+ * mountPaths: ["/chat", "/api/chat"],
19
+ * bannerHtml: '<div role="status">You are interacting with an automated assistant.</div>',
20
+ * bannerJson: { _bot: true, disclosure: "automated-assistant" },
21
+ * });
22
+ * router.use(bot);
23
+ *
24
+ * Per SB 1001 §17941(a), the disclosure must be "clear, conspicuous,
25
+ * and reasonably designed to inform"; operators with custom UI wire
26
+ * `bannerHtml` to match their visual design.
27
+ */
28
+
29
+ var defineClass = require("../framework-error").defineClass;
30
+ var lazyRequire = require("../lazy-require");
31
+ var validateOpts = require("../validate-opts");
32
+
33
+ var audit = lazyRequire(function () { return require("../audit"); });
34
+
35
+ var BotDiscloseError = defineClass("BotDiscloseError", { alwaysPermanent: true });
36
+
37
+ var DEFAULT_BANNER_HTML = '<div role="status" data-bot-disclosure="true" ' +
38
+ 'style="border:1px solid #888;padding:8px;margin:8px 0;background:#fff8e1;font-size:14px;">' +
39
+ '<strong>Automated assistant.</strong> ' +
40
+ 'You are interacting with an automated agent. ' +
41
+ 'For California users: this disclosure is provided per Cal. Bus. &amp; Prof. Code §17941.' +
42
+ '</div>';
43
+
44
+ function create(opts) {
45
+ opts = opts || {};
46
+ validateOpts(opts, [
47
+ "audit", "mountPaths", "bannerHtml", "bannerJson",
48
+ "headerName", "auditAction",
49
+ ], "middleware.botDisclose");
50
+
51
+ var mountPaths = Array.isArray(opts.mountPaths) ? opts.mountPaths.slice() : null;
52
+ var bannerHtml = typeof opts.bannerHtml === "string" ? opts.bannerHtml : DEFAULT_BANNER_HTML;
53
+ var bannerJson = (opts.bannerJson && typeof opts.bannerJson === "object")
54
+ ? opts.bannerJson : { _bot: true, disclosure: "automated-assistant" };
55
+ var headerName = typeof opts.headerName === "string" && opts.headerName.length > 0
56
+ ? opts.headerName : "X-Bot-Disclosure";
57
+ var auditOn = opts.audit !== false;
58
+ var actionBase = typeof opts.auditAction === "string" && opts.auditAction.length > 0
59
+ ? opts.auditAction : "middleware.bot_disclose";
60
+
61
+ function _matches(req) {
62
+ if (!mountPaths) return true; // null = match every path
63
+ var p = req.url || "";
64
+ var qpos = p.indexOf("?");
65
+ if (qpos !== -1) p = p.slice(0, qpos);
66
+ for (var i = 0; i < mountPaths.length; i++) {
67
+ var m = mountPaths[i];
68
+ if (typeof m === "string" && (p === m || p.indexOf(m + "/") === 0)) return true;
69
+ if (m instanceof RegExp && m.test(p)) return true;
70
+ }
71
+ return false;
72
+ }
73
+
74
+ function _emitAudit(action, outcome, metadata) {
75
+ if (!auditOn) return;
76
+ try {
77
+ audit().safeEmit({
78
+ action: actionBase + "." + action,
79
+ outcome: outcome,
80
+ metadata: metadata || {},
81
+ });
82
+ } catch (_e) { /* drop-silent */ }
83
+ }
84
+
85
+ return function botDiscloseMiddleware(req, res, next) {
86
+ if (!_matches(req)) return next();
87
+
88
+ // Always set the header (cheap; API consumers see the disclosure
89
+ // even on JSON endpoints).
90
+ if (typeof res.setHeader === "function") {
91
+ res.setHeader(headerName, "automated-assistant");
92
+ }
93
+ _emitAudit("disclosed", "success", { method: req.method, path: req.url });
94
+
95
+ // Patch res.write / res.end so the first text/html response gets
96
+ // the banner injected before <body>'s first child. Operators
97
+ // wanting deeper integration override bannerHtml or set
98
+ // mountPaths to scope where injection happens.
99
+ var origWrite = res.write && res.write.bind(res);
100
+ var origEnd = res.end && res.end.bind(res);
101
+ var injected = false;
102
+
103
+ function _maybeInject(chunk, encoding) {
104
+ if (injected) return chunk;
105
+ var ct = typeof res.getHeader === "function" ? res.getHeader("content-type") : "";
106
+ if (typeof ct !== "string" || ct.indexOf("text/html") === -1) return chunk;
107
+ var body = Buffer.isBuffer(chunk) ? chunk.toString("utf8") :
108
+ (typeof chunk === "string" ? chunk : "");
109
+ // Inject after <body> opening tag if present, else after <html>
110
+ // opening tag, else prepend.
111
+ var bodyMatch = body.match(/<body[^>]*>/i);
112
+ if (bodyMatch) {
113
+ var idx = bodyMatch.index + bodyMatch[0].length;
114
+ body = body.slice(0, idx) + "\n" + bannerHtml + "\n" + body.slice(idx);
115
+ } else {
116
+ body = bannerHtml + "\n" + body;
117
+ }
118
+ injected = true;
119
+ return Buffer.from(body, "utf8");
120
+ }
121
+
122
+ if (origWrite) {
123
+ res.write = function (chunk, encoding, cb) {
124
+ return origWrite(_maybeInject(chunk, encoding), encoding, cb);
125
+ };
126
+ }
127
+ if (origEnd) {
128
+ res.end = function (chunk, encoding, cb) {
129
+ if (chunk) chunk = _maybeInject(chunk, encoding);
130
+ return origEnd(chunk, encoding, cb);
131
+ };
132
+ }
133
+
134
+ // For JSON responses, attach the disclosure to res.locals so
135
+ // operator handlers building JSON via res.json(...) can include
136
+ // it explicitly. We don't auto-merge into the JSON body — the
137
+ // header + audit are the load-bearing disclosure surfaces.
138
+ if (res.locals && typeof res.locals === "object") {
139
+ res.locals.botDisclosure = bannerJson;
140
+ }
141
+ return next();
142
+ };
143
+ }
144
+
145
+ module.exports = {
146
+ create: create,
147
+ BotDiscloseError: BotDiscloseError,
148
+ DEFAULT_BANNER_HTML: DEFAULT_BANNER_HTML,
149
+ };
@@ -25,6 +25,7 @@ var assetlinks = require("./assetlinks");
25
25
  var attachUser = require("./attach-user");
26
26
  var bearerAuth = require("./bearer-auth");
27
27
  var bodyParser = require("./body-parser");
28
+ var botDisclose = require("./bot-disclose");
28
29
  var botGuard = require("./bot-guard");
29
30
  var compression = require("./compression");
30
31
  var cookies = require("./cookies");
@@ -47,6 +48,7 @@ var requestLog = require("./request-log");
47
48
  var requireAal = require("./require-aal");
48
49
  var requireAuth = require("./require-auth");
49
50
  var requireContentType = require("./require-content-type");
51
+ var ageGate = require("./age-gate");
50
52
  var requireMethods = require("./require-methods");
51
53
  var requireMtls = require("./require-mtls");
52
54
  var requireStepUp = require("./require-step-up");
@@ -63,6 +65,7 @@ module.exports = {
63
65
  requestId: requestId.create,
64
66
  securityHeaders: securityHeaders.create,
65
67
  errorHandler: errorHandler.create,
68
+ botDisclose: botDisclose.create,
66
69
  botGuard: botGuard.create,
67
70
  cors: cors.create,
68
71
  dailyByteQuota: dailyByteQuota.create,
@@ -72,6 +75,7 @@ module.exports = {
72
75
  requireAal: requireAal.create,
73
76
  requireAuth: requireAuth.create,
74
77
  requireContentType: requireContentType.create,
78
+ ageGate: ageGate.create,
75
79
  requireMethods: requireMethods.create,
76
80
  requireMtls: requireMtls.create,
77
81
  requireStepUp: requireStepUp.create,
@@ -108,6 +112,7 @@ module.exports = {
108
112
  requestId: requestId,
109
113
  securityHeaders: securityHeaders,
110
114
  errorHandler: errorHandler,
115
+ botDisclose: botDisclose,
111
116
  botGuard: botGuard,
112
117
  cors: cors,
113
118
  dailyByteQuota: dailyByteQuota,
@@ -117,6 +122,7 @@ module.exports = {
117
122
  requireAal: requireAal,
118
123
  requireAuth: requireAuth,
119
124
  requireContentType: requireContentType,
125
+ ageGate: ageGate,
120
126
  requireMethods: requireMethods,
121
127
  requireMtls: requireMtls,
122
128
  requireStepUp: requireStepUp,
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ /**
3
+ * b.nis2.report — NIS2 Directive incident-reporting wrapper.
4
+ *
5
+ * Directive (EU) 2022/2555 (NIS2) Article 23 mandates that essential
6
+ * + important entities report significant incidents to the national
7
+ * CSIRT or competent authority. Three-stage pattern with statutory
8
+ * deadlines:
9
+ * - early warning within 24h of becoming aware (Art. 23 §4(a))
10
+ * - incident notification within 72h, including initial assessment
11
+ * of severity + impact + indicators of compromise (Art. 23 §4(b))
12
+ * - final report within 1 month, with detailed root cause +
13
+ * remediation + cross-border implications (Art. 23 §4(d))
14
+ *
15
+ * var nis2 = b.nis2.report.create({
16
+ * audit: b.audit,
17
+ * entityId: "acme-cloud-1",
18
+ * entityType: "essential", // "essential" | "important"
19
+ * sectorAnnex: "I.6", // NIS2 Annex I/II row id
20
+ * csirtEndpoint: "https://csirt.example/api",
21
+ * httpClient: b.httpClient,
22
+ * });
23
+ *
24
+ * Sector annex codes follow NIS2's two annexes:
25
+ * - Annex I (essential): I.1 energy / I.2 transport / I.3 banking /
26
+ * I.4 financial-market-infrastructures / I.5 health /
27
+ * I.6 drinking-water / I.7 wastewater / I.8 digital-infrastructure /
28
+ * I.9 ICT-service-management / I.10 public-administration /
29
+ * I.11 space
30
+ * - Annex II (important): II.1 postal / II.2 waste-management /
31
+ * II.3 chemicals / II.4 food / II.5 manufacturing /
32
+ * II.6 digital-providers / II.7 research
33
+ */
34
+
35
+ var C = require("./constants");
36
+ var defineClass = require("./framework-error").defineClass;
37
+ var lazyRequire = require("./lazy-require");
38
+ var validateOpts = require("./validate-opts");
39
+
40
+ var incidentReport = lazyRequire(function () { return require("./incident-report"); });
41
+ var audit = lazyRequire(function () { return require("./audit"); });
42
+
43
+ var Nis2ReportError = defineClass("Nis2ReportError", { alwaysPermanent: true });
44
+
45
+ var VALID_ENTITY_TYPES = Object.freeze({ essential: 1, important: 1 });
46
+
47
+ function create(opts) {
48
+ opts = opts || {};
49
+ validateOpts(opts, [
50
+ "audit", "persist", "httpClient", "csirtEndpoint",
51
+ "entityId", "entityType", "sectorAnnex", "now",
52
+ ], "nis2.report");
53
+
54
+ validateOpts.requireNonEmptyString(opts.entityId,
55
+ "nis2.report.create: opts.entityId is required (NIS2 registration ID)",
56
+ Nis2ReportError, "nis2-report/bad-entity-id");
57
+ if (!VALID_ENTITY_TYPES[opts.entityType]) {
58
+ throw new Nis2ReportError("nis2-report/bad-entity-type",
59
+ "nis2.report.create: opts.entityType must be 'essential' or 'important' (NIS2 Article 3 classification)");
60
+ }
61
+ validateOpts.requireNonEmptyString(opts.sectorAnnex,
62
+ "nis2.report.create: opts.sectorAnnex is required (e.g. 'I.6' for drinking water, 'II.6' for digital-providers)",
63
+ Nis2ReportError, "nis2-report/bad-sector");
64
+ var entityId = opts.entityId;
65
+ var entityType = opts.entityType;
66
+ var sectorAnnex = opts.sectorAnnex;
67
+ var csirtEndpoint = opts.csirtEndpoint || null;
68
+ var httpClient = opts.httpClient || null;
69
+ var auditOn = opts.audit !== false;
70
+
71
+ var ir = incidentReport().create({
72
+ audit: opts.audit,
73
+ persist: opts.persist,
74
+ now: opts.now,
75
+ deadlines: {
76
+ initial: C.TIME.hours(24), // NIS2 Art. 23 §4(a) — early warning
77
+ intermediate: C.TIME.hours(72), // NIS2 Art. 23 §4(b) — incident notification
78
+ final: C.TIME.days(30), // NIS2 Art. 23 §4(d) — final report (1 month)
79
+ },
80
+ });
81
+
82
+ function _emitAudit(action, outcome, metadata) {
83
+ if (!auditOn) return;
84
+ try {
85
+ audit().safeEmit({
86
+ action: "nis2.report." + action,
87
+ outcome: outcome,
88
+ metadata: metadata || {},
89
+ });
90
+ } catch (_e) { /* drop-silent */ }
91
+ }
92
+
93
+ async function _submitToCsirt(payload) {
94
+ if (!csirtEndpoint || !httpClient) {
95
+ _emitAudit("submit_skipped", "warning", { reason: "no-endpoint-or-client" });
96
+ return { submitted: false, reason: "no-endpoint-or-client" };
97
+ }
98
+ try {
99
+ var res = await httpClient.request({
100
+ url: csirtEndpoint, method: "POST",
101
+ headers: { "Content-Type": "application/json" },
102
+ body: Buffer.from(JSON.stringify(payload), "utf8"),
103
+ responseMode: "always-resolve",
104
+ });
105
+ var ok = res.statusCode >= 200 && res.statusCode < 300; // allow:raw-byte-literal — HTTP status range
106
+ _emitAudit("submitted", ok ? "success" : "failure", { statusCode: res.statusCode });
107
+ return { submitted: ok, statusCode: res.statusCode };
108
+ } catch (e) {
109
+ _emitAudit("submit_failed", "failure", { error: (e && e.message) || String(e) });
110
+ return { submitted: false, error: (e && e.message) || String(e) };
111
+ }
112
+ }
113
+
114
+ function _envelope(stage, incident, fields) {
115
+ return {
116
+ directive: "(EU) 2022/2555",
117
+ article: "23",
118
+ stage: stage, // "early-warning" / "notification" / "final"
119
+ entity: { id: entityId, type: entityType, sector: sectorAnnex },
120
+ incident: {
121
+ id: incident.id,
122
+ detected_at: new Date(incident.detectedAt).toISOString(),
123
+ scope: incident.scope,
124
+ summary: incident.summary,
125
+ impact: incident.impact,
126
+ },
127
+ fields: fields || {},
128
+ };
129
+ }
130
+
131
+ async function open(spec) {
132
+ spec = Object.assign({}, spec || {}, { regime: "nis2" });
133
+ var rec = await ir.open(spec);
134
+ _emitAudit("opened", "success", { incidentId: rec.id, entityId: entityId, entityType: entityType });
135
+ return rec;
136
+ }
137
+
138
+ async function earlyWarning(incidentId, fields) {
139
+ var rec = await ir.recordInitial(incidentId, fields || {});
140
+ var result = { record: rec, submitted: null };
141
+ if (fields && fields.submit === true) {
142
+ result.submitted = await _submitToCsirt(_envelope("early-warning", rec, fields));
143
+ }
144
+ return result;
145
+ }
146
+ async function notification(incidentId, fields) {
147
+ var rec = await ir.recordIntermediate(incidentId, fields || {});
148
+ var result = { record: rec, submitted: null };
149
+ if (fields && fields.submit === true) {
150
+ result.submitted = await _submitToCsirt(_envelope("notification", rec, fields));
151
+ }
152
+ return result;
153
+ }
154
+ async function finalReport(incidentId, fields) {
155
+ var rec = await ir.recordFinal(incidentId, fields || {});
156
+ var result = { record: rec, submitted: null };
157
+ if (fields && fields.submit === true) {
158
+ result.submitted = await _submitToCsirt(_envelope("final", rec, fields));
159
+ }
160
+ return result;
161
+ }
162
+
163
+ return {
164
+ open: open,
165
+ earlyWarning: earlyWarning,
166
+ notification: notification,
167
+ finalReport: finalReport,
168
+ get: function (id) { return ir.get(id); },
169
+ list: function () { return ir.list(); },
170
+ status: function () { return ir.status(); },
171
+ entityId: entityId,
172
+ entityType: entityType,
173
+ sectorAnnex: sectorAnnex,
174
+ };
175
+ }
176
+
177
+ module.exports = {
178
+ create: create,
179
+ Nis2ReportError: Nis2ReportError,
180
+ VALID_ENTITY_TYPES: Object.keys(VALID_ENTITY_TYPES),
181
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.8.9",
3
+ "version": "0.8.11",
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:b2addb57-b40a-4a44-9c34-7f7bd7d741c3",
5
+ "serialNumber": "urn:uuid:ae68f706-2c92-4dfa-b852-f29896f91c62",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-07T02:18:24.599Z",
8
+ "timestamp": "2026-05-07T03:30:10.287Z",
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.9",
22
+ "bom-ref": "@blamejs/core@0.8.11",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.8.9",
25
+ "version": "0.8.11",
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.9",
29
+ "purl": "pkg:npm/%40blamejs/core@0.8.11",
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.9",
57
+ "ref": "@blamejs/core@0.8.11",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]