@blamejs/core 0.9.7 → 0.9.9

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,16 @@
1
+ // vendor-data-pubkey — SLH-DSA-SHAKE-256f public key inlined as a CommonJS
2
+ // module so lib/vendor-data.js can verify .data.js signatures without
3
+ // any fs.readFileSync call. Packaging-mode-invariant (SEA / pkg / nexe /
4
+ // esbuild / Bun compile / Deno compile / Lambda all preserve require()
5
+ // resolution but not __dirname-relative file lookups).
6
+ //
7
+ // Fingerprint (SHA-256 of the raw SPKI): dd02aaf2f700e1403172f1e57619ba6308642e107853891866ffd8589b4164d1
8
+ //
9
+ // Regenerated by scripts/vendor-data-keygen.js whenever the maintainer
10
+ // vendor-data signing key rotates. The companion PEM at
11
+ // lib/vendor/.vendor-data-pubkey is the operator-readable form; this
12
+ // JS module is the runtime form. Both regenerate together.
13
+
14
+ "use strict";
15
+
16
+ module.exports = "-----BEGIN SLH-DSA-SHAKE-256F PUBLIC KEY-----\nNyFNN00fxmgkHnZwtfy1FU9SodrVbgJRihT5/S605tM7c6YAKIS1mry4y8VCKh2h\nG+oRhGOPLb1ebxNmcQZw+g==\n-----END SLH-DSA-SHAKE-256F PUBLIC KEY-----\n";
@@ -0,0 +1,363 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.vendorData
4
+ * @nav Supply Chain
5
+ * @title Vendor Data — packaging-mode-invariant + signed + canary-guarded
6
+ * @order 710
7
+ *
8
+ * @intro
9
+ * Loader for vendored data files that ship inside the framework
10
+ * tarball (`lib/vendor/*`). Inline-as-JS means the data survives any
11
+ * packaging mode — SEA, esbuild bundle, `pkg`, `nexe`, Bun compile,
12
+ * AWS Lambda — without `__dirname`-relative `fs.readFileSync` paths
13
+ * collapsing under the bundler. Every load runs four orthogonal
14
+ * integrity checks before returning a byte to the caller:
15
+ *
16
+ * 1. SHA-256 of the embedded payload matches the expected constant
17
+ * 2. SHA3-512 of the embedded payload matches the expected constant
18
+ * 3. SLH-DSA-SHAKE-256f signature over the payload verifies against
19
+ * the maintainer's pinned public key (`lib/vendor/.vendor-data-pubkey`)
20
+ * 4. The known canary entry (where applicable per data file) is
21
+ * present in the parsed payload — defends against content swap
22
+ * even when an attacker forges hashes + signatures
23
+ *
24
+ * Refusal at any step throws `VendorDataError`. `verifyAll()` runs
25
+ * at framework boot so a tampered vendor file is a fail-fast — not
26
+ * a first-request-touches-PSL surprise.
27
+ *
28
+ * Each vendored data file ships as a `<name>.data.js` module that
29
+ * exports `{ payload, metadata, canary }`. The `.data.js` is
30
+ * regenerated by `scripts/vendor-update.sh --refresh-data` whenever
31
+ * the upstream source is refreshed; it is never hand-edited.
32
+ *
33
+ * @card
34
+ * Signed + dual-hashed + canary-guarded loader for vendored data.
35
+ * Survives SEA / pkg / bundler builds because the data is inline,
36
+ * not `fs.readFileSync`-loaded.
37
+ */
38
+
39
+ var nodeCrypto = require("crypto");
40
+ var safeEnv = require("./parsers/safe-env");
41
+ var { defineClass } = require("./framework-error");
42
+ var pqcSoftware = require("./pqc-software");
43
+
44
+ // Framework-pinned vendor-data public key. Inlined as a CommonJS
45
+ // module (lib/vendor/vendor-data-pubkey.js) so the loader has zero
46
+ // fs.readFileSync calls and survives any packaging mode that
47
+ // preserves require() resolution (SEA, pkg, nexe, esbuild, Bun /
48
+ // Deno compile, Lambda layers). The companion PEM at
49
+ // lib/vendor/.vendor-data-pubkey is the operator-readable form for
50
+ // external inspection; this JS module is the runtime trust root.
51
+ // Both regenerate together whenever the maintainer signing key
52
+ // rotates (scripts/vendor-data-keygen.js).
53
+ var PUBKEY_PEM = require("./vendor/vendor-data-pubkey");
54
+
55
+ // Static require()s — every modern bundler (esbuild, webpack, ncc,
56
+ // rollup, Bun's bundler, Deno's bundler) traces these at bundle time
57
+ // only when the require() argument is a STRING LITERAL. A dynamic
58
+ // `require(variable)` is opaque to static analysis: bundlers can't
59
+ // determine what files to include, so the .data.js payloads silently
60
+ // fall out of the SEA / pkg / nexe / esbuild blob and the SEA-mode
61
+ // promise of v0.9.8 is defeated at boot. Each require below sits at
62
+ // column 0 with a literal-string argument so static analysis traces
63
+ // them; codebase-patterns enforces this via the testNoDynamicRequires
64
+ // + testNoInlineRequires detectors.
65
+ var _PSL_DATA = require("./vendor/public-suffix-list.data");
66
+ var _COMMON_PW_DATA = require("./vendor/common-passwords-top-10000.data");
67
+ var _BIMI_ANCHORS_DATA = require("./vendor/bimi-trust-anchors.data");
68
+
69
+ var _MODULES = {
70
+ "public-suffix-list": _PSL_DATA,
71
+ "common-passwords-top-10000": _COMMON_PW_DATA,
72
+ "bimi-trust-anchors": _BIMI_ANCHORS_DATA,
73
+ };
74
+
75
+ var VendorDataError = defineClass("VendorDataError", { alwaysPermanent: true });
76
+
77
+ // KNOWN_VENDOR_DATA — the canonical list of vendored data names. Each
78
+ // entry carries the canary token the payload must contain after parse
79
+ // (where applicable) and a description for the inventory surface.
80
+ //
81
+ // The `module` string is documentation-only as of v0.9.9 — the runtime
82
+ // uses the `_MODULES` table above (static literal-string requires) so
83
+ // bundlers can statically trace the .data.js dependency. The `module`
84
+ // field stays exported for downstream tooling that inspects
85
+ // `b.vendorData.KNOWN_VENDOR_DATA[name].module` for diagnostics or
86
+ // vendor-refresh tooling. NEVER call `require(entry.module)` — dynamic
87
+ // require(variable) breaks SEA / esbuild / pkg bundling.
88
+ var KNOWN_VENDOR_DATA = Object.freeze({
89
+ "public-suffix-list": {
90
+ module: "./vendor/public-suffix-list.data",
91
+ canary: "_blamejs_canary_v0_9_8_.local",
92
+ // Canary parse check — operator-side `b.publicSuffix.isPublicSuffix(canary)`
93
+ // MUST return true after the PSL parser ingests the data. The check
94
+ // is run by every `get()` via the .data.js's canaryCheck closure.
95
+ description: "Mozilla Public Suffix List (PSL). Used by b.publicSuffix for organizational-domain + public-suffix lookups.",
96
+ },
97
+ "common-passwords-top-10000": {
98
+ module: "./vendor/common-passwords-top-10000.data",
99
+ canary: "_blamejs_canary_password_2026_05_13_blamejs_internal_",
100
+ description: "Top-10000 most common passwords (SecLists). Used by b.auth.password to refuse known-breached credentials.",
101
+ },
102
+ "bimi-trust-anchors": {
103
+ module: "./vendor/bimi-trust-anchors.data",
104
+ canary: null,
105
+ // BIMI trust anchor file is a PEM bundle; injecting an in-payload
106
+ // canary would require minting a real self-signed cert against
107
+ // every refresh which would inflate the vendor pipeline. The
108
+ // dual-hash + SLH-DSA signature layers cover the threat; canary
109
+ // is intentionally skipped for this entry.
110
+ description: "BIMI Group VMC + CMC trust anchors. Used by b.mail.bimi.fetchAndVerifyMark for chain validation.",
111
+ },
112
+ });
113
+
114
+ // Per-name cache so verify-once-on-first-load + boot-time verifyAll
115
+ // don't pay the SLH-DSA-SHAKE-256f verify cost twice. Cached entries
116
+ // are payload Buffers (caller-receivable); signature verification
117
+ // runs at module-load (in `_loadAndVerify`), not at every `get()`.
118
+ var _payloadCache = Object.create(null);
119
+
120
+ function _loadAndVerify(name) {
121
+ if (_payloadCache[name]) return _payloadCache[name];
122
+
123
+ var entry = KNOWN_VENDOR_DATA[name];
124
+ if (!entry) {
125
+ throw new VendorDataError("vendor-data/unknown",
126
+ "vendorData: '" + name + "' not in KNOWN_VENDOR_DATA. " +
127
+ "Registered names: " + Object.keys(KNOWN_VENDOR_DATA).join(", "));
128
+ }
129
+
130
+ var mod = _MODULES[name];
131
+ if (!mod) {
132
+ throw new VendorDataError("vendor-data/module-missing",
133
+ "vendorData: '" + name + "' .data.js module not statically " +
134
+ "require'd by lib/vendor-data.js. Add the literal-string require " +
135
+ "to the _MODULES table at the top of the file — dynamic " + // allow:dynamic-require — diagnostic message text
136
+ "require(variable) breaks SEA / esbuild bundling."); // allow:dynamic-require — diagnostic message text
137
+ }
138
+
139
+ // Module-shape gate
140
+ if (!mod || !Buffer.isBuffer(mod.payload) || !mod.metadata) {
141
+ throw new VendorDataError("vendor-data/bad-shape",
142
+ "vendorData: '" + name + "' .data.js missing payload Buffer or metadata. " +
143
+ "File is hand-edited or corrupted; re-run vendor-update.sh --refresh-data.");
144
+ }
145
+ var meta = mod.metadata;
146
+ if (typeof meta.sha256 !== "string" || typeof meta.sha3_512 !== "string" ||
147
+ typeof meta.signatureB64 !== "string") {
148
+ throw new VendorDataError("vendor-data/bad-metadata",
149
+ "vendorData: '" + name + "' metadata missing sha256 / sha3_512 / signatureB64. " +
150
+ "File is hand-edited or corrupted; re-run vendor-update.sh --refresh-data.");
151
+ }
152
+
153
+ // Layer 1: SHA-256 self-verify
154
+ var actual256 = nodeCrypto.createHash("sha256").update(mod.payload).digest("hex");
155
+ if (actual256 !== meta.sha256) {
156
+ throw new VendorDataError("vendor-data/sha256-mismatch",
157
+ "vendorData: '" + name + "' SHA-256 mismatch — payload tampered. " +
158
+ "expected=" + meta.sha256.slice(0, 12) + "… got=" + actual256.slice(0, 12) + "…");
159
+ }
160
+
161
+ // Layer 2: SHA3-512 self-verify
162
+ var actual3 = nodeCrypto.createHash("sha3-512").update(mod.payload).digest("hex");
163
+ if (actual3 !== meta.sha3_512) {
164
+ throw new VendorDataError("vendor-data/sha3-512-mismatch",
165
+ "vendorData: '" + name + "' SHA3-512 mismatch — payload tampered. " +
166
+ "expected=" + meta.sha3_512.slice(0, 12) + "… got=" + actual3.slice(0, 12) + "…");
167
+ }
168
+
169
+ // Layer 3: SLH-DSA-SHAKE-256f signature verify against maintainer pubkey
170
+ var sigBytes = Buffer.from(meta.signatureB64, "base64");
171
+ var pubkeyBytes = _pemToRaw(PUBKEY_PEM);
172
+ var slh = pqcSoftware.slh_dsa_shake_256f;
173
+ var sigOk = false;
174
+ try {
175
+ // noble API: verify(signature, message, publicKey)
176
+ sigOk = slh.verify(sigBytes, mod.payload, pubkeyBytes);
177
+ } catch (e) {
178
+ throw new VendorDataError("vendor-data/signature-verify-error",
179
+ "vendorData: '" + name + "' SLH-DSA-SHAKE-256f verify threw: " +
180
+ (e && e.message ? e.message : "unknown error"));
181
+ }
182
+ if (!sigOk) {
183
+ throw new VendorDataError("vendor-data/signature-invalid",
184
+ "vendorData: '" + name + "' SLH-DSA-SHAKE-256f signature INVALID — " +
185
+ "payload not signed by maintainer pubkey (fingerprint " +
186
+ meta.publicKeyFingerprint + "). An attacker has swapped content + " +
187
+ "forged hashes but cannot forge the signature without the maintainer " +
188
+ "private key. Stop the framework and audit the install path immediately.");
189
+ }
190
+
191
+ // Layer 4: canary check (always-on, runs per get() not only at verifyAll).
192
+ // For entries with a registered canary, the .data.js exports a
193
+ // canaryCheck closure that scans the raw payload for the expected
194
+ // token. Refuses to return the bytes if the canary is absent.
195
+ if (entry.canary !== null) {
196
+ if (typeof mod.canaryCheck !== "function" || mod.canaryCheck() !== true) {
197
+ throw new VendorDataError("vendor-data/canary-absent",
198
+ "vendorData: '" + name + "' canary '" + entry.canary +
199
+ "' NOT FOUND in payload. An attacker has swapped data bytes with " +
200
+ "correctly-forged hashes + signatures but failed to preserve the " +
201
+ "canary token the framework expects.");
202
+ }
203
+ }
204
+
205
+ _payloadCache[name] = mod.payload;
206
+ return mod.payload;
207
+ }
208
+
209
+ // _pemToRaw — extract the raw SPKI bytes from a PEM-wrapped public key.
210
+ // The .vendor-data-pubkey file ships as PEM (BEGIN PUBLIC KEY ... END
211
+ // PUBLIC KEY); the SLH-DSA verifier expects raw bytes. We strip the
212
+ // header/footer + decode base64. Defensive — refuses anything that
213
+ // doesn't look like the expected PEM shape.
214
+ function _pemToRaw(pem) {
215
+ var lines = pem.replace(/\r/g, "").split("\n");
216
+ if (lines[0].indexOf("-----BEGIN ") !== 0) {
217
+ throw new VendorDataError("vendor-data/pubkey-bad-pem",
218
+ "vendorData: inlined pubkey module is not PEM-shaped (lib/vendor/vendor-data-pubkey.js corrupted; re-run scripts/vendor-data-keygen.js)");
219
+ }
220
+ var body = "";
221
+ for (var i = 1; i < lines.length; i++) {
222
+ if (lines[i].indexOf("-----END ") === 0) break;
223
+ body += lines[i];
224
+ }
225
+ return Buffer.from(body, "base64");
226
+ }
227
+
228
+ /**
229
+ * @primitive b.vendorData.get
230
+ * @signature b.vendorData.get(name)
231
+ * @since 0.9.8
232
+ * @status stable
233
+ * @related b.vendorData.verifyAll, b.vendorData.inventory
234
+ *
235
+ * Return the verified payload Buffer for the named vendored data file.
236
+ * First call per name runs all integrity layers (dual-hash + SLH-DSA
237
+ * signature); subsequent calls return from cache. The canary-in-payload
238
+ * check is run by `verifyAll()` at framework boot; `get()` skips it on
239
+ * the hot path because the canary's parse-side semantics are caller-
240
+ * specific (PSL parser vs password-set lookup vs PEM chain).
241
+ *
242
+ * @example
243
+ * var pslBytes = b.vendorData.get("public-suffix-list");
244
+ * var psl = pslBytes.toString("utf8");
245
+ */
246
+ function get(name) {
247
+ return _loadAndVerify(name);
248
+ }
249
+
250
+ /**
251
+ * @primitive b.vendorData.getAsString
252
+ * @signature b.vendorData.getAsString(name)
253
+ * @since 0.9.8
254
+ * @status stable
255
+ * @related b.vendorData.get
256
+ *
257
+ * Convenience wrapper around `get(name)` that returns the payload
258
+ * decoded as UTF-8. Use when the caller wants a string directly; the
259
+ * underlying Buffer caches once so repeated calls don't re-decode.
260
+ *
261
+ * @example
262
+ * var passwordList = b.vendorData.getAsString("common-passwords-top-10000");
263
+ * var lines = passwordList.split(/\r?\n/);
264
+ */
265
+ function getAsString(name) {
266
+ return _loadAndVerify(name).toString("utf8");
267
+ }
268
+
269
+ /**
270
+ * @primitive b.vendorData.verifyAll
271
+ * @signature b.vendorData.verifyAll()
272
+ * @since 0.9.8
273
+ * @status stable
274
+ * @related b.vendorData.get, b.vendorData.inventory
275
+ *
276
+ * Run all four integrity layers across every registered vendored data
277
+ * file — including the in-payload canary check via the caller-supplied
278
+ * canaryCheck closure each `.data.js` exports. Returns the inventory
279
+ * of names verified. Throws on any failure. Operators wire this into
280
+ * framework boot so a tampered install fails fast.
281
+ *
282
+ * @example
283
+ * b.vendorData.verifyAll(); // throws VendorDataError on any tamper
284
+ */
285
+ function verifyAll() {
286
+ var names = Object.keys(KNOWN_VENDOR_DATA);
287
+ for (var i = 0; i < names.length; i++) {
288
+ // _loadAndVerify runs all four layers (sha256 + sha3-512 +
289
+ // SLH-DSA signature + canary). verifyAll() now just forces
290
+ // eager-load of every registered entry — useful in tests and
291
+ // for operators wanting "verify everything upfront at boot"
292
+ // semantics even before any caller touches a specific entry.
293
+ _loadAndVerify(names[i]);
294
+ }
295
+ return names.slice();
296
+ }
297
+
298
+ /**
299
+ * @primitive b.vendorData.inventory
300
+ * @signature b.vendorData.inventory()
301
+ * @since 0.9.8
302
+ * @status stable
303
+ * @related b.vendorData.verifyAll
304
+ *
305
+ * Return per-vendor-data metadata for compliance reporting. Each
306
+ * entry: `{ name, source, fetchedAt, sha256, sha3_512, signedBy,
307
+ * canary, byteLength }`. Pipes directly into SBOM emission +
308
+ * b.compliance posture rendering.
309
+ *
310
+ * @example
311
+ * var inv = b.vendorData.inventory();
312
+ * inv.forEach(function (entry) {
313
+ * console.log(entry.name + " (" + entry.byteLength + " bytes) " +
314
+ * "fetched " + entry.fetchedAt + " from " + entry.source +
315
+ * " — signed by " + entry.signedBy);
316
+ * });
317
+ */
318
+ function inventory() {
319
+ var out = [];
320
+ var names = Object.keys(KNOWN_VENDOR_DATA);
321
+ for (var i = 0; i < names.length; i++) {
322
+ var name = names[i];
323
+ var entry = KNOWN_VENDOR_DATA[name];
324
+ _loadAndVerify(name);
325
+ var mod = _MODULES[name];
326
+ var meta = mod.metadata;
327
+ out.push({
328
+ name: name,
329
+ source: meta.source,
330
+ fetchedAt: meta.fetchedAt,
331
+ license: meta.license,
332
+ sha256: meta.sha256,
333
+ sha3_512: meta.sha3_512,
334
+ signedBy: meta.publicKeyFingerprint,
335
+ canary: entry.canary,
336
+ byteLength: mod.payload.length,
337
+ description: entry.description,
338
+ });
339
+ }
340
+ return out;
341
+ }
342
+
343
+ // Eager verification at module-load. The first time anything
344
+ // require()s b.vendorData (e.g. lib/public-suffix.js, lib/auth/
345
+ // password.js, lib/mail-bimi.js — all of which load early in any
346
+ // framework consumer's import graph), every registered vendor data
347
+ // file is dual-hash + signature + canary-verified before any caller
348
+ // gets the chance to call get(). Tamper = fail-fast at boot, not at
349
+ // first-request-touches-PSL surprise. Operators wanting to defer
350
+ // verification (e.g. test rigs that mock require() resolution) set
351
+ // BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY=1 in the environment.
352
+ if (safeEnv.readVar("BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY", { default: "" }) !== "1") {
353
+ verifyAll();
354
+ }
355
+
356
+ module.exports = {
357
+ get: get,
358
+ getAsString: getAsString,
359
+ verifyAll: verifyAll,
360
+ inventory: inventory,
361
+ KNOWN_VENDOR_DATA: KNOWN_VENDOR_DATA,
362
+ VendorDataError: VendorDataError,
363
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
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.6",
5
- "serialNumber": "urn:uuid:4587a803-c639-4aa0-ba4d-6b9bb2ad5845",
5
+ "serialNumber": "urn:uuid:cd59dfcc-1a4e-488c-b7fa-8f4b9ac61c45",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-13T05:39:58.426Z",
8
+ "timestamp": "2026-05-13T14:31:49.840Z",
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.9.7",
22
+ "bom-ref": "@blamejs/core@0.9.9",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.9.7",
25
+ "version": "0.9.9",
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.9.7",
29
+ "purl": "pkg:npm/%40blamejs/core@0.9.9",
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.9.7",
57
+ "ref": "@blamejs/core@0.9.9",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]