@blamejs/core 0.7.78 → 0.7.80

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 CHANGED
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.7.x
10
10
 
11
+ - **0.7.80** (2026-05-06) — `b.middleware.securityTxt` (RFC 9116) + SQLite `secure_delete=ON` at DB boot. **`b.middleware.securityTxt({ contact, expires, encryption?, policy?, ack?, preferredLanguages?, hiring?, canonical?, alsoAtRoot?, audit? })`** serves a static body at `/.well-known/security.txt` (and root `/security.txt` when `alsoAtRoot: true`) per RFC 9116. Operators wire it on their app so security researchers know where to find the disclosure policy. The `Contact:` and `Expires:` fields are REQUIRED per §2.5; the framework throws at config-time when either is missing AND when `expires` is in the past (RFC 9116 §2.5.5). All field values are CR/LF/NUL-screened (header injection defense). Body is built once at create() and served with `Content-Length` + `Cache-Control: public, max-age=86400` + `X-Content-Type-Options: nosniff`. **SQLite `PRAGMA secure_delete=ON`** is now applied at `b.db.init` time alongside the existing PRAGMA block. SQLite normally just unlinks rows from the B-tree; the underlying page bytes survive on disk until a new write reuses the slot. With `secure_delete=ON`, freed pages are overwritten with zeros so a forensic recovery against the encrypted database file can't reconstruct deleted rows. The cost is one extra write per delete — already dominated by the framework's audit-chain emissions on every DSR erase / cascade fan-out.
12
+
13
+ - **0.7.79** (2026-05-06) — PQC TLS handshake key shares + DB hardening sweep (3 items). **`b.network.tls.pqc`** — operator-facing TLS 1.3 key-share configuration. The framework's app-layer envelope has been PQC-first since v0.7.28 (ML-KEM-1024 + X25519 hybrid for sealed records); this slice extends PQC posture down to the **TLS handshake itself**. `b.network.tls.pqc.setKeyShares(["X25519MLKEM768", "X25519", "secp256r1"])` configures the TLS 1.3 key-share groups the framework's `https.Server` / `https.Agent` advertises. The first listed group is the operator priority; the peer picks the first mutually supported entry. **`X25519MLKEM768`** is the IETF draft-kwiatkowski-tls-ecdhe-mlkem-02 hybrid KEM that negotiates post-quantum + classical in one handshake — forward-secrecy survives both classical-CRQC and future quantum cryptanalysis. Default list is `["X25519MLKEM768", "X25519", "secp256r1"]` so the framework attempts hybrid first, falls back to classical X25519 with peers that don't support the hybrid (most of the public web today), and to `secp256r1` for legacy peers. **`b.network.tls.applyToContext({ base })`** now threads the configured key-share list through as the `groups` option to Node's TLS context (operators who explicitly set `groups` in their `base` config keep the override). Operators wanting classical-only call `b.network.tls.pqc.setKeyShares(["X25519"])`; calling `.resetKeyShares()` restores the default. Requires Node 24+ with OpenSSL 3.5+ for the X25519MLKEM768 group; older Node falls back silently to the classical entries. **DB hardening: PRAGMA integrity_check at boot** — `b.db.init` now runs `PRAGMA integrity_check` after the existing PRAGMA block and refuses boot if SQLite reports anything other than `"ok"`. Catches B-tree corruption at boot rather than letting it surface mid-query when the engine stumbles on a bad page. Skip via `opts.skipIntegrityCheck: true` for tmpfs-only fixtures (audited reason). **DB hardening: migration-lock holder ID boot token** — `lib/external-db-migrate.js:_lockHolderId()` now appends a per-process random 8-byte boot token, so a recycled PID-on-hostname slot after a container restart can't be misattributed back to the new boot when reading stale lock rows. Closes the PID-reuse-across-container-restart concurrent-ownership window flagged in the v0.7.67 audit batch.
14
+
11
15
  - **0.7.78** (2026-05-06) — `b.cloudEvents.wrap` / `.parse` — CloudEvents 1.0 envelope (cloudevents.io/spec/v1.0). Vendor-neutral event-format spec adopted by AWS EventBridge, Knative, Azure Event Grid, Google Eventarc, Datadog, and the broader CNCF event ecosystem; operators wrap outbound events from webhook / pubsub / queue boundaries to interop with these consumers without each consumer learning a bespoke shape. **`b.cloudEvents.wrap({ source, type, data?, subject?, time?, id?, datacontenttype?, dataschema?, extensions? })`** produces a CloudEvents 1.0 envelope: required attributes (`id` auto-minted as RFC 4122 v4 UUID when omitted, `source`, `specversion="1.0"`, `type`, `time` auto-set to `new Date().toISOString()`), optional attributes (`subject`, `datacontenttype` auto-set to `"application/json"` when `data` is JSON-serializable or `"application/octet-stream"` when `data` is a `Buffer` — base64-encoded into `data_base64`, `dataschema`), plus operator-defined extension attributes that conform to the §3.1 naming rules (lowercase ASCII alnum, 1-20 chars). **`b.cloudEvents.parse(envelope)`** validates the envelope shape and returns a structured form with `extensions` surfaced as a separate object so consumers can route on operator-defined fields without grepping the envelope. Refuses `data` + `data_base64` together (CloudEvents §3.1.1), unsupported specversion, missing required attributes, malformed extension names. **Test cleanup**: the `testAuditSafeEmitRedacts` smoke fixture from v0.7.75 now registers the `test` audit namespace before emitting so the audit handler's noise log line ("namespace 'test' is not registered") doesn't appear in CI smoke output.
12
16
 
13
17
  - **0.7.77** (2026-05-06) — Argon2 switched from vendored prebuilds to Node's built-in `crypto.argon2*` (Node 24+). The framework's `lib/vendor/argon2/` directory (with the `argon2.cjs` bundle and the `prebuilds/` tree of platform-specific `.glibc.node` / `.musl.node` artifacts for darwin-arm64 / darwin-x64 / freebsd-arm64 / freebsd-x64 / linux-arm / linux-arm64 / linux-x64 / win32-x64) is **deleted**. New `lib/argon2-builtin.js` is a thin wrapper over `crypto.argon2Sync` that produces and parses the PHC string format (`$argon2id$v=19$m=...,t=...,p=...$<salt>$<hash>`). Wire-format compatibility preserved: existing rows in operator databases continue to verify. Behavior preserved: `b.auth.password.hash` / `.verify` / `.needsRehash` retain their async signatures and return shapes; `b.vault.wrap` and `b.backupCrypto.deriveKey` use the same `raw: true` path for raw-bytes output. Operators wanting to supply their own argon2 implementation (pinned upstream, hardware-accelerated, etc.) override at the call site via `opts.argon2` — the supplied object MUST expose the same `hash` / `verify` / `needsRehash` shape. Drops ~440 KB of platform-specific native prebuilds from the repository and shipped npm tarball; eliminates a supply-chain hop. The vendor manifest's `argon2` entry is removed; `scripts/vendor-update.sh argon2` now refuses with a pointer to `lib/argon2-builtin.js`.
package/lib/db.js CHANGED
@@ -681,6 +681,37 @@ async function init(opts) {
681
681
  // structured `foreignKeys` declarations actually constrain writes.
682
682
  runSql(database, "PRAGMA foreign_keys=ON");
683
683
 
684
+ // PRAGMA secure_delete=ON — SQLite normally just unlinks rows from
685
+ // the B-tree; the underlying page bytes survive on disk until a new
686
+ // write reuses the slot. With secure_delete=ON, freed pages are
687
+ // overwritten with zeros so a forensic recovery against the file
688
+ // can't reconstruct deleted rows. The cost is one extra write per
689
+ // delete, which the framework's audit-and-DSR-erase path already
690
+ // dominates with audit-chain emissions and cascade fan-out.
691
+ runSql(database, "PRAGMA secure_delete=ON");
692
+
693
+ // PRAGMA integrity_check — refuse boot on B-tree corruption (per
694
+ // audit-batch finding). SQLite returns "ok" for a healthy database;
695
+ // any other result means corruption. Catching it at boot beats
696
+ // stumbling on it later in a query that hits the bad page. Skip
697
+ // when opts.skipIntegrityCheck is set (e.g. tmpfs-only fixtures).
698
+ if (opts.skipIntegrityCheck !== true) {
699
+ var integrityRows = [];
700
+ try {
701
+ // .all-style read; runSql is for statements without rows.
702
+ integrityRows = database.prepare("PRAGMA integrity_check").all();
703
+ } catch (e) {
704
+ throw new DbError("db/integrity-check-failed",
705
+ "PRAGMA integrity_check failed at boot: " + ((e && e.message) || String(e)));
706
+ }
707
+ if (integrityRows.length !== 1 ||
708
+ !integrityRows[0] || integrityRows[0].integrity_check !== "ok") {
709
+ throw new DbError("db/integrity-check-failed",
710
+ "PRAGMA integrity_check reported corruption: " +
711
+ JSON.stringify(integrityRows));
712
+ }
713
+ }
714
+
684
715
  // Refuse app schema entries that collide with framework-reserved names
685
716
  for (var ri = 0; ri < opts.schema.length; ri++) {
686
717
  if (RESERVED_TABLE_NAMES.has(opts.schema[ri].name)) {
@@ -86,8 +86,16 @@ function _err(code, message) {
86
86
  return new ExternalDbMigrateError(code, message);
87
87
  }
88
88
 
89
+ // Boot-token suffix ensures _lockHolderId() is unique across container
90
+ // restarts even if the OS recycles a PID into the same hostname slot —
91
+ // without it, a stolen-and-released migration lock could be wrongly
92
+ // attributed back to the new boot. The token is process-scoped so
93
+ // every replica picks a fresh one at module load.
94
+ var _BOOT_TOKEN = require("node:crypto").randomBytes(8).toString("hex"); // allow:raw-byte-literal — boot-id token entropy
95
+
89
96
  function _lockHolderId() {
90
- return String(process.pid) + "@" + (require("node:os").hostname() || "unknown");
97
+ return String(process.pid) + "@" +
98
+ (require("node:os").hostname() || "unknown") + "@" + _BOOT_TOKEN;
91
99
  }
92
100
 
93
101
  async function _ensureTrackingTable(xdb) {
@@ -40,6 +40,7 @@ var requestLog = require("./request-log");
40
40
  var requireAal = require("./require-aal");
41
41
  var requireAuth = require("./require-auth");
42
42
  var securityHeaders = require("./security-headers");
43
+ var securityTxt = require("./security-txt");
43
44
  var sse = require("./sse");
44
45
 
45
46
  module.exports = {
@@ -62,6 +63,7 @@ module.exports = {
62
63
  compression: compression.create,
63
64
  cookies: cookies.create,
64
65
  cspNonce: cspNonce.create,
66
+ securityTxt: securityTxt.create,
65
67
  sse: sse.create,
66
68
  requestLog: requestLog.create,
67
69
  apiEncrypt: apiEncrypt,
@@ -87,6 +89,7 @@ module.exports = {
87
89
  health: health,
88
90
  compression: compression,
89
91
  cspNonce: cspNonce,
92
+ securityTxt: securityTxt,
90
93
  sse: sse,
91
94
  requestLog: requestLog,
92
95
  apiEncrypt: apiEncrypt,
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ /**
3
+ * security-txt middleware — RFC 9116 /.well-known/security.txt emitter.
4
+ *
5
+ * Operators wire this on their app so security researchers know where
6
+ * to find the disclosure policy. The middleware serves a static body
7
+ * at `/.well-known/security.txt` (and root `/security.txt` when
8
+ * opts.alsoAtRoot is true) per RFC 9116 §3 ("Format" — text/plain
9
+ * with one field per line, "Field: value" pairs).
10
+ *
11
+ * var txt = b.middleware.securityTxt({
12
+ * contact: ["mailto:security@example.com", "https://example.com/security/report"],
13
+ * expires: "2027-01-01T00:00:00Z",
14
+ * encryption:["https://example.com/pgp.asc"],
15
+ * policy: "https://example.com/security/policy",
16
+ * ack: "https://example.com/security/hall-of-fame",
17
+ * preferredLanguages: ["en"],
18
+ * });
19
+ * router.use(txt);
20
+ *
21
+ * Per RFC 9116 §2.5, `Contact:` and `Expires:` are REQUIRED. The
22
+ * middleware throws at config-time when either is missing.
23
+ *
24
+ * Per §2.5.1, `Expires:` MUST be a future timestamp; the framework
25
+ * also throws when the operator-supplied `expires` is in the past.
26
+ */
27
+
28
+ var lazyRequire = require("../lazy-require");
29
+ var validateOpts = require("../validate-opts");
30
+ var { defineClass } = require("../framework-error");
31
+
32
+ var SecurityTxtError = defineClass("SecurityTxtError", { alwaysPermanent: true });
33
+
34
+ var observability = lazyRequire(function () { return require("../observability"); });
35
+
36
+ function _arrayOfStrings(value, label) {
37
+ if (value === undefined || value === null) return [];
38
+ var arr = Array.isArray(value) ? value : [value];
39
+ for (var i = 0; i < arr.length; i += 1) {
40
+ if (typeof arr[i] !== "string" || arr[i].length === 0) {
41
+ throw new SecurityTxtError("security-txt/bad-" + label,
42
+ label + "[" + i + "] must be a non-empty string");
43
+ }
44
+ if (/[\r\n\0]/.test(arr[i])) {
45
+ throw new SecurityTxtError("security-txt/bad-" + label,
46
+ label + "[" + i + "] contains forbidden CR/LF/NUL");
47
+ }
48
+ }
49
+ return arr;
50
+ }
51
+
52
+ function _isoFuture(s) {
53
+ if (typeof s !== "string" || s.length === 0) return false;
54
+ var d = new Date(s);
55
+ if (isNaN(d.getTime())) return false;
56
+ return d.getTime() > Date.now();
57
+ }
58
+
59
+ function create(opts) {
60
+ validateOpts.requireObject(opts, "middleware.securityTxt", SecurityTxtError);
61
+ validateOpts(opts, [
62
+ "contact", "expires", "encryption", "policy", "ack",
63
+ "preferredLanguages", "hiring", "canonical",
64
+ "alsoAtRoot", "audit",
65
+ ], "middleware.securityTxt");
66
+
67
+ var contact = _arrayOfStrings(opts.contact, "contact");
68
+ if (contact.length === 0) {
69
+ throw new SecurityTxtError("security-txt/no-contact",
70
+ "middleware.securityTxt: contact is required (RFC 9116 §2.5.3)");
71
+ }
72
+ validateOpts.requireNonEmptyString(opts.expires,
73
+ "middleware.securityTxt: expires", SecurityTxtError, "security-txt/no-expires");
74
+ if (!_isoFuture(opts.expires)) {
75
+ throw new SecurityTxtError("security-txt/expires-in-past",
76
+ "middleware.securityTxt: expires must be a future ISO 8601 timestamp (got '" + opts.expires + "')");
77
+ }
78
+ var encryption = _arrayOfStrings(opts.encryption, "encryption");
79
+ var policy = _arrayOfStrings(opts.policy, "policy");
80
+ var ack = _arrayOfStrings(opts.ack, "ack");
81
+ var canonical = _arrayOfStrings(opts.canonical, "canonical");
82
+ var hiring = _arrayOfStrings(opts.hiring, "hiring");
83
+ var prefLangs = _arrayOfStrings(opts.preferredLanguages, "preferredLanguages");
84
+
85
+ // Build the body once at create time — the response is identical
86
+ // for every request and the Content-Length is known up front.
87
+ var lines = [];
88
+ for (var i = 0; i < contact.length; i += 1) lines.push("Contact: " + contact[i]);
89
+ lines.push("Expires: " + opts.expires);
90
+ for (var ei = 0; ei < encryption.length; ei += 1) lines.push("Encryption: " + encryption[ei]);
91
+ for (var pi = 0; pi < policy.length; pi += 1) lines.push("Policy: " + policy[pi]);
92
+ for (var ai = 0; ai < ack.length; ai += 1) lines.push("Acknowledgments: " + ack[ai]);
93
+ for (var ci = 0; ci < canonical.length; ci += 1) lines.push("Canonical: " + canonical[ci]);
94
+ for (var hi = 0; hi < hiring.length; hi += 1) lines.push("Hiring: " + hiring[hi]);
95
+ if (prefLangs.length > 0) lines.push("Preferred-Languages: " + prefLangs.join(", "));
96
+ var body = lines.join("\n") + "\n";
97
+ var bodyBuf = Buffer.from(body, "utf8");
98
+ var alsoAtRoot = opts.alsoAtRoot === true;
99
+
100
+ return function securityTxtMiddleware(req, res, next) {
101
+ var url = req.url || "";
102
+ // Strip query string for the path comparison.
103
+ var qIdx = url.indexOf("?");
104
+ var path = qIdx === -1 ? url : url.slice(0, qIdx);
105
+ var matches = (path === "/.well-known/security.txt") ||
106
+ (alsoAtRoot && path === "/security.txt");
107
+ if (!matches) return next();
108
+ if (req.method !== "GET" && req.method !== "HEAD") {
109
+ res.writeHead(405, { // allow:raw-byte-literal — HTTP 405 status
110
+ "Allow": "GET, HEAD",
111
+ "Content-Type": "text/plain; charset=utf-8",
112
+ "Content-Length": 18, // allow:raw-byte-literal — len of "Method Not Allowed"
113
+ });
114
+ res.end("Method Not Allowed");
115
+ return;
116
+ }
117
+ res.writeHead(200, { // allow:raw-byte-literal — HTTP 200 status
118
+ "Content-Type": "text/plain; charset=utf-8",
119
+ "Content-Length": bodyBuf.length,
120
+ "Cache-Control": "public, max-age=86400",
121
+ "X-Content-Type-Options": "nosniff",
122
+ });
123
+ if (req.method === "HEAD") { res.end(); return; }
124
+ res.end(bodyBuf);
125
+ try { observability().safeEvent("middleware.securityTxt.served", 1, { path: path }); }
126
+ catch (_e) { /* obs best-effort */ }
127
+ };
128
+ }
129
+
130
+ module.exports = {
131
+ create: create,
132
+ };
@@ -17,10 +17,15 @@ var observability = lazyRequire(function () { return require("./observability");
17
17
  var audit = lazyRequire(function () { return require("./audit"); });
18
18
  var asn1 = require("./asn1-der");
19
19
 
20
+ // STATE.tlsKeyShares is initialized to the default PQC group list at
21
+ // module load — operator setKeyShares() overrides; resetKeyShares()
22
+ // restores the default. Empty array means "fall back to Node's TLS
23
+ // default groups" (operator opt-out).
20
24
  var STATE = {
21
25
  cas: [],
22
26
  systemTrust: false,
23
27
  baselineFingerprints: null,
28
+ tlsKeyShares: ["X25519MLKEM768", "X25519", "secp256r1"],
24
29
  };
25
30
 
26
31
  function _normalizePem(pem) {
@@ -267,9 +272,77 @@ function applyToContext(opts) {
267
272
  }
268
273
  }
269
274
  if (caStrings.length > 0) base.ca = caStrings;
275
+ // PQC TLS handshake — apply the operator-configured key-share groups
276
+ // (default ["X25519MLKEM768", "X25519"]) so https.Server / https.Agent
277
+ // negotiate the hybrid KEM with peers that support it and fall back
278
+ // to classical X25519 with peers that don't. Operators who explicitly
279
+ // pass `groups` in their base config keep the override.
280
+ if (base.groups === undefined && STATE.tlsKeyShares.length > 0) {
281
+ base.groups = STATE.tlsKeyShares.join(":");
282
+ }
270
283
  return base;
271
284
  }
272
285
 
286
+ // ---- PQC TLS key shares (RFC draft-ietf-tls-hybrid-design) ----
287
+ //
288
+ // b.network.tls.pqc.setKeyShares(["X25519MLKEM768", "X25519"]) — set the
289
+ // TLS 1.3 key-share groups the framework's https.Server / https.Agent
290
+ // will advertise. The first listed group is the priority; the peer
291
+ // picks the first mutually supported entry. Hybrid groups
292
+ // (X25519MLKEM768) negotiate post-quantum + classical in one
293
+ // handshake so forward-secrecy survives both classical-CRQC and
294
+ // future quantum cryptanalysis.
295
+ //
296
+ // getKeyShares() → string[] (current)
297
+ // setKeyShares(["X25519MLKEM768", "X25519"]) → string[] (after)
298
+ // resetKeyShares() → restores default
299
+
300
+ var DEFAULT_PQC_KEY_SHARES = Object.freeze([
301
+ "X25519MLKEM768", // hybrid KEM, draft-kwiatkowski-tls-ecdhe-mlkem-02
302
+ "X25519", // classical fallback
303
+ "secp256r1", // legacy peers
304
+ ]);
305
+
306
+ function _validateKeyShare(name) {
307
+ if (typeof name !== "string" || name.length === 0 || name.length > C.BYTES.bytes(64)) { // bound
308
+ throw new TlsTrustError("tls/bad-key-share",
309
+ "tls.pqc.setKeyShares: each entry must be a non-empty string up to 64 chars");
310
+ }
311
+ // RFC draft-ietf-tls-hybrid-design + IANA TLS Group Registry only
312
+ // emit alphanumeric + underscore identifiers. Refuse `:` (the join
313
+ // separator) outright so an operator can't smuggle a second entry
314
+ // through one slot.
315
+ if (!/^[A-Za-z0-9_]+$/.test(name)) {
316
+ throw new TlsTrustError("tls/bad-key-share",
317
+ "tls.pqc.setKeyShares: '" + name + "' has illegal characters " +
318
+ "(must match [A-Za-z0-9_]+)");
319
+ }
320
+ }
321
+
322
+ function setKeyShares(list) {
323
+ if (!Array.isArray(list) || list.length === 0) {
324
+ throw new TlsTrustError("tls/bad-key-shares",
325
+ "tls.pqc.setKeyShares: must be a non-empty array of group names");
326
+ }
327
+ for (var i = 0; i < list.length; i += 1) _validateKeyShare(list[i]);
328
+ STATE.tlsKeyShares = list.slice();
329
+ return getKeyShares();
330
+ }
331
+
332
+ function getKeyShares() { return STATE.tlsKeyShares.slice(); }
333
+
334
+ function resetKeyShares() {
335
+ STATE.tlsKeyShares = DEFAULT_PQC_KEY_SHARES.slice();
336
+ return getKeyShares();
337
+ }
338
+
339
+ var pqc = Object.freeze({
340
+ setKeyShares: setKeyShares,
341
+ getKeyShares: getKeyShares,
342
+ resetKeyShares: resetKeyShares,
343
+ DEFAULT_KEY_SHARES: DEFAULT_PQC_KEY_SHARES,
344
+ });
345
+
273
346
  function getCaPems() {
274
347
  return STATE.cas.map(function (e) { return e.pem; });
275
348
  }
@@ -307,6 +380,7 @@ function _resetForTest() {
307
380
  STATE.cas = [];
308
381
  STATE.systemTrust = false;
309
382
  STATE.baselineFingerprints = null;
383
+ STATE.tlsKeyShares = DEFAULT_PQC_KEY_SHARES.slice();
310
384
  }
311
385
 
312
386
  // ---- OCSP / OCSP-stapling wrappers around node:tls ----------------
@@ -1492,6 +1566,7 @@ module.exports = {
1492
1566
  getCaPems: getCaPems,
1493
1567
  ocsp: ocsp,
1494
1568
  ct: ct,
1569
+ pqc: pqc,
1495
1570
  TlsTrustError: TlsTrustError,
1496
1571
  _resetForTest: _resetForTest,
1497
1572
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.7.78",
3
+ "version": "0.7.80",
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:357e4142-2f9f-4e03-8132-644e87b5224e",
5
+ "serialNumber": "urn:uuid:1dbeaa45-dcef-4b96-9342-08799488a713",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-06T05:12:59.485Z",
8
+ "timestamp": "2026-05-06T05:36:24.642Z",
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.7.78",
22
+ "bom-ref": "@blamejs/core@0.7.80",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.7.78",
25
+ "version": "0.7.80",
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.7.78",
29
+ "purl": "pkg:npm/%40blamejs/core@0.7.80",
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.7.78",
57
+ "ref": "@blamejs/core@0.7.80",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]