@blamejs/core 0.9.6 → 0.9.8

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,339 @@
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
+ var VendorDataError = defineClass("VendorDataError", { alwaysPermanent: true });
56
+
57
+ // KNOWN_VENDOR_DATA — the canonical list of vendored data names. Each
58
+ // entry maps name → `<name>.data.js` module + the canary token the
59
+ // payload must contain after parse (where applicable). Adding a new
60
+ // vendored data file: append to this table, ship the `.data.js`,
61
+ // migrate the caller. The framework's drift detector
62
+ // (codebase-patterns) refuses any `.data.js` not registered here.
63
+ var KNOWN_VENDOR_DATA = Object.freeze({
64
+ "public-suffix-list": {
65
+ module: "./vendor/public-suffix-list.data",
66
+ canary: "_blamejs_canary_v0_9_8_.local",
67
+ // Canary parse check — operator-side `b.publicSuffix.isPublicSuffix(canary)` MUST return true after the PSL parser ingests
68
+ // the data. The check is run by `verifyAll()` at boot via the
69
+ // caller-supplied canaryCheck closure registered in the .data.js.
70
+ description: "Mozilla Public Suffix List (PSL). Used by b.publicSuffix for organizational-domain + public-suffix lookups.",
71
+ },
72
+ "common-passwords-top-10000": {
73
+ module: "./vendor/common-passwords-top-10000.data",
74
+ canary: "_blamejs_canary_password_2026_05_13_blamejs_internal_",
75
+ description: "Top-10000 most common passwords (SecLists). Used by b.auth.password to refuse known-breached credentials.",
76
+ },
77
+ "bimi-trust-anchors": {
78
+ module: "./vendor/bimi-trust-anchors.data",
79
+ canary: null,
80
+ // BIMI trust anchor file is a PEM bundle; injecting an in-payload
81
+ // canary would require minting a real self-signed cert against
82
+ // every refresh which would inflate the vendor pipeline. The
83
+ // dual-hash + SLH-DSA signature layers cover the threat; canary
84
+ // is intentionally skipped for this entry.
85
+ description: "BIMI Group VMC + CMC trust anchors. Used by b.mail.bimi.fetchAndVerifyMark for chain validation.",
86
+ },
87
+ });
88
+
89
+ // Per-name cache so verify-once-on-first-load + boot-time verifyAll
90
+ // don't pay the SLH-DSA-SHAKE-256f verify cost twice. Cached entries
91
+ // are payload Buffers (caller-receivable); signature verification
92
+ // runs at module-load (in `_loadAndVerify`), not at every `get()`.
93
+ var _payloadCache = Object.create(null);
94
+
95
+ function _loadAndVerify(name) {
96
+ if (_payloadCache[name]) return _payloadCache[name];
97
+
98
+ var entry = KNOWN_VENDOR_DATA[name];
99
+ if (!entry) {
100
+ throw new VendorDataError("vendor-data/unknown",
101
+ "vendorData: '" + name + "' not in KNOWN_VENDOR_DATA. " +
102
+ "Registered names: " + Object.keys(KNOWN_VENDOR_DATA).join(", "));
103
+ }
104
+
105
+ var mod;
106
+ try {
107
+ mod = require(entry.module);
108
+ } catch (e) {
109
+ throw new VendorDataError("vendor-data/module-missing",
110
+ "vendorData: '" + name + "' module not loadable at " + entry.module +
111
+ " — has scripts/vendor-update.sh --refresh-data been run? " +
112
+ "(" + (e && e.message ? e.message : "unknown error") + ")");
113
+ }
114
+
115
+ // Module-shape gate
116
+ if (!mod || !Buffer.isBuffer(mod.payload) || !mod.metadata) {
117
+ throw new VendorDataError("vendor-data/bad-shape",
118
+ "vendorData: '" + name + "' .data.js missing payload Buffer or metadata. " +
119
+ "File is hand-edited or corrupted; re-run vendor-update.sh --refresh-data.");
120
+ }
121
+ var meta = mod.metadata;
122
+ if (typeof meta.sha256 !== "string" || typeof meta.sha3_512 !== "string" ||
123
+ typeof meta.signatureB64 !== "string") {
124
+ throw new VendorDataError("vendor-data/bad-metadata",
125
+ "vendorData: '" + name + "' metadata missing sha256 / sha3_512 / signatureB64. " +
126
+ "File is hand-edited or corrupted; re-run vendor-update.sh --refresh-data.");
127
+ }
128
+
129
+ // Layer 1: SHA-256 self-verify
130
+ var actual256 = nodeCrypto.createHash("sha256").update(mod.payload).digest("hex");
131
+ if (actual256 !== meta.sha256) {
132
+ throw new VendorDataError("vendor-data/sha256-mismatch",
133
+ "vendorData: '" + name + "' SHA-256 mismatch — payload tampered. " +
134
+ "expected=" + meta.sha256.slice(0, 12) + "… got=" + actual256.slice(0, 12) + "…");
135
+ }
136
+
137
+ // Layer 2: SHA3-512 self-verify
138
+ var actual3 = nodeCrypto.createHash("sha3-512").update(mod.payload).digest("hex");
139
+ if (actual3 !== meta.sha3_512) {
140
+ throw new VendorDataError("vendor-data/sha3-512-mismatch",
141
+ "vendorData: '" + name + "' SHA3-512 mismatch — payload tampered. " +
142
+ "expected=" + meta.sha3_512.slice(0, 12) + "… got=" + actual3.slice(0, 12) + "…");
143
+ }
144
+
145
+ // Layer 3: SLH-DSA-SHAKE-256f signature verify against maintainer pubkey
146
+ var sigBytes = Buffer.from(meta.signatureB64, "base64");
147
+ var pubkeyBytes = _pemToRaw(PUBKEY_PEM);
148
+ var slh = pqcSoftware.slh_dsa_shake_256f;
149
+ var sigOk = false;
150
+ try {
151
+ // noble API: verify(signature, message, publicKey)
152
+ sigOk = slh.verify(sigBytes, mod.payload, pubkeyBytes);
153
+ } catch (e) {
154
+ throw new VendorDataError("vendor-data/signature-verify-error",
155
+ "vendorData: '" + name + "' SLH-DSA-SHAKE-256f verify threw: " +
156
+ (e && e.message ? e.message : "unknown error"));
157
+ }
158
+ if (!sigOk) {
159
+ throw new VendorDataError("vendor-data/signature-invalid",
160
+ "vendorData: '" + name + "' SLH-DSA-SHAKE-256f signature INVALID — " +
161
+ "payload not signed by maintainer pubkey (fingerprint " +
162
+ meta.publicKeyFingerprint + "). An attacker has swapped content + " +
163
+ "forged hashes but cannot forge the signature without the maintainer " +
164
+ "private key. Stop the framework and audit the install path immediately.");
165
+ }
166
+
167
+ // Layer 4: canary check (always-on, runs per get() not only at verifyAll).
168
+ // For entries with a registered canary, the .data.js exports a
169
+ // canaryCheck closure that scans the raw payload for the expected
170
+ // token. Refuses to return the bytes if the canary is absent.
171
+ if (entry.canary !== null) {
172
+ if (typeof mod.canaryCheck !== "function" || mod.canaryCheck() !== true) {
173
+ throw new VendorDataError("vendor-data/canary-absent",
174
+ "vendorData: '" + name + "' canary '" + entry.canary +
175
+ "' NOT FOUND in payload. An attacker has swapped data bytes with " +
176
+ "correctly-forged hashes + signatures but failed to preserve the " +
177
+ "canary token the framework expects.");
178
+ }
179
+ }
180
+
181
+ _payloadCache[name] = mod.payload;
182
+ return mod.payload;
183
+ }
184
+
185
+ // _pemToRaw — extract the raw SPKI bytes from a PEM-wrapped public key.
186
+ // The .vendor-data-pubkey file ships as PEM (BEGIN PUBLIC KEY ... END
187
+ // PUBLIC KEY); the SLH-DSA verifier expects raw bytes. We strip the
188
+ // header/footer + decode base64. Defensive — refuses anything that
189
+ // doesn't look like the expected PEM shape.
190
+ function _pemToRaw(pem) {
191
+ var lines = pem.replace(/\r/g, "").split("\n");
192
+ if (lines[0].indexOf("-----BEGIN ") !== 0) {
193
+ throw new VendorDataError("vendor-data/pubkey-bad-pem",
194
+ "vendorData: inlined pubkey module is not PEM-shaped (lib/vendor/vendor-data-pubkey.js corrupted; re-run scripts/vendor-data-keygen.js)");
195
+ }
196
+ var body = "";
197
+ for (var i = 1; i < lines.length; i++) {
198
+ if (lines[i].indexOf("-----END ") === 0) break;
199
+ body += lines[i];
200
+ }
201
+ return Buffer.from(body, "base64");
202
+ }
203
+
204
+ /**
205
+ * @primitive b.vendorData.get
206
+ * @signature b.vendorData.get(name)
207
+ * @since 0.9.8
208
+ * @status stable
209
+ * @related b.vendorData.verifyAll, b.vendorData.inventory
210
+ *
211
+ * Return the verified payload Buffer for the named vendored data file.
212
+ * First call per name runs all integrity layers (dual-hash + SLH-DSA
213
+ * signature); subsequent calls return from cache. The canary-in-payload
214
+ * check is run by `verifyAll()` at framework boot; `get()` skips it on
215
+ * the hot path because the canary's parse-side semantics are caller-
216
+ * specific (PSL parser vs password-set lookup vs PEM chain).
217
+ *
218
+ * @example
219
+ * var pslBytes = b.vendorData.get("public-suffix-list");
220
+ * var psl = pslBytes.toString("utf8");
221
+ */
222
+ function get(name) {
223
+ return _loadAndVerify(name);
224
+ }
225
+
226
+ /**
227
+ * @primitive b.vendorData.getAsString
228
+ * @signature b.vendorData.getAsString(name)
229
+ * @since 0.9.8
230
+ * @status stable
231
+ * @related b.vendorData.get
232
+ *
233
+ * Convenience wrapper around `get(name)` that returns the payload
234
+ * decoded as UTF-8. Use when the caller wants a string directly; the
235
+ * underlying Buffer caches once so repeated calls don't re-decode.
236
+ *
237
+ * @example
238
+ * var passwordList = b.vendorData.getAsString("common-passwords-top-10000");
239
+ * var lines = passwordList.split(/\r?\n/);
240
+ */
241
+ function getAsString(name) {
242
+ return _loadAndVerify(name).toString("utf8");
243
+ }
244
+
245
+ /**
246
+ * @primitive b.vendorData.verifyAll
247
+ * @signature b.vendorData.verifyAll()
248
+ * @since 0.9.8
249
+ * @status stable
250
+ * @related b.vendorData.get, b.vendorData.inventory
251
+ *
252
+ * Run all four integrity layers across every registered vendored data
253
+ * file — including the in-payload canary check via the caller-supplied
254
+ * canaryCheck closure each `.data.js` exports. Returns the inventory
255
+ * of names verified. Throws on any failure. Operators wire this into
256
+ * framework boot so a tampered install fails fast.
257
+ *
258
+ * @example
259
+ * b.vendorData.verifyAll(); // throws VendorDataError on any tamper
260
+ */
261
+ function verifyAll() {
262
+ var names = Object.keys(KNOWN_VENDOR_DATA);
263
+ for (var i = 0; i < names.length; i++) {
264
+ // _loadAndVerify runs all four layers (sha256 + sha3-512 +
265
+ // SLH-DSA signature + canary). verifyAll() now just forces
266
+ // eager-load of every registered entry — useful in tests and
267
+ // for operators wanting "verify everything upfront at boot"
268
+ // semantics even before any caller touches a specific entry.
269
+ _loadAndVerify(names[i]);
270
+ }
271
+ return names.slice();
272
+ }
273
+
274
+ /**
275
+ * @primitive b.vendorData.inventory
276
+ * @signature b.vendorData.inventory()
277
+ * @since 0.9.8
278
+ * @status stable
279
+ * @related b.vendorData.verifyAll
280
+ *
281
+ * Return per-vendor-data metadata for compliance reporting. Each
282
+ * entry: `{ name, source, fetchedAt, sha256, sha3_512, signedBy,
283
+ * canary, byteLength }`. Pipes directly into SBOM emission +
284
+ * b.compliance posture rendering.
285
+ *
286
+ * @example
287
+ * var inv = b.vendorData.inventory();
288
+ * inv.forEach(function (entry) {
289
+ * console.log(entry.name + " (" + entry.byteLength + " bytes) " +
290
+ * "fetched " + entry.fetchedAt + " from " + entry.source +
291
+ * " — signed by " + entry.signedBy);
292
+ * });
293
+ */
294
+ function inventory() {
295
+ var out = [];
296
+ var names = Object.keys(KNOWN_VENDOR_DATA);
297
+ for (var i = 0; i < names.length; i++) {
298
+ var name = names[i];
299
+ var entry = KNOWN_VENDOR_DATA[name];
300
+ _loadAndVerify(name);
301
+ var mod = require(entry.module);
302
+ var meta = mod.metadata;
303
+ out.push({
304
+ name: name,
305
+ source: meta.source,
306
+ fetchedAt: meta.fetchedAt,
307
+ license: meta.license,
308
+ sha256: meta.sha256,
309
+ sha3_512: meta.sha3_512,
310
+ signedBy: meta.publicKeyFingerprint,
311
+ canary: entry.canary,
312
+ byteLength: mod.payload.length,
313
+ description: entry.description,
314
+ });
315
+ }
316
+ return out;
317
+ }
318
+
319
+ // Eager verification at module-load. The first time anything
320
+ // require()s b.vendorData (e.g. lib/public-suffix.js, lib/auth/
321
+ // password.js, lib/mail-bimi.js — all of which load early in any
322
+ // framework consumer's import graph), every registered vendor data
323
+ // file is dual-hash + signature + canary-verified before any caller
324
+ // gets the chance to call get(). Tamper = fail-fast at boot, not at
325
+ // first-request-touches-PSL surprise. Operators wanting to defer
326
+ // verification (e.g. test rigs that mock require() resolution) set
327
+ // BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY=1 in the environment.
328
+ if (safeEnv.readVar("BLAMEJS_VENDOR_DATA_DEFER_BOOT_VERIFY", { default: "" }) !== "1") {
329
+ verifyAll();
330
+ }
331
+
332
+ module.exports = {
333
+ get: get,
334
+ getAsString: getAsString,
335
+ verifyAll: verifyAll,
336
+ inventory: inventory,
337
+ KNOWN_VENDOR_DATA: KNOWN_VENDOR_DATA,
338
+ VendorDataError: VendorDataError,
339
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
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:1564ed32-1ca1-46e6-be60-6ea469f08940",
5
+ "serialNumber": "urn:uuid:b2d344e9-ce35-4d60-9617-51bdb1f2c4a4",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-12T13:18:41.980Z",
8
+ "timestamp": "2026-05-13T13:08:39.464Z",
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.6",
22
+ "bom-ref": "@blamejs/core@0.9.8",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.9.6",
25
+ "version": "0.9.8",
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.6",
29
+ "purl": "pkg:npm/%40blamejs/core@0.9.8",
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.6",
57
+ "ref": "@blamejs/core@0.9.8",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]