@blamejs/blamejs-shop 0.2.31 → 0.3.1

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.
@@ -287,6 +287,200 @@ async function testCreateAppMiddlewareDisableable() {
287
287
  }
288
288
  }
289
289
 
290
+ // Extract a 64-hex csrf token for `name` from a response's Set-Cookie(s).
291
+ function _csrfCookieFrom(resp, name) {
292
+ var sc = resp.headers["set-cookie"];
293
+ if (!sc) return null;
294
+ var arr = Array.isArray(sc) ? sc : [sc];
295
+ var re = new RegExp("(?:^|;\\s*)" + name + "=([a-f0-9]{64})");
296
+ for (var i = 0; i < arr.length; i++) {
297
+ var m = re.exec(arr[i]);
298
+ if (m) return m[1];
299
+ }
300
+ return null;
301
+ }
302
+
303
+ async function testCreateAppSecurityDefaultsWired() {
304
+ // createApp wires cookies + cspNonce + fetchMetadata + bodyParser + csrf
305
+ // ON by default (Core Rule §3). Verify cspNonce patches the CSP, csrf
306
+ // issues a double-submit cookie + enforces it, and the stateless-skip
307
+ // lets token-API / cookieless callers through.
308
+ process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
309
+ process.env.BLAMEJS_AUDIT_SIGNING_MODE = "plaintext";
310
+ b.cluster._resetForTest();
311
+ b.audit._resetForTest();
312
+ b.vault._resetForTest();
313
+ b.db._resetForTest();
314
+ var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-app-sec-"));
315
+ var app = await b.createApp({
316
+ dataDir: dataDir,
317
+ vault: { mode: "plaintext" },
318
+ db: { atRest: "plain", auditSigning: { mode: "plaintext" } },
319
+ schema: [],
320
+ middleware: { botGuard: false }, // bot-guard refuses non-browser-y test clients
321
+ routes: function (r) {
322
+ r.get("/", function (req, res) { b.render.text(res, "OK"); });
323
+ r.post("/act", function (req, res) { b.render.text(res, "DID"); });
324
+ },
325
+ });
326
+ var addr = await app.listen({ port: 0, host: "127.0.0.1" });
327
+ try {
328
+ var g = await _appGet(addr.port, "/");
329
+ check("security defaults: cspNonce patched the CSP header",
330
+ typeof g.headers["content-security-policy"] === "string" &&
331
+ g.headers["content-security-policy"].indexOf("nonce-") !== -1);
332
+ var token = _csrfCookieFrom(g, "csrf");
333
+ check("security defaults: csrf double-submit cookie issued",
334
+ typeof token === "string" && token.length === 64);
335
+
336
+ // Cookie-bearing POST without a token → enforced (403).
337
+ var noTok = await b.httpClient.request({
338
+ method: "POST", url: "http://127.0.0.1:" + addr.port + "/act",
339
+ headers: { "Cookie": "csrf=" + token }, body: Buffer.from(""),
340
+ responseMode: "always-resolve",
341
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
342
+ });
343
+ check("security defaults: cookie'd POST without token → 403", noTok.statusCode === 403);
344
+
345
+ // Cookie + matching header token → validates (200).
346
+ var withTok = await b.httpClient.request({
347
+ method: "POST", url: "http://127.0.0.1:" + addr.port + "/act",
348
+ headers: { "Cookie": "csrf=" + token, "X-CSRF-Token": token }, body: Buffer.from(""),
349
+ responseMode: "always-resolve",
350
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
351
+ });
352
+ check("security defaults: cookie'd POST with valid token → 200", withTok.statusCode === 200);
353
+
354
+ // Cookieless POST → not CSRF-able → skipStateless skip (200).
355
+ var cookieless = await b.httpClient.request({
356
+ method: "POST", url: "http://127.0.0.1:" + addr.port + "/act",
357
+ body: Buffer.from(""), responseMode: "always-resolve",
358
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
359
+ });
360
+ check("security defaults: cookieless POST skipped → 200", cookieless.statusCode === 200);
361
+
362
+ // Bearer-authenticated POST (even WITH a cookie) → token-auth, not
363
+ // CSRF-able → skipStateless skip (200).
364
+ var bearer = await b.httpClient.request({
365
+ method: "POST", url: "http://127.0.0.1:" + addr.port + "/act",
366
+ headers: { "Cookie": "csrf=" + token, "Authorization": "Bearer test.token.value" },
367
+ body: Buffer.from(""), responseMode: "always-resolve",
368
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
369
+ });
370
+ check("security defaults: bearer-auth POST skipped → 200", bearer.statusCode === 200);
371
+ } finally {
372
+ await app.shutdown();
373
+ fs.rmSync(dataDir, { recursive: true, force: true });
374
+ }
375
+ }
376
+
377
+ async function testCreateAppCsrfCustomNaming() {
378
+ // The default csrf wiring is configurable — operator cookie/field names
379
+ // flow straight through opts.middleware.csrf, nothing static is baked in.
380
+ process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
381
+ process.env.BLAMEJS_AUDIT_SIGNING_MODE = "plaintext";
382
+ b.cluster._resetForTest();
383
+ b.audit._resetForTest();
384
+ b.vault._resetForTest();
385
+ b.db._resetForTest();
386
+ var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-app-csrfname-"));
387
+ var app = await b.createApp({
388
+ dataDir: dataDir,
389
+ vault: { mode: "plaintext" },
390
+ db: { atRest: "plain", auditSigning: { mode: "plaintext" } },
391
+ schema: [],
392
+ middleware: { botGuard: false, csrf: { cookie: { name: "app_csrf" }, fieldName: "tok" } },
393
+ routes: function (r) { r.get("/", function (req, res) { b.render.text(res, "OK"); }); },
394
+ });
395
+ var addr = await app.listen({ port: 0, host: "127.0.0.1" });
396
+ try {
397
+ var g = await _appGet(addr.port, "/");
398
+ check("csrf custom naming: operator cookie name issued through the default wiring",
399
+ typeof _csrfCookieFrom(g, "app_csrf") === "string");
400
+ check("csrf custom naming: default 'csrf' cookie name not used",
401
+ _csrfCookieFrom(g, "csrf") === null);
402
+ } finally {
403
+ await app.shutdown();
404
+ fs.rmSync(dataDir, { recursive: true, force: true });
405
+ }
406
+ }
407
+
408
+ async function testCreateAppSecurityDisableAudits() {
409
+ // Disabling a security default leaves an audit trace (app.middleware.
410
+ // disabled) and actually drops the middleware.
411
+ process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
412
+ process.env.BLAMEJS_AUDIT_SIGNING_MODE = "plaintext";
413
+ b.cluster._resetForTest();
414
+ b.audit._resetForTest();
415
+ b.vault._resetForTest();
416
+ b.db._resetForTest();
417
+ var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-app-disable-"));
418
+ var emitted = [];
419
+ var realSafeEmit = b.audit.safeEmit;
420
+ b.audit.safeEmit = function (ev) { emitted.push(ev); return realSafeEmit.call(b.audit, ev); };
421
+ var app;
422
+ try {
423
+ app = await b.createApp({
424
+ dataDir: dataDir,
425
+ vault: { mode: "plaintext" },
426
+ db: { atRest: "plain", auditSigning: { mode: "plaintext" } },
427
+ schema: [],
428
+ middleware: { botGuard: false, csrf: false },
429
+ routes: function (r) { r.get("/", function (req, res) { b.render.text(res, "OK"); }); },
430
+ });
431
+ var disabled = emitted
432
+ .filter(function (e) { return e && e.action === "app.middleware.disabled"; })
433
+ .map(function (e) { return e.metadata && e.metadata.middleware; });
434
+ check("disable audit: csrf disable emits app.middleware.disabled",
435
+ disabled.indexOf("csrf") !== -1);
436
+ check("disable audit: botGuard disable emits app.middleware.disabled",
437
+ disabled.indexOf("botGuard") !== -1);
438
+ var addr = await app.listen({ port: 0, host: "127.0.0.1" });
439
+ var g = await _appGet(addr.port, "/");
440
+ check("disable audit: csrf off → no csrf cookie issued",
441
+ _csrfCookieFrom(g, "csrf") === null);
442
+ } finally {
443
+ b.audit.safeEmit = realSafeEmit;
444
+ if (app) await app.shutdown();
445
+ fs.rmSync(dataDir, { recursive: true, force: true });
446
+ }
447
+ }
448
+
449
+ async function testCreateAppCsrfIdempotent() {
450
+ // An operator who ALSO mounts csrf inside opts.routes must not double-
451
+ // apply — the second mount is a no-op (single cookie issued).
452
+ process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
453
+ process.env.BLAMEJS_AUDIT_SIGNING_MODE = "plaintext";
454
+ b.cluster._resetForTest();
455
+ b.audit._resetForTest();
456
+ b.vault._resetForTest();
457
+ b.db._resetForTest();
458
+ var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-app-idem-"));
459
+ var app = await b.createApp({
460
+ dataDir: dataDir,
461
+ vault: { mode: "plaintext" },
462
+ db: { atRest: "plain", auditSigning: { mode: "plaintext" } },
463
+ schema: [],
464
+ middleware: { botGuard: false },
465
+ routes: function (r) {
466
+ r.use(b.middleware.csrfProtect({ cookie: true })); // redundant — createApp already wired csrf
467
+ r.get("/", function (req, res) { b.render.text(res, "OK"); });
468
+ },
469
+ });
470
+ var addr = await app.listen({ port: 0, host: "127.0.0.1" });
471
+ try {
472
+ var g = await _appGet(addr.port, "/");
473
+ var sc = g.headers["set-cookie"];
474
+ var arr = Array.isArray(sc) ? sc : (sc ? [sc] : []);
475
+ var csrfCookies = arr.filter(function (c) { return /^csrf=/.test(c); });
476
+ check("csrf idempotent: redundant route-level mount issues a single csrf cookie",
477
+ csrfCookies.length === 1);
478
+ } finally {
479
+ await app.shutdown();
480
+ fs.rmSync(dataDir, { recursive: true, force: true });
481
+ }
482
+ }
483
+
290
484
  async function testCreateAppRoutesCallback() {
291
485
  process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
292
486
  process.env.BLAMEJS_AUDIT_SIGNING_MODE = "plaintext";
@@ -462,6 +656,10 @@ async function run() {
462
656
  await testCreateAppMinimalBoot();
463
657
  await testCreateAppDefaultMiddleware();
464
658
  await testCreateAppMiddlewareDisableable();
659
+ await testCreateAppSecurityDefaultsWired();
660
+ await testCreateAppCsrfCustomNaming();
661
+ await testCreateAppSecurityDisableAudits();
662
+ await testCreateAppCsrfIdempotent();
465
663
  await testCreateAppRoutesCallback();
466
664
  await testCreateAppWithJobs();
467
665
  await testCreateAppShutdown();
@@ -480,6 +678,10 @@ module.exports = {
480
678
  testCreateAppMinimalBoot: testCreateAppMinimalBoot,
481
679
  testCreateAppDefaultMiddleware: testCreateAppDefaultMiddleware,
482
680
  testCreateAppMiddlewareDisableable: testCreateAppMiddlewareDisableable,
681
+ testCreateAppSecurityDefaultsWired: testCreateAppSecurityDefaultsWired,
682
+ testCreateAppCsrfCustomNaming: testCreateAppCsrfCustomNaming,
683
+ testCreateAppSecurityDisableAudits: testCreateAppSecurityDisableAudits,
684
+ testCreateAppCsrfIdempotent: testCreateAppCsrfIdempotent,
483
685
  testCreateAppRoutesCallback: testCreateAppRoutesCallback,
484
686
  testCreateAppWithJobs: testCreateAppWithJobs,
485
687
  testCreateAppShutdown: testCreateAppShutdown,
@@ -245,6 +245,49 @@ function testFactoryRefusesBadOpts() {
245
245
  });
246
246
  check("cert name '" + JSON.stringify(badName) + "' refused as path-segment", e && e.code === "cert/bad-cert-name");
247
247
  });
248
+
249
+ // ---- compliance posture validation ----
250
+ // opts.compliance names are validated against b.compliance.KNOWN_POSTURES
251
+ // at create() so a typo is caught at boot rather than silently recorded.
252
+ var goodChallenge = { type: "http-01", provision: function () {}, cleanup: function () {} };
253
+ var eBadPosture = threw(function () {
254
+ b.cert.create({
255
+ storage: { type: "sealed-disk", rootDir: _tmpDir(), vault: _ephemeralVault() },
256
+ acme: { directory: "https://example/", accountKey: "auto" },
257
+ certs: [{ name: "m", domains: ["a.com"], challenge: goodChallenge }],
258
+ compliance: ["not-a-real-posture"],
259
+ audit: false,
260
+ });
261
+ });
262
+ check("unknown compliance posture → cert/unknown-compliance-posture",
263
+ eBadPosture && eBadPosture.code === "cert/unknown-compliance-posture");
264
+
265
+ var eGoodPosture = threw(function () {
266
+ b.cert.create({
267
+ storage: { type: "sealed-disk", rootDir: _tmpDir(), vault: _ephemeralVault() },
268
+ acme: { directory: "https://example/", accountKey: "auto" },
269
+ certs: [{ name: "m", domains: ["a.com"], challenge: goodChallenge }],
270
+ compliance: ["hipaa", "pci-dss"],
271
+ audit: false,
272
+ });
273
+ });
274
+ check("known compliance postures accepted", !eGoodPosture);
275
+
276
+ // ---- b.network.tls.ocsp.fetch composition surface ----
277
+ check("b.network.tls.ocsp.fetch is a function",
278
+ typeof b.network.tls.ocsp.fetch === "function");
279
+ }
280
+
281
+ // b.network.tls.ocsp.fetch rejects on missing leafPem/issuerPem rather than
282
+ // issuing an outbound request with undefined inputs.
283
+ async function testOcspFetchRejectsBadInput() {
284
+ var rejected = false;
285
+ try {
286
+ await b.network.tls.ocsp.fetch({});
287
+ } catch (_e) {
288
+ rejected = true;
289
+ }
290
+ check("ocsp.fetch({}) rejects (no leafPem/issuerPem)", rejected);
248
291
  }
249
292
 
250
293
  // ---- Sealed-disk storage roundtrip ----
@@ -699,6 +742,7 @@ async function testCorruptAccountKeyClearError() {
699
742
  async function run() {
700
743
  testSurface();
701
744
  testFactoryRefusesBadOpts();
745
+ await testOcspFetchRejectsBadInput();
702
746
  await testStorageRoundtrip();
703
747
  await testSniCallback();
704
748
  await testSniWildcardSingleLabel();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.2.31",
3
+ "version": "0.3.1",
4
4
  "description": "Open-source framework built on blamejs. Vendored stack, zero npm runtime deps, PQC-first crypto, security-on by default.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {