@blamejs/blamejs-shop 0.5.7 → 0.5.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.
Files changed (27) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/lib/asset-manifest.json +1 -1
  3. package/lib/cost-layers.js +86 -49
  4. package/lib/plan-changes.js +14 -0
  5. package/lib/vendor/MANIFEST.json +31 -15
  6. package/lib/vendor/blamejs/.github/workflows/release-container.yml +2 -2
  7. package/lib/vendor/blamejs/CHANGELOG.md +2 -0
  8. package/lib/vendor/blamejs/api-snapshot.json +2 -2
  9. package/lib/vendor/blamejs/examples/wiki/package-lock.json +1 -1
  10. package/lib/vendor/blamejs/lib/content-credentials.js +43 -2
  11. package/lib/vendor/blamejs/lib/guard-sql.js +29 -0
  12. package/lib/vendor/blamejs/lib/mcp.js +37 -4
  13. package/lib/vendor/blamejs/lib/metrics.js +15 -3
  14. package/lib/vendor/blamejs/lib/middleware/tus-upload.js +9 -2
  15. package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +33 -2
  16. package/lib/vendor/blamejs/lib/safe-buffer.js +11 -1
  17. package/lib/vendor/blamejs/package-lock.json +2 -2
  18. package/lib/vendor/blamejs/package.json +1 -1
  19. package/lib/vendor/blamejs/release-notes/v0.16.3.json +71 -0
  20. package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials-coverage.test.js +319 -0
  21. package/lib/vendor/blamejs/test/layer-0-primitives/cycle2-bugfixes.test.js +142 -0
  22. package/lib/vendor/blamejs/test/layer-0-primitives/guard-sql-coverage.test.js +432 -0
  23. package/lib/vendor/blamejs/test/layer-0-primitives/mcp-coverage.test.js +443 -0
  24. package/lib/vendor/blamejs/test/layer-0-primitives/metrics-coverage.test.js +395 -0
  25. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-tus-upload-coverage.test.js +402 -0
  26. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-safe-yaml-coverage.test.js +292 -0
  27. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.5.x
10
10
 
11
+ - v0.5.8 (2026-07-03) — **Inventory cost-layer consumption and subscription plan-change execution are now correct under concurrent requests; vendored framework refreshed to 0.16.3.** Two ledger-atomicity fixes for concurrent requests, plus a vendored framework security refresh. Cost-layer (COGS) consumption debited each inventory layer with an unguarded read-modify-write against a snapshot, so two overlapping sales or write-offs of the same SKU could both draw from the same layer — driving a layer's remaining quantity negative, stranding a later layer, and understating cost of goods sold. Each layer debit is now an atomic guarded claim (the decrement only applies while the layer still holds the planned quantity); a losing claim reverts its own partial work and re-plans against a fresh snapshot, and the cost attribution rows are written only after every layer in the plan is claimed, so the combined consumption is exact regardless of interleaving. Separately, an immediate subscription plan change wrote its history row as executed before claiming the plan transition, so a double-submitted or retried change that lost the transition claim left a phantom executed row — carrying charge and credit amounts even though no plan moved, no invoice was recorded, and no credit was issued. The losing change now voids that row, so subscription history and any revenue rollup over executed changes reflect only changes that actually happened. The vendored framework is refreshed to 0.16.3, which redacts credential-shaped values in the rendered /metrics exposition (they were masked only in the internal cardinality key), closes a SQL-guard fragment-mode bypass for PostgreSQL dollar-quoted strings, and fails closed on MCP tool input whose JSON Schema omits an explicit object type — all inherited without shop code changes. **Changed:** *Vendored framework refreshed to 0.16.3* — Refreshes the framework the shop builds on from 0.16.2 to 0.16.3, a security and robustness patch across the observability, SQL-guard, MCP, YAML-parser, content-credentials, and resumable-upload primitives. Most consequential: a credential-shaped metric label value was redacted only in the internal cardinality key, not in the rendered exposition, so a raw token on a label leaked to every /metrics scrape — redaction now covers the rendered labels; and the SQL guard's raw-fragment escape hatch now rejects a PostgreSQL dollar-quoted string and a lone statement terminator, closing a bypass of the placeholder-binding floor. The minimum Node.js engine is unchanged at 24.18.0. **Fixed:** *Concurrent cost-layer consumption no longer double-debits a layer* — FIFO, LIFO, and weighted-average COGS consumption planned each layer debit from a snapshot and then applied an unconditional decrement, so two concurrent consumptions of the same SKU could both debit the same front layer — driving its remaining quantity negative and understating COGS while a later layer was left stranded. Each debit is now an atomic guarded claim that only applies while the layer still holds the planned quantity; a losing claim reverts the attempt's earlier claims and re-plans against a re-read of the active layers, within a bounded retry. Cost attribution rows are persisted only after every layer in the plan is claimed, so a mid-plan contention loss leaves no orphaned rows and the summed consumption and attributed cost are exact. · *A lost immediate plan-change claim voids its row instead of leaving a phantom executed change* — An immediate subscription plan change inserted its history row as executed and then claimed the plan transition with a conditional update. A double-submit or retry could have two changes both insert an executed row while only one wins the transition; the loser previously returned leaving a phantom executed change with a charge and credit amount even though no plan moved, no proration invoice was recorded, and no store credit was issued. The losing change now voids its row (cancelled, reason recorded), matching how the same path already handles a failed processor push, so history and any rollup over executed changes count only changes that actually settled.
12
+
11
13
  - v0.5.7 (2026-07-03) — **Storefront redirect targets route through the framework open-redirect guard, closing a TAB-injection open redirect on return_to / redirect_to.** Closes an open-redirect (CWE-601) in the storefront's post-action redirects. The cookie-consent submit, currency and locale switchers, and the wishlist and compare toggles validated their return_to / redirect_to target with a hand-rolled leading-slash regex that accepted a horizontal TAB (0x09) at the second byte. A user agent strips ASCII TAB, CR, and LF from a URL before resolving it, so a target like /<TAB>/evil.example collapsed to the protocol-relative //evil.example and navigated the shopper off-origin under the shop's trust — a phishing bounce. Every request-derived redirect target now routes through the framework's b.safeRedirect.resolve primitive (exposed once as a shared same-origin helper), which rejects every control character including TAB, protocol-relative //host and \\host forms, and full URLs — only genuine same-origin relative paths survive; anything else falls back to a safe default. The two guards that already rejected control characters are consolidated onto the same primitive so there is one redirect-safety path to reason about, and a codebase check refuses any future hand-rolled leading-slash redirect guard. No operator action and no configuration change. **Security:** *Open-redirect on storefront return_to / redirect_to closed* — The post-action redirect guards (cookie consent, currency switch, locale switch, wishlist toggle, compare toggle, announcement dismiss) accepted a leading-slash target whose second byte was a control character. A horizontal TAB there is stripped by the browser before URL parsing, turning /<TAB>/evil.example into the protocol-relative //evil.example — an off-origin redirect an attacker could use to launder a phishing link through the shop's origin. All these targets now resolve through b.safeRedirect.resolve, which rejects control characters (TAB, CR, LF, NUL), protocol-relative and backslash forms, and off-origin absolute URLs; an invalid target falls back to a same-origin default. A codebase-patterns check prevents a hand-rolled leading-slash redirect guard from reappearing.
12
14
 
13
15
  - v0.5.6 (2026-07-03) — **Minimum Node is now 24.18.0; the vendored framework refresh brings a broad batch of request-path and mail-authentication security hardening.** Refreshes the vendored framework to 0.16.2 and raises the minimum supported Node.js to 24.18.0 — operators must run Node 24.18.0 or newer before upgrading. The floor moves with the framework, which requires 24.18.0 for its bundled SQLite 3.53.1, a backup-path fix, and the native OpenSSL 3.5 SLH-DSA surface. The refresh carries a large batch of security and correctness fixes that harden the request lifecycle the storefront and account portal already run on, at no code cost to the shop: the router refuses an encoded path separator or null byte and decodes the request path once, so a path-scoped security gate and the resource it guards can no longer disagree; res.redirect rejects a horizontal TAB that user agents strip before URL parsing, closing an open-redirect bypass; OAuth refresh-token and step-up replay gates treat any truthy store result as seen and bind grants to the authenticated principal; SAML Single-Logout fails closed when a verification key arrives without its algorithm; the mail-authentication builders (Authentication-Results, delivery-status, ARC) refuse CR/LF/NUL header injection and SPF evaluation fails closed on a malformed ip4 CIDR; file uploads enforce per-upload ownership and reject path-traversal ids; recursive parsers refuse pathologically deep input instead of overflowing the stack; and shared-cache counters (lockout, byte quotas, rate caps) accumulate with an atomic compare-and-set so distributed requests cannot lose increments. Every Node-version pin — engines, .nvmrc, the container base image, CI, and the docs — is synced to the new floor from the framework's own engines constraint. **Changed:** *Minimum Node.js raised to 24.18.0* — engines.node is now >=24.18.0 (was >=24.16.0). Operators must run Node 24.18.0 or newer. The floor is dictated by the vendored framework, which requires 24.18.0 for its bundled SQLite 3.53.1, a database backup-path fix, and the native OpenSSL 3.5 SLH-DSA sign/verify surface. The .nvmrc, the container base image, the CI runner pins, and the README/architecture docs are all synced to the new floor. · *Vendored framework refreshed to 0.16.2* — The framework the shop builds on is refreshed from 0.15.38 to 0.16.2. Beyond the request-path and mail hardening in this release's summary, the refresh includes: the router rejects percent-encoded path separators so a security middleware and the route it guards agree on the path; ARC and DKIM verification reject forged or body-length-truncated signatures; OCSP validation binds the response to the certificate's issuer; status-list verification binds the token type against replay; and OAuth authorization-URL builders refuse operator parameters that collide with framework-managed ones. These land in the request lifecycle the storefront and account portal already compose, with no shop code change required.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.5.7",
2
+ "version": "0.5.8",
3
3
  "assets": {
4
4
  "css/admin.css": {
5
5
  "integrity": "sha384-imfe0otYErcB8rr2h6KLSGTtStirysptpXETSPY4zLv3bZoIT75Lo1dOvkOav+xL",
@@ -388,6 +388,21 @@ function create(opts) {
388
388
 
389
389
  var method = await _methodFor(sku);
390
390
  var direction = method === "lifo" ? "lifo" : "fifo";
391
+ // Consuming is a check-then-write against a snapshot of the active
392
+ // layers, so it runs under a bounded claim-retry loop. Each planned
393
+ // layer debit is an ATOMIC guarded UPDATE (`... AND quantity_remaining
394
+ // >= take`); the cost attribution rows are written only after EVERY
395
+ // layer in the plan has been claimed. If a concurrent consumer moved a
396
+ // layer since the snapshot, the losing attempt reverts its own winning
397
+ // claims — a compensating add restores exactly what it subtracted — and
398
+ // re-reads. Without the guard, two concurrent consumes of the same SKU
399
+ // both debit the same front layer(s) from a stale snapshot, driving
400
+ // quantity_remaining negative and misstating COGS.
401
+ var MAX_CLAIM_ATTEMPTS = 8;
402
+ var consumed, totalCogs, currency;
403
+ var attributionTs = requested;
404
+
405
+ for (var attempt = 1; ; attempt += 1) {
391
406
  var layers = await _activeLayers(sku, direction);
392
407
 
393
408
  // Pre-flight: confirm on-hand >= requested. No partial
@@ -403,7 +418,7 @@ function create(opts) {
403
418
  // same currency. Mixed currencies are refused at the primitive
404
419
  // layer (the operator reconciles via a manual receipt rather
405
420
  // than getting a mysteriously-FX-blended COGS row).
406
- var currency = layers[0].currency;
421
+ currency = layers[0].currency;
407
422
  for (var c = 1; c < layers.length; c += 1) {
408
423
  if (layers[c].currency !== currency) {
409
424
  throw new TypeError("cost-layers.consumeForSale: layers for sku " +
@@ -412,14 +427,11 @@ function create(opts) {
412
427
  }
413
428
  }
414
429
 
415
- // The sale's `occurred_at` is the operator-supplied timestamp
416
- // (defaulting to now when omitted). `cogsForPeriod` filters
417
- // attribution rows by this column using the sale time, not
418
- // the wall-clock at write time, keeps backfilled imports
419
- // accurate.
420
- var consumed = [];
421
- var totalCogs = 0;
422
- var attributionTs = requested;
430
+ // Build the debit PLAN for this attempt without writing anything.
431
+ // Each entry: { layer_id, take, attrs: [{ qty, unit_cost_minor }] }.
432
+ // `planCogs` is the line's total COGS in minor units.
433
+ var plan = [];
434
+ var planCogs = 0;
423
435
 
424
436
  if (method === "weighted_average") {
425
437
  // Compute weighted-average unit cost in minor units. Round
@@ -493,11 +505,6 @@ function create(opts) {
493
505
  if (attrCogs > residual) attrCogs = residual;
494
506
  residual -= attrCogs;
495
507
  }
496
- // Debit the layer by the full consumed quantity (one update).
497
- await query(
498
- "UPDATE cost_layers SET quantity_remaining = quantity_remaining - ?1 WHERE id = ?2",
499
- [dRow.qty, dRow.layer.id],
500
- );
501
508
  // Attribute the debit's cost so `qty * unit_cost_minor` summed over
502
509
  // its attribution rows reconstructs attrCogs EXACTLY — the COGS
503
510
  // reports (cogsForOrder / cogsForPeriod) recover each line's cost
@@ -513,60 +520,90 @@ function create(opts) {
513
520
  var floorUnit = dRow.qty > 0 ? Math.floor(attrCogs / dRow.qty) : avgUnitCost;
514
521
  var rem = dRow.qty > 0 ? attrCogs - (floorUnit * dRow.qty) : 0;
515
522
  var parts = [];
516
- if (dRow.qty - rem > 0) parts.push({ qty: dRow.qty - rem, unit: floorUnit });
517
- if (rem > 0) parts.push({ qty: rem, unit: floorUnit + 1 });
518
- for (var p = 0; p < parts.length; p += 1) {
519
- var attrId = b.uuid.v7();
520
- await query(
521
- "INSERT INTO cogs_attributions " +
522
- "(id, order_id, line_id, sku, layer_id, qty, unit_cost_minor, " +
523
- "currency, reversed, reversal_reason, occurred_at) " +
524
- "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, 0, NULL, ?9)",
525
- [attrId, orderId, lineId, sku, dRow.layer.id, parts[p].qty, parts[p].unit, currency, attributionTs],
526
- );
527
- consumed.push({
528
- layer_id: dRow.layer.id,
529
- qty: parts[p].qty,
530
- unit_cost_minor: parts[p].unit,
531
- });
532
- }
533
- totalCogs += attrCogs;
523
+ if (dRow.qty - rem > 0) parts.push({ qty: dRow.qty - rem, unit_cost_minor: floorUnit });
524
+ if (rem > 0) parts.push({ qty: rem, unit_cost_minor: floorUnit + 1 });
525
+ plan.push({ layer_id: dRow.layer.id, take: dRow.qty, attrs: parts });
534
526
  }
535
- // Edge case: residual unspent (impossible given the math
536
- // above, but defensive). Push it onto the last attribution.
537
- if (residual !== 0 && consumed.length) {
538
- totalCogs += residual;
539
- }
540
- totalCogs = lineCogs;
527
+ planCogs = lineCogs;
541
528
  } else {
542
- // FIFO / LIFO: walk layers in order, debit each one until
543
- // the requested quantity is satisfied.
529
+ // FIFO / LIFO: walk layers in order, planning a debit against each
530
+ // one until the requested quantity is satisfied.
544
531
  var remaining = quantity;
545
532
  for (var f = 0; f < layers.length && remaining > 0; f += 1) {
546
533
  var lay = layers[f];
547
534
  var take = lay.quantity_remaining;
548
535
  if (take > remaining) take = remaining;
536
+ plan.push({
537
+ layer_id: lay.id,
538
+ take: take,
539
+ attrs: [{ qty: take, unit_cost_minor: lay.unit_cost_minor }],
540
+ });
541
+ planCogs += take * lay.unit_cost_minor;
542
+ remaining -= take;
543
+ }
544
+ }
545
+
546
+ // Claim every planned layer atomically. A guarded decrement loses (0
547
+ // rows) when a concurrent consumer has drawn the layer below `take`
548
+ // since this attempt's snapshot was read.
549
+ var claimed = [];
550
+ var lostClaim = false;
551
+ for (var g = 0; g < plan.length; g += 1) {
552
+ var claimRes = await query(
553
+ "UPDATE cost_layers SET quantity_remaining = quantity_remaining - ?1 " +
554
+ "WHERE id = ?2 AND quantity_remaining >= ?1",
555
+ [plan[g].take, plan[g].layer_id],
556
+ );
557
+ if (b.sql.casWon(claimRes).won) {
558
+ claimed.push(plan[g]);
559
+ } else {
560
+ lostClaim = true;
561
+ break;
562
+ }
563
+ }
564
+
565
+ if (lostClaim) {
566
+ // Revert this attempt's winning claims — a compensating add restores
567
+ // exactly what was subtracted — then re-read the layers and re-plan.
568
+ for (var rv = 0; rv < claimed.length; rv += 1) {
549
569
  await query(
550
- "UPDATE cost_layers SET quantity_remaining = quantity_remaining - ?1 WHERE id = ?2",
551
- [take, lay.id],
570
+ "UPDATE cost_layers SET quantity_remaining = quantity_remaining + ?1 WHERE id = ?2",
571
+ [claimed[rv].take, claimed[rv].layer_id],
552
572
  );
553
- var attrIdF = b.uuid.v7();
573
+ }
574
+ if (attempt >= MAX_CLAIM_ATTEMPTS) {
575
+ throw new Error("cost-layers.consumeForSale: layer contention on sku " +
576
+ JSON.stringify(sku) + " — claim retries exhausted after " + attempt + " attempts");
577
+ }
578
+ continue;
579
+ }
580
+
581
+ // Every planned layer was claimed — persist the attribution rows. The
582
+ // row set (layer / qty / unit_cost_minor) is identical to the pre-fix
583
+ // interleaved write; only the ordering (claim-all, then insert-all)
584
+ // changed so a lost mid-plan claim reverts cleanly with no orphan rows.
585
+ consumed = [];
586
+ totalCogs = planCogs;
587
+ for (var pj = 0; pj < plan.length; pj += 1) {
588
+ for (var pa = 0; pa < plan[pj].attrs.length; pa += 1) {
589
+ var attrRow = plan[pj].attrs[pa];
590
+ var attrRowId = b.uuid.v7();
554
591
  await query(
555
592
  "INSERT INTO cogs_attributions " +
556
593
  "(id, order_id, line_id, sku, layer_id, qty, unit_cost_minor, " +
557
594
  "currency, reversed, reversal_reason, occurred_at) " +
558
595
  "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, 0, NULL, ?9)",
559
- [attrIdF, orderId, lineId, sku, lay.id, take, lay.unit_cost_minor, currency, attributionTs],
596
+ [attrRowId, orderId, lineId, sku, plan[pj].layer_id, attrRow.qty, attrRow.unit_cost_minor, currency, attributionTs],
560
597
  );
561
598
  consumed.push({
562
- layer_id: lay.id,
563
- qty: take,
564
- unit_cost_minor: lay.unit_cost_minor,
599
+ layer_id: plan[pj].layer_id,
600
+ qty: attrRow.qty,
601
+ unit_cost_minor: attrRow.unit_cost_minor,
565
602
  });
566
- totalCogs += take * lay.unit_cost_minor;
567
- remaining -= take;
568
603
  }
569
604
  }
605
+ break;
606
+ } // end bounded claim-retry loop
570
607
 
571
608
  return {
572
609
  consumed_layers: consumed,
@@ -635,6 +635,20 @@ function create(opts) {
635
635
  periodEnd: sub.current_period_end,
636
636
  planChangeId: id,
637
637
  });
638
+ } else {
639
+ // Lost the transition claim — a concurrent executeChange (double-submit
640
+ // or retry) already flipped this subscription's plan. This row was
641
+ // inserted 'executed' before the claim, but no plan moved, no credit
642
+ // was issued, and no invoice was recorded under it. Void it rather than
643
+ // leave a phantom 'executed' history row that carries money columns yet
644
+ // reflects nothing — a naive revenue rollup
645
+ // (SUM(first_charge_minor) WHERE status='executed') would double-count
646
+ // it. Mirrors the stripe-push-failed / credit-settlement-failed voids.
647
+ await query(
648
+ "UPDATE subscription_plan_changes SET status = 'cancelled', cancelled_at = ?1, " +
649
+ "cancel_reason = ?2 WHERE id = ?3",
650
+ [_now(), "transition-lost", id],
651
+ );
638
652
  }
639
653
 
640
654
  return await _getById(id);
@@ -3,8 +3,8 @@
3
3
  "_about": "blamejs.shop vendors a single framework — blamejs — which itself bundles every server-side crypto/identity dependency. The transitive packages blamejs ships are surfaced in its own MANIFEST.json at lib/vendor/blamejs/lib/vendor/MANIFEST.json — Trivy / Grype rely on that nested data for CVE attribution.",
4
4
  "packages": {
5
5
  "blamejs": {
6
- "version": "0.16.2",
7
- "tag": "v0.16.2",
6
+ "version": "0.16.3",
7
+ "tag": "v0.16.3",
8
8
  "license": "Apache-2.0",
9
9
  "author": "blamejs contributors",
10
10
  "source": "https://github.com/blamejs/blamejs",
@@ -838,6 +838,7 @@
838
838
  "release-notes/v0.16.0.json": "lib/vendor/blamejs/release-notes/v0.16.0.json",
839
839
  "release-notes/v0.16.1.json": "lib/vendor/blamejs/release-notes/v0.16.1.json",
840
840
  "release-notes/v0.16.2.json": "lib/vendor/blamejs/release-notes/v0.16.2.json",
841
+ "release-notes/v0.16.3.json": "lib/vendor/blamejs/release-notes/v0.16.3.json",
841
842
  "release-notes/v0.2.x.json": "lib/vendor/blamejs/release-notes/v0.2.x.json",
842
843
  "release-notes/v0.3.x.json": "lib/vendor/blamejs/release-notes/v0.3.x.json",
843
844
  "release-notes/v0.4.x.json": "lib/vendor/blamejs/release-notes/v0.4.x.json",
@@ -1100,6 +1101,7 @@
1100
1101
  "test/layer-0-primitives/config-drift.test.js": "lib/vendor/blamejs/test/layer-0-primitives/config-drift.test.js",
1101
1102
  "test/layer-0-primitives/config.test.js": "lib/vendor/blamejs/test/layer-0-primitives/config.test.js",
1102
1103
  "test/layer-0-primitives/consent-purposes.test.js": "lib/vendor/blamejs/test/layer-0-primitives/consent-purposes.test.js",
1104
+ "test/layer-0-primitives/content-credentials-coverage.test.js": "lib/vendor/blamejs/test/layer-0-primitives/content-credentials-coverage.test.js",
1103
1105
  "test/layer-0-primitives/content-credentials.test.js": "lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js",
1104
1106
  "test/layer-0-primitives/content-digest.test.js": "lib/vendor/blamejs/test/layer-0-primitives/content-digest.test.js",
1105
1107
  "test/layer-0-primitives/cors.test.js": "lib/vendor/blamejs/test/layer-0-primitives/cors.test.js",
@@ -1132,6 +1134,7 @@
1132
1134
  "test/layer-0-primitives/csrf-protect.test.js": "lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js",
1133
1135
  "test/layer-0-primitives/csv.test.js": "lib/vendor/blamejs/test/layer-0-primitives/csv.test.js",
1134
1136
  "test/layer-0-primitives/cwt.test.js": "lib/vendor/blamejs/test/layer-0-primitives/cwt.test.js",
1137
+ "test/layer-0-primitives/cycle2-bugfixes.test.js": "lib/vendor/blamejs/test/layer-0-primitives/cycle2-bugfixes.test.js",
1135
1138
  "test/layer-0-primitives/daemon.test.js": "lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js",
1136
1139
  "test/layer-0-primitives/daily-byte-quota.test.js": "lib/vendor/blamejs/test/layer-0-primitives/daily-byte-quota.test.js",
1137
1140
  "test/layer-0-primitives/dane.test.js": "lib/vendor/blamejs/test/layer-0-primitives/dane.test.js",
@@ -1235,6 +1238,7 @@
1235
1238
  "test/layer-0-primitives/guard-saga-config.test.js": "lib/vendor/blamejs/test/layer-0-primitives/guard-saga-config.test.js",
1236
1239
  "test/layer-0-primitives/guard-smtp-command.test.js": "lib/vendor/blamejs/test/layer-0-primitives/guard-smtp-command.test.js",
1237
1240
  "test/layer-0-primitives/guard-snapshot-envelope.test.js": "lib/vendor/blamejs/test/layer-0-primitives/guard-snapshot-envelope.test.js",
1241
+ "test/layer-0-primitives/guard-sql-coverage.test.js": "lib/vendor/blamejs/test/layer-0-primitives/guard-sql-coverage.test.js",
1238
1242
  "test/layer-0-primitives/guard-stream-args.test.js": "lib/vendor/blamejs/test/layer-0-primitives/guard-stream-args.test.js",
1239
1243
  "test/layer-0-primitives/guard-svg.test.js": "lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js",
1240
1244
  "test/layer-0-primitives/guard-tenant-id.test.js": "lib/vendor/blamejs/test/layer-0-primitives/guard-tenant-id.test.js",
@@ -1324,12 +1328,15 @@
1324
1328
  "test/layer-0-primitives/mail-store.test.js": "lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js",
1325
1329
  "test/layer-0-primitives/mail-unsubscribe.test.js": "lib/vendor/blamejs/test/layer-0-primitives/mail-unsubscribe.test.js",
1326
1330
  "test/layer-0-primitives/mail.test.js": "lib/vendor/blamejs/test/layer-0-primitives/mail.test.js",
1331
+ "test/layer-0-primitives/mcp-coverage.test.js": "lib/vendor/blamejs/test/layer-0-primitives/mcp-coverage.test.js",
1327
1332
  "test/layer-0-primitives/mcp-tool-registry.test.js": "lib/vendor/blamejs/test/layer-0-primitives/mcp-tool-registry.test.js",
1328
1333
  "test/layer-0-primitives/mcp.test.js": "lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js",
1329
1334
  "test/layer-0-primitives/mdoc.test.js": "lib/vendor/blamejs/test/layer-0-primitives/mdoc.test.js",
1335
+ "test/layer-0-primitives/metrics-coverage.test.js": "lib/vendor/blamejs/test/layer-0-primitives/metrics-coverage.test.js",
1330
1336
  "test/layer-0-primitives/metrics-shadow-registry.test.js": "lib/vendor/blamejs/test/layer-0-primitives/metrics-shadow-registry.test.js",
1331
1337
  "test/layer-0-primitives/metrics-snapshot.test.js": "lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js",
1332
1338
  "test/layer-0-primitives/middleware-compose-pipeline.test.js": "lib/vendor/blamejs/test/layer-0-primitives/middleware-compose-pipeline.test.js",
1339
+ "test/layer-0-primitives/middleware-tus-upload-coverage.test.js": "lib/vendor/blamejs/test/layer-0-primitives/middleware-tus-upload-coverage.test.js",
1333
1340
  "test/layer-0-primitives/mime-parse.test.js": "lib/vendor/blamejs/test/layer-0-primitives/mime-parse.test.js",
1334
1341
  "test/layer-0-primitives/money.test.js": "lib/vendor/blamejs/test/layer-0-primitives/money.test.js",
1335
1342
  "test/layer-0-primitives/mtls-ca-paths.test.js": "lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-paths.test.js",
@@ -1367,6 +1374,7 @@
1367
1374
  "test/layer-0-primitives/output-header-hardening.test.js": "lib/vendor/blamejs/test/layer-0-primitives/output-header-hardening.test.js",
1368
1375
  "test/layer-0-primitives/pagination.test.js": "lib/vendor/blamejs/test/layer-0-primitives/pagination.test.js",
1369
1376
  "test/layer-0-primitives/parser-verify-hardening.test.js": "lib/vendor/blamejs/test/layer-0-primitives/parser-verify-hardening.test.js",
1377
+ "test/layer-0-primitives/parsers-safe-yaml-coverage.test.js": "lib/vendor/blamejs/test/layer-0-primitives/parsers-safe-yaml-coverage.test.js",
1370
1378
  "test/layer-0-primitives/parsers-standalone.test.js": "lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js",
1371
1379
  "test/layer-0-primitives/passkey-real-vectors.test.js": "lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js",
1372
1380
  "test/layer-0-primitives/passkey.test.js": "lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js",
@@ -1552,7 +1560,7 @@
1552
1560
  ".github/workflows/ci.yml": "sha256:c9cd093403b410d14f6cf61bfc864b8862cc0d8f5c8dbf01a979881a4204fe83",
1553
1561
  ".github/workflows/codeql.yml": "sha256:9622f111e4da9028dc94507fe5a7c9ee4cd19dfcfbffeed1edbefde284133fea",
1554
1562
  ".github/workflows/npm-publish.yml": "sha256:cac26b6d72101e882a59d8d25fb6977973e1f8d67c87b491297712fca479b93d",
1555
- ".github/workflows/release-container.yml": "sha256:17ccd9081d26e69d258efeb37ba135e68949b7d4e1f4444697071ac57ccee75e",
1563
+ ".github/workflows/release-container.yml": "sha256:89c6722c30f1a69f2367086f46eeb213fe384f0aa44a44e81f64866f4c19a6e2",
1556
1564
  ".github/workflows/scorecard.yml": "sha256:8a3a8a25eafe6e7c965983fe550a3e7a945386a5e9e88341c801b10b504d94be",
1557
1565
  ".github/workflows/sha-to-tag-verify.yml": "sha256:6ed546bdb518514c79a69aa9afe87c92903a8663afb57d4d07b5e81fbda8b844",
1558
1566
  ".github/zizmor.yml": "sha256:fe08e66c5763dfb4edf1ea33e715f04e514b1baceb3a242241d8a60793db51d2",
@@ -1562,7 +1570,7 @@
1562
1570
  ".npmrc": "sha256:66f104e7d07c496d2d0409988225e8c0e4ceb8d247dbcac3be75b2128d20ce66",
1563
1571
  ".pinact.yaml": "sha256:0213ffda55961dc49b64c0a5dfa3c0567419633b1499d57eaf7c8d842d7da6c7",
1564
1572
  "ARCHITECTURE.md": "sha256:9b1c8d2b1b7a41838eb348b0a008e4b4369718fd72bfe2974b37155f7536d35b",
1565
- "CHANGELOG.md": "sha256:62fb356e0451a9500a2e74e1cfc49bdfacf99c579c0ead43b9282f22603c736c",
1573
+ "CHANGELOG.md": "sha256:4fa997c021b50ee725c66753a8ee6ea86b26fa4233101dd9070d5702b1adaed9",
1566
1574
  "CODE_OF_CONDUCT.md": "sha256:148a281960fff7c2fe6554dab66da572c72245ddeb00b0d14811558397bff386",
1567
1575
  "CONTRIBUTING.md": "sha256:3e4575c3cd57e72317773a80faabcabb611133ace5ee523db4b8d631e80bdaa8",
1568
1576
  "GOVERNANCE.md": "sha256:906df6afb1f552b27b9acb50f7f96c47b917a2f1021cd4e987dbf4ee0e0a821b",
@@ -1573,7 +1581,7 @@
1573
1581
  "README.md": "sha256:e1c6d401d81c959a0f264d73b3640a26465921f0ceb1103be6936816d99d21b4",
1574
1582
  "ROADMAP.md": "sha256:d66be1de21603794608395c57ecc17381b673452ea74159bd805c85684821ecd",
1575
1583
  "SECURITY.md": "sha256:e47271ef5094205c0ce38c10c8674c42a59b7210311f0e27ecc028c7a387c2cc",
1576
- "api-snapshot.json": "sha256:626f0081f7985e9d85b9135eec545a12aeb950c9e7983e706b9dc24488057a55",
1584
+ "api-snapshot.json": "sha256:765b6929a2fde973e67fb49fd49942d4053eabd6e858b0b5f3942cf06e3400ec",
1577
1585
  "assets/BlameJS_Logo.png": "sha256:3c65699753c771b48ef9ac7f45bb40815ec19a23afcdd0cd30ef4601bbbe293e",
1578
1586
  "assets/BlameJS_Logo.svg": "sha256:dda44f3fb1343d5de9db6b1fcdb75fc649c57e7a99a8e8239fcf852e3841e1a8",
1579
1587
  "bench/README.md": "sha256:74202f2507fd840bfc1ac6c681975d9273cf36cca6e0f72655f138337304033c",
@@ -1632,7 +1640,7 @@
1632
1640
  "examples/wiki/lib/source-doc-parser.js": "sha256:e39a1701dbe6940987b11e9318e535039e8a5d1dbc67ba7b7e6bac4ef87ef638",
1633
1641
  "examples/wiki/lib/symbol-index.js": "sha256:abd133b4aa8e582cf72b7c92fafe7f342b2d06d1a9f3d1082eb0c4df8a743b0e",
1634
1642
  "examples/wiki/migrations/0001-pages-schema.js": "sha256:b4459ee6d1d4f1130dbf0c7a22b39ffa0f972a7ca3a0ef19215abe4bf35e46a5",
1635
- "examples/wiki/package-lock.json": "sha256:66cf7211967af7f63784a84db80287b63a7ccd4db69fdc400af16986753f459e",
1643
+ "examples/wiki/package-lock.json": "sha256:ceb2632eea43f0b167da990fee70ae16dbd24577673c0f5e0f9c3c0781ce1170",
1636
1644
  "examples/wiki/package.json": "sha256:8efb9ee3012107f9631ce23e7846ff2b10cadf643ec449e7940de496728aaedb",
1637
1645
  "examples/wiki/public/img/blamejs-logo.png": "sha256:3c65699753c771b48ef9ac7f45bb40815ec19a23afcdd0cd30ef4601bbbe293e",
1638
1646
  "examples/wiki/public/img/blamejs-logo.svg": "sha256:dda44f3fb1343d5de9db6b1fcdb75fc649c57e7a99a8e8239fcf852e3841e1a8",
@@ -1904,7 +1912,7 @@
1904
1912
  "lib/config.js": "sha256:7f28de45331b39de8bb3b9281ef0641c4b3fdf928088a22fdb51ecac2a1c21d7",
1905
1913
  "lib/consent.js": "sha256:0eb93f281d05ac35895cd1e0b8bf7c0cf121bf6c72901f844b52028c6bd3a14c",
1906
1914
  "lib/constants.js": "sha256:31897caa2604b53c5365e9e054373b29c753c226beefd3a47e515a9f441c105c",
1907
- "lib/content-credentials.js": "sha256:d548860bb5c658698509c7245df8e6c117a855bfdd2f43a1566c1eb3f2a333ce",
1915
+ "lib/content-credentials.js": "sha256:df200fe8945fcbf4783c81a52b552dc9fc5630fdc4674275a1521f88175bbeb8",
1908
1916
  "lib/content-digest.js": "sha256:ae9052c1d3f54a314c3d25b2e45a173459efc12a470b5b632b8c31db0e89c5ff",
1909
1917
  "lib/cookies.js": "sha256:5230ca525e8d618a0cb9df607235b5fd194b2138915e8089dc4ea3824aa92675",
1910
1918
  "lib/cose.js": "sha256:89c8672c0a5a019bc70e7889f4441a8ba6a6a3a52d288f0515199982046810cc",
@@ -2014,7 +2022,7 @@
2014
2022
  "lib/guard-shell.js": "sha256:bc866a05afba17004ce5ce7497d884d0a59fbec3aa8b81c76840eeddd2c3b9a3",
2015
2023
  "lib/guard-smtp-command.js": "sha256:a761c4f5d5d8f5083339e095dca2ede035330b760735bcf76693b9e6484ab4ae",
2016
2024
  "lib/guard-snapshot-envelope.js": "sha256:13eab8a92ef38c9d0a58adf30844a0a41030e9d0b67e447480a2d21c6db052ff",
2017
- "lib/guard-sql.js": "sha256:1f2a18e1f7e603377d898900eef0636d49f2a6c2f7a2d8fd746982e5b25c201f",
2025
+ "lib/guard-sql.js": "sha256:fef254470a15a7b9be78300c8fa86d5dd26ae88f654f5230f5f3051597f6f108",
2018
2026
  "lib/guard-stream-args.js": "sha256:3d20d6a57639c38fb406b40540d4bd738387ec2f1eca3e63c3a586a75527e61a",
2019
2027
  "lib/guard-svg.js": "sha256:c9fbca850273d43fddaf6232949b963fbe48cfa73068540fb30aee8ce8d6f017",
2020
2028
  "lib/guard-template.js": "sha256:861a566fd25a7abaf541c0e7875f8dc30ae5b34832ee81ee37f1ee26ae96b58b",
@@ -2107,9 +2115,9 @@
2107
2115
  "lib/markup-escape.js": "sha256:7cb287780b9bb8a5ab37b1f8c36427df432331def15e2ef4d63c4b29809357ee",
2108
2116
  "lib/markup-tokenizer.js": "sha256:b02048184fb1a55e410cdf15049935b3f1ab4895c595b0bd2a6589bcdab9f87e",
2109
2117
  "lib/mcp-tool-registry.js": "sha256:57722b488a0977704997acbb61d2fad13155040007516f996b6b574c64d482ee",
2110
- "lib/mcp.js": "sha256:d914512443b33bbe047f68e1ae5358ac5b9a1b25f6801f2bc360adbea1b857dc",
2118
+ "lib/mcp.js": "sha256:d0d40b1ee3cc4d66d249f6ad15ba5e1bdb0db66c689a940206c25a1d51a2c694",
2111
2119
  "lib/mdoc.js": "sha256:536d6ad327e15f11f3279781f93a779bffeddfec06222b0e601728f905561d21",
2112
- "lib/metrics.js": "sha256:f0f677ac7b2c82ab89059ed2d1bbe580a84bea0e93f6ae37990314a123a3e503",
2120
+ "lib/metrics.js": "sha256:f12e30587a8a2c7f74be480dbb7c8f5e11a8e2ddca644cb88a225367168b241e",
2113
2121
  "lib/middleware/age-gate.js": "sha256:ef9113093151494eca51bbcd60bd334aa2e8865969e2f3ad34cc6d8ce180fc25",
2114
2122
  "lib/middleware/ai-act-disclosure.js": "sha256:e6f5bb9aab37bcec5ecef2ec5b3688ed0eb069eb61279d297e1ddd8460bc4568",
2115
2123
  "lib/middleware/api-encrypt.js": "sha256:48c59f52ffc9b05e38c58e193e2df743b62c3f80ac61336bf2c4abade18437a7",
@@ -2164,7 +2172,7 @@
2164
2172
  "lib/middleware/sse.js": "sha256:5bf672e6a9f2988719b820ed8d67bda34b9b7a4d54d170620614ed24bc688bfc",
2165
2173
  "lib/middleware/trace-log-correlation.js": "sha256:abf2339adddc2a2b3f61b26e9c1cb6ebffae5fc02ca51bf9ee002df99dfe622f",
2166
2174
  "lib/middleware/trace-propagate.js": "sha256:ddfebd2947bca77be6023fd4833bd80f8d03bef7ca2d63c7fc27f447108cd451",
2167
- "lib/middleware/tus-upload.js": "sha256:bc9a7d002f36b3d121d606e4b2f1b683461f003f6d33f6e3f44c5c99d10effac",
2175
+ "lib/middleware/tus-upload.js": "sha256:0e85b893fdd1e081a3d32fd6a1e47ddf56730c7e855c670b1dfdb50ed7dd6e46",
2168
2176
  "lib/middleware/web-app-manifest.js": "sha256:d2e50696b4c49d4a089a79b7514fb89fe4c765b9e7edfd9bc7e0e3bf31ca1d23",
2169
2177
  "lib/migration-files.js": "sha256:6381dd46fdcb5796ac094ec9353ce6c4996d56f826f70bc4939349b934a7cef1",
2170
2178
  "lib/migrations.js": "sha256:229a067f388db89d74d7f1c6b2a5b4c48840966bf90d918fc786f489995e4b83",
@@ -2218,7 +2226,7 @@
2218
2226
  "lib/parsers/safe-ini.js": "sha256:58287b28aa6f0dbeb0f45526e671790a8647b3b3e4c78ae8961042e11ff22ae9",
2219
2227
  "lib/parsers/safe-toml.js": "sha256:148e874454ae1ed6f882f450b38e08e6b73b64b100b499b5eaa739c0fd8064be",
2220
2228
  "lib/parsers/safe-xml.js": "sha256:82a3bb0c0e08d7437c478d74a50b7ba949a2e090c8e4600f5f18c69de681a48a",
2221
- "lib/parsers/safe-yaml.js": "sha256:47702d747136220b2edaae310274b328c589d32cdf27c651f8f107742179e23e",
2229
+ "lib/parsers/safe-yaml.js": "sha256:bb91ee487c4599333d7d87ed2aa670bea939ed776108eed5f8e92093e2d36353",
2222
2230
  "lib/permissions.js": "sha256:c18f53a53c4e9b0c9a212bc39703000ea844a7beb407d19f82455bb1d4a48cdf",
2223
2231
  "lib/pick.js": "sha256:7e4f5aca12ed39311afbb71885332884cb7fbaeb02dd9a2d6ecf9cb53dba1b57",
2224
2232
  "lib/pipl-cn.js": "sha256:4855a8653ef6c91fc2dec0a1c997ac7ba4d38ec737add5f77a5733ee2d56b1b2",
@@ -2254,7 +2262,7 @@
2254
2262
  "lib/router.js": "sha256:1225dbe9a51d846bbfdaa29041b19675b4d0c01084b70e9ae795026c0b382651",
2255
2263
  "lib/safe-archive.js": "sha256:43a81f432e1cf9f294cbcf60636514cf2a5f3c67ec8e36aa8f8521c4cc657d39",
2256
2264
  "lib/safe-async.js": "sha256:4095c238c1fb0ca788946c757aea2371d832648865ef1810bd2f3a95dbe30bf4",
2257
- "lib/safe-buffer.js": "sha256:99f64bc544a46037cc43fe3faadd81e2757e039dade46469f3d274591c8434c3",
2265
+ "lib/safe-buffer.js": "sha256:881cf46b90f17f0e36d360a627aa4d10b9d9928472fd2254ae8ddff7bdf49789",
2258
2266
  "lib/safe-decompress.js": "sha256:f06a4d1e31453a94b6722cbb305111b10d0a683fe36c186a174954f45f4a521a",
2259
2267
  "lib/safe-dns.js": "sha256:e1c2c381bae7fa7e0ee2b84a7f09870b95760a007aa0ae0793679f78982cb902",
2260
2268
  "lib/safe-ical.js": "sha256:ad3cc9810d538aba86fed57b2e4bc79eaa8760bc9ef146dae898b1d496058ea9",
@@ -2348,8 +2356,8 @@
2348
2356
  "oss-fuzz/projects/blamejs/README.md": "sha256:ae13b7bb79ed8d69b1b3276e5562807a0349fb6e6b7d11cf1f683aad1eafdb4b",
2349
2357
  "oss-fuzz/projects/blamejs/build.sh": "sha256:0ced1cf21782c97be7f8d74faf5e27a308b60b2f858836fb5ca3b8c4e939a8f7",
2350
2358
  "oss-fuzz/projects/blamejs/project.yaml": "sha256:59f2cb83aa622325a175b77416fe155be15b70a9c798bd1a78bba05763b1b03d",
2351
- "package-lock.json": "sha256:a993222d15820ddfccda00518ea1dcc788873a851a8258799dd0995c5b34b1bf",
2352
- "package.json": "sha256:b82adb1c4fd61687844feeb477960e33787de640bde9680e962152ae2122f838",
2359
+ "package-lock.json": "sha256:e09ec4b072f8f1f38ddb0b59b855e0ca1bca293a9568daa4be3e765dd237047e",
2360
+ "package.json": "sha256:ed80932595218a5a1da1c3f405a1dfc989ab3910f27b39df5f15b601f95225e9",
2353
2361
  "release-notes/v0.0.x.json": "sha256:7a49819f30068ee119000cad7010194882bb8bfaa12acbdab4dfc066efb7982f",
2354
2362
  "release-notes/v0.1.x.json": "sha256:6742a8c17f947c5cb76f69dead7eea86b942d80621d914b774ba5488e09937e5",
2355
2363
  "release-notes/v0.10.x.json": "sha256:fe498045daf88337bd3d987e5964aa42c99a50e1685b6f09e51f698b8687726f",
@@ -2361,6 +2369,7 @@
2361
2369
  "release-notes/v0.16.0.json": "sha256:70def9578a6ae184dfb2f82c2f2b1bda15c4d9b8f987f0047da7f68f06a7e41b",
2362
2370
  "release-notes/v0.16.1.json": "sha256:67f5833cf682266b0ea93d410fdeedfa427e80781532d15e64c7d440dfefa95c",
2363
2371
  "release-notes/v0.16.2.json": "sha256:54d139821c154cffcd5b87b2cad1cff696f1db2af49742a144cd86c3194b3ece",
2372
+ "release-notes/v0.16.3.json": "sha256:0de1e0284e41575b8480d26f99b49d9dea550ef035f5c45e0b1723620d84f49d",
2364
2373
  "release-notes/v0.2.x.json": "sha256:985e27ff5de04cfc7869a3986dd0b9f0fcdfcddfb67d3fae3f4ae70856722d7e",
2365
2374
  "release-notes/v0.3.x.json": "sha256:e2db5eae66977b272bb185cad668386afb8fd33998a17c22eb6e411c0f8ca588",
2366
2375
  "release-notes/v0.4.x.json": "sha256:c3d19cb9c50a976432fc0bb612c87741d8728ae37562b501e6f1eccd01dd574d",
@@ -2623,6 +2632,7 @@
2623
2632
  "test/layer-0-primitives/config-drift.test.js": "sha256:75371225fef23f472e26444e672c7822668e0400407e05652bc73f3a368b97aa",
2624
2633
  "test/layer-0-primitives/config.test.js": "sha256:6adc4278b64f960ea2649779d7048c90ff54dbd849832c088700fbdca150ef94",
2625
2634
  "test/layer-0-primitives/consent-purposes.test.js": "sha256:a1669c7fb9c999ced399a4d93dccf5195008c422f28a669c25a9832f2114ce2d",
2635
+ "test/layer-0-primitives/content-credentials-coverage.test.js": "sha256:81a1942e6306e69159d3da8da97c55e70e3c66d19d8d6ad187e6f2a627e57ddc",
2626
2636
  "test/layer-0-primitives/content-credentials.test.js": "sha256:10014ff2e44cd5185d6c3401cb797db4813c4aa794cd01f07a221953cd4f029f",
2627
2637
  "test/layer-0-primitives/content-digest.test.js": "sha256:7b28731f3806576e04669a62919be1d79df3881b1b80b6eaf7cf101a2aa56c91",
2628
2638
  "test/layer-0-primitives/cors.test.js": "sha256:74b0feee302861b3a6385cf43dfb286b008a10c37794e7074f34236124d924f2",
@@ -2655,6 +2665,7 @@
2655
2665
  "test/layer-0-primitives/csrf-protect.test.js": "sha256:4b7d07492a1f1a218aa8106a387401cd3d6f3b43d3c6833ee4db5386bc2bc712",
2656
2666
  "test/layer-0-primitives/csv.test.js": "sha256:1367035130388f4cf75c9bc03cc88b4d4ac1aca3fb6c62d7a98c34d256f89e7a",
2657
2667
  "test/layer-0-primitives/cwt.test.js": "sha256:2e992ab7445db02581275ae5fd8b38de210e21f38a1594f27942f1bb1f9495e4",
2668
+ "test/layer-0-primitives/cycle2-bugfixes.test.js": "sha256:d23919a13e58a84d61a1a3d2eb95ec6f9d24ab94e3f2221e7002e07438d48341",
2658
2669
  "test/layer-0-primitives/daemon.test.js": "sha256:5e75c56ad03579293561ce8708a4289f2b8967a6b28bec31d5e3138bc49c17b3",
2659
2670
  "test/layer-0-primitives/daily-byte-quota.test.js": "sha256:f8ac102047451f6dd04050096674b739f012117b1845215bf4f1b47df25be72f",
2660
2671
  "test/layer-0-primitives/dane.test.js": "sha256:0fff14dfa609fa04a30f6a5a644288bcfafb3292044797cdc89674e5dc9485e2",
@@ -2758,6 +2769,7 @@
2758
2769
  "test/layer-0-primitives/guard-saga-config.test.js": "sha256:f35eb034a7b53cb7f3219998d546834cfc2b0c7b6af71a5f6d127e59152f8648",
2759
2770
  "test/layer-0-primitives/guard-smtp-command.test.js": "sha256:a31c1f379ff873253e165359ad251a6574b8aff532b6c177e0af1c52caca7702",
2760
2771
  "test/layer-0-primitives/guard-snapshot-envelope.test.js": "sha256:7fb6a0c76febd6576ca47c0712eb8f2db4305e7000fbee1450f98450a58caae9",
2772
+ "test/layer-0-primitives/guard-sql-coverage.test.js": "sha256:8ce355a7ff8b1923090b2f2409a0a0726a418221eb4b901920216f387784ecde",
2761
2773
  "test/layer-0-primitives/guard-stream-args.test.js": "sha256:7c7401b88eccfbd50964d41c826a1f75be3ac3bdb4caa1ac929add180ca01976",
2762
2774
  "test/layer-0-primitives/guard-svg.test.js": "sha256:057b281464de02d03e5e1a5c76a0a7efb23904bbcc7be43095924d6a73e5bbfe",
2763
2775
  "test/layer-0-primitives/guard-tenant-id.test.js": "sha256:4083f32ea58e4b350fd6b40e7ada751e4b213ae4a03b85500505863fb72d800a",
@@ -2847,12 +2859,15 @@
2847
2859
  "test/layer-0-primitives/mail-store.test.js": "sha256:80f98a73baf628158df0bc879ca9f35c4c534e3e5562e2a25fae41b0b5a1bbc1",
2848
2860
  "test/layer-0-primitives/mail-unsubscribe.test.js": "sha256:b745189403f7574e4845c2366a05fcdb9a3fcaf21c6930e2fe53ac94f1d1dec8",
2849
2861
  "test/layer-0-primitives/mail.test.js": "sha256:c83e3d7a9d22ad072104ac6d3c360a2b272f16f364ad8206b5eff7043fe97e48",
2862
+ "test/layer-0-primitives/mcp-coverage.test.js": "sha256:b27f9f19be15c0f5308d1fe88534bd979d4b84439f1a6ecb117bc085cae4626b",
2850
2863
  "test/layer-0-primitives/mcp-tool-registry.test.js": "sha256:57bc395acf4e7bac1bd4385ba6bf3d749ecee20dacd20b97ae1a6ca7b439a201",
2851
2864
  "test/layer-0-primitives/mcp.test.js": "sha256:21d45e7d498b5d7404294709b0031fa1882505ee81681090190aaeeece066a20",
2852
2865
  "test/layer-0-primitives/mdoc.test.js": "sha256:d89818a74820c8729d9c9b6024311468fcc72c3333474a5658f57fa79f514e07",
2866
+ "test/layer-0-primitives/metrics-coverage.test.js": "sha256:1ee32f903567bc9451d7f0257b3fb830a438283e0714c5ee38af37ec5cdeffef",
2853
2867
  "test/layer-0-primitives/metrics-shadow-registry.test.js": "sha256:60e3a71c5ec83f83901a9484db09079d1d8e32768538fb953fd3732ce1c15076",
2854
2868
  "test/layer-0-primitives/metrics-snapshot.test.js": "sha256:57b4686c1d472c07e85ee32ab8f08e7ba9332ccdb1e6000363be87815e4eef32",
2855
2869
  "test/layer-0-primitives/middleware-compose-pipeline.test.js": "sha256:012ccb0976f5fddbc6ef370a7c2dabed7c24a9619e0cfb0ea1532551b40fc0c0",
2870
+ "test/layer-0-primitives/middleware-tus-upload-coverage.test.js": "sha256:c8a54bc3116804a8ca6b7a8d8ddce75de6771af421af531b5c8043918e8bffed",
2856
2871
  "test/layer-0-primitives/mime-parse.test.js": "sha256:d6d843a250c6dccac9c125668de5cb3885bf043a0c24eb176ab871d9cadbe878",
2857
2872
  "test/layer-0-primitives/money.test.js": "sha256:76fb9d1071d72cdf7f26bbd0a69dc5329a5a35f436d52b0a041c30afef602831",
2858
2873
  "test/layer-0-primitives/mtls-ca-paths.test.js": "sha256:e770ce593b1f7c271be6aea5c4245fbda32ba22b66466c9cff9c35b0886262e9",
@@ -2890,6 +2905,7 @@
2890
2905
  "test/layer-0-primitives/output-header-hardening.test.js": "sha256:3c31c56bb1ac63783c40832cf35d0748c801ad7a74e385fc8d46f63ad2907206",
2891
2906
  "test/layer-0-primitives/pagination.test.js": "sha256:19364aa49ce2808b5ab20cc16d59ffe6dc41a2d05ff868cdd83b076f0adb7793",
2892
2907
  "test/layer-0-primitives/parser-verify-hardening.test.js": "sha256:cba2f34efc631efe95c5cc10ffb8af75d4fbd7f8586520149cf6f05b96bede19",
2908
+ "test/layer-0-primitives/parsers-safe-yaml-coverage.test.js": "sha256:5717cc0b7d36264f40f833d3284245c30f09573942218731702450c7f1341633",
2893
2909
  "test/layer-0-primitives/parsers-standalone.test.js": "sha256:21ea49f2722bf595f4d04491f08626dcbee6d1be4241667a33ac6f9450671ef7",
2894
2910
  "test/layer-0-primitives/passkey-real-vectors.test.js": "sha256:5ac058d841807fe15a9d44f823e6cfbcf028efbedd14c3cc7e902eb5e31aaa92",
2895
2911
  "test/layer-0-primitives/passkey.test.js": "sha256:a46e1f12c969efaa3d1fd04bcc248a0d8f80f976f52404cb772683d56091a80d",
@@ -135,7 +135,7 @@ jobs:
135
135
 
136
136
  - name: Log in to GHCR
137
137
  if: ${{ !inputs.dry_run }}
138
- uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
138
+ uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
139
139
  with:
140
140
  registry: ghcr.io
141
141
  username: ${{ github.actor }}
@@ -340,7 +340,7 @@ jobs:
340
340
  # against a public package, so this is the right shape regardless
341
341
  # of visibility.
342
342
  - name: Log in to GHCR
343
- uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
343
+ uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
344
344
  with:
345
345
  registry: ghcr.io
346
346
  username: ${{ github.actor }}
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.16.x
10
10
 
11
+ - v0.16.3 (2026-07-03) — **A batch of fail-open, injection-bypass and error-path fixes across the metrics, guard-sql, MCP, YAML-parser, content-credentials, tus-upload and buffer primitives — including a credential-leak fix on the /metrics exposition surface — alongside a further expansion of the automated test suite.** This patch closes a set of security and robustness defects concentrated in the error-handling and adversarial-input paths of the observability, SQL-guard, MCP, YAML-parsing, content-credentials and resumable-upload primitives, surfaced while continuing to expand the automated test suite. The most consequential: credential-shaped metric label values were redacted only in the internal cardinality key, not in the rendered exposition, so a raw bearer token / API key / JWT placed on a label leaked verbatim to every `/metrics` scrape reader — redaction now applies to the stored labels the exposition renders. The SQL guard's fragment mode (the `whereRaw` escape hatch) refused an embedded single-quoted literal but not a PostgreSQL dollar-quoted string (`$tag$…$tag$`) or a lone top-level `;`, so operator concatenation into a dollar-quoted context bypassed the floor that forces `?`-placeholder binding. MCP tool-input validation was fail-open when a JSON Schema omitted an explicit top-level `type:"object"` (a valid, common shape) — `required`/`properties`/`additionalProperties` were skipped and any input accepted; and its server-initiated sampling token cap was bypassable with a string `maxTokens`. The YAML parser's flow style (`{…}` / `[…]`) accepted duplicate keys and the merge key that block style rejects — letting a later value silently override an earlier one a preceding check had already read — and mis-parsed a root-level JSON object. content-credentials returned a only shallowly-frozen manifest (nested claim fields stayed mutable before signing), and its identity-assertion hash re-check used set membership rather than multiset matching, so duplicate referenced assertions could leave a bound assertion never re-confirmed. Robustness fixes round out the batch: metrics now reject `±Infinity`; the buffer collector preserves a caller error class's code/message (they were swapped for framework error classes, so a caller branching on the error code — e.g. a resumable-upload `413` vs `400` — never matched); resumable uploads return `413` (not a `400` that leaked the internal error code) on an oversized chunk, reject a prototype-key checksum algorithm with `400` (not `500`), and parse a deferred `Upload-Length` as strictly as creation; the MCP guard refuses `/register/` (trailing slash) like `/register`, answers a missing/invalid bearer with `401` and its `WWW-Authenticate` challenge instead of `400`, honors an explicit empty accepted-version list, and accepts a loopback `http` redirect URI for native/CLI clients while still refusing non-loopback `http`. **Fixed:** *Metrics reject non-finite values* — `gauge.set`, `histogram.observe` and `counter.inc` guarded their value with an `isNaN`-only check (counter only checked `< 0`), so `±Infinity` passed and produced `Infinity` / `-Infinity` in the exposition — invalid Prometheus 0.0.4 / OpenMetrics, which a scraper rejects. All three now require a finite number, matching their `must be a finite number` error text. · *Buffer stream collector preserves a caller error class's code and message* — `safeBuffer.collectStream` constructs its size-limit error with `(message, code)`, matching its own error class — but a framework error class (the convention elsewhere) takes `(code, message)`, the opposite order, so a caller that passed its own error class received an error with `code` and `message` swapped. A caller branching on the error code then never matched — for example the resumable-upload handler mapped an oversized chunk to `400` instead of `413`. The collector now sets both fields explicitly so they are correct whichever constructor order the passed class uses. · *Resumable upload: correct status codes and strict deferred-length parse* — An oversized `PATCH`/creation chunk now returns `413 Payload Too Large` instead of a `400` whose body leaked the internal error code; an `Upload-Checksum` naming a prototype key (e.g. `__proto__`) returns `400 unsupported` instead of a `500` from an unguarded lookup; and a deferred-length finalization now parses `Upload-Length` with the same strict check as creation, rejecting trailing junk (`"10abc"`) rather than silently truncating it. · *MCP guard: trailing-slash registration refusal, 401 bearer challenge, empty accepted-version list* — The static-registration refusal matched `/register` exactly or by suffix, so `/register/` — which most routers normalize to the same handler — slipped through; it is now refused too. A missing or invalid bearer now returns `401 Unauthorized` with its `WWW-Authenticate` challenge (RFC 6750 §3) instead of `400`, so clients keyed on `401` re-authenticate. And an explicit empty `accepted` protocol-version list now rejects every version instead of silently widening to the default set. · *MCP accepts a loopback `http` redirect URI* — `redirect_uri` validation parsed the URI with the default HTTPS-only protocol set, so a loopback `http://localhost/…` redirect (RFC 8252 native-app / CLI client, permitted by RFC 9700 §4.1.1) failed as `did not parse` before the local-host allowance could run. The parse now admits `http` so the loopback allowance applies; a non-loopback `http` redirect is still refused. · *YAML: trailing content after a flow collection and root-level JSON objects* — `root: [1, 2] junk` silently dropped the trailing content where the quoted-scalar path rejects it; trailing content after a flow collection is now refused (`yaml/trailing-content`). A root-level flow/JSON object (`{"a": 1, "b": 2}`) was mis-scanned as a block mapping with a garbage key and returned structurally-wrong data; the leading flow bracket is now recognized before the block-mapping scan so a root JSON object parses correctly. · *content-credentials: deep-frozen manifest and typed rejections for bad build/sign inputs* — `build` returned a shallowly-frozen manifest, leaving every nested claim field (`provider`/`system`/`content`) mutable before signing despite the documented pre-sign immutability guarantee; the manifest is now deep-frozen. A non-finite or out-of-Date-range `generatedAt` (`NaN` / `Infinity`) crashed with an untyped `RangeError`, and a non-Buffer `certChain` entry to `signCose` threw an untyped Node `TypeError`; both now reject with a typed `content-credentials/*` error at call time. **Security:** *Credential-shaped metric labels are redacted in the `/metrics` exposition, not just the cardinality key* — The metrics registry rewrote a credential-shaped label value to `[REDACTED-CREDENTIAL]`, but only inside the function that builds the internal Map cardinality key. The stored label object — the one the exposition renderer emits — kept the raw value, so a bearer token, API key or JWT placed on a label (e.g. an `authorization` label) was written verbatim to every `/metrics` scrape reader, contradicting the documented guarantee that the bytes never reach the scrape stream. Redaction now runs on the resolved labels themselves, so the key and the rendered output both carry the marker. · *SQL guard fragment mode refuses PostgreSQL dollar-quoted literals and any top-level semicolon* — In fragment mode (`whereRaw`), the embedded-string-literal floor that forces every value through a `?` placeholder detected a single-quoted literal but not a dollar-quoted one (`$tag$…$tag$` or `$$…$$`), so operator input concatenated into a dollar-quoted context passed the strict floor that the equivalent single-quoted value is refused by. The floor now recognizes dollar-quoted literals. Fragment mode also now refuses any top-level `;` — including a lone or trailing one — rather than relying on the stacked-statement scan, which only fired when a second statement followed the `;`. · *MCP tool-input validation enforces the schema without an explicit `type:"object"`* — `validateToolInput` gated the `required` / `properties` / `additionalProperties` checks on the schema declaring `type:"object"`. A schema that carries `properties`/`required` but omits `type` — a valid and common JSON Schema shape — was treated as untyped, so those constraints were skipped and any argument object was accepted, defeating the primitive's stated purpose (OWASP LLM07). Such a schema is now treated as an object schema and its constraints enforced. · *MCP sampling token cap rejects a non-numeric `maxTokens`* — `sampling.guard` applied the per-request token cap only when `maxTokens` was a number, so a hostile-tool-initiated sampling request carrying `maxTokens` as a string skipped the cap entirely — and a downstream SDK that coerces the string would then exceed the budget. A non-numeric `maxTokens` on this server-initiated surface is now refused with a typed error. · *content-credentials identity-assertion re-check uses multiset matching* — `verifyIdentityAssertion` re-confirmed the signed referenced-assertion hash binding with count-equality plus set membership, never consuming a matched entry. Supplying referenced assertions that hash to `[A, A]` against a signed binding of `[A, B]` passed — the counts matched and each supplied hash was present — even though the bound `hash(B)` was never re-confirmed against a real assertion (a transplant fail-open). The re-check now consumes each bound hash as it is matched, so duplicates can no longer stand in for a distinct bound assertion. · *YAML flow-style mappings refuse duplicate keys and the merge key like block style* — Block mappings rejected a duplicate key (`yaml/duplicate-key`) and the anchor-merge key `<<` (`yaml/merge-key-banned`), but flow mappings (`{ a: 1, a: 2 }`) enforced neither — silently keeping the last value on a repeat and accepting `<<` as an ordinary key. A value an earlier check has already read could thus be overridden through flow style. Flow mappings now apply both guards.
12
+
11
13
  - v0.16.2 (2026-07-03) — **Security hardening across the mail, OAuth/SAML, ACME, keychain and router primitives — six fail-open / injection / bypass fixes and seven correctness fixes — alongside a large expansion of the automated test suite.** This patch closes a batch of security and correctness defects surfaced while substantially expanding the test suite across the framework's mail-authentication, federated-identity, ACME, keychain, backup, CLI and router primitives. The security fixes: SPF evaluation no longer throws (and thus can no longer be laundered into a temperror that a permissive inbound policy accepts) on a malformed `ip4:` CIDR mask; the Authentication-Results builder now refuses CR/LF/NUL in the `version` field, closing the last header-injection gap left by the earlier mail-header sweep; `res.redirect` now rejects a horizontal TAB (which user agents strip before URL parsing, turning `/\t/evil.example` into a protocol-relative redirect to an attacker origin); the OAuth refresh-token one-time-use replay gate now treats any truthy `seen()` result as seen (a Redis `1` / SQL `COUNT` no longer slips a stolen refresh token past a `=== true` compare) — with the same normalization applied to its sibling replay checks; SAML Single-Logout over HTTP-POST/SOAP now fails closed (throws) when a verification key is supplied without its algorithm, matching the redirect binding instead of silently skipping signature verification; and the OAuth authorization-URL / PAR builders now refuse operator `extraParams` that collide with framework-managed parameters (`redirect_uri`, `state`, `code_challenge`, …). The correctness fixes: concurrent keychain file-store writes are serialized under the existing file lock (no more lost bindings); TLS-RPT submission reads the real `statusCode` (successful reports are no longer misreported as failures); ACME account key-rollover stops double-encoding its inner JWS payload (rollover was always rejected by the CA); the response-schema validator now also validates JSON bodies sent via `res.end`; keychain `remove` validates a relative fallback path like `store`/`retrieve`; `bundleAdapterStorage` honors the ambient compliance posture like `create`; and a refused `dev --ignore` pattern now returns exit code 2 instead of rejecting the CLI promise. **Fixed:** *Concurrent keychain file-store writes no longer drop bindings* — `store` and the file-remove path performed an unlocked read-modify-write of the fallback file, so two concurrent stores could each read the pre-update document and the later write would clobber the earlier binding. Both read-modify-write sequences are now serialized under the file's existing lock. · *TLS-RPT submission reads the response `statusCode`* — `tlsRpt.submit` read `status` from the HTTP client response, but the client resolves `statusCode`, so a successful (2xx) report POST was reported as `{ ok: false, error: 'HTTP undefined' }`. It now reads `statusCode`, so delivered reports are distinguished from rejected ones. · *ACME account key-rollover no longer double-encodes its inner JWS payload* — `accountKeyRollover` passed an already-stringified payload to a signer that stringifies internally, so the inner keyChange JWS payload encoded a JSON string instead of the JSON object and every rollover was rejected by the CA (RFC 8555 §7.3.5). The payload is now passed once. · *Response-schema validator also validates `res.end` JSON bodies* — The response validator wrapped only `res.json`, so a handler shipping an invalid body via `res.end(JSON.stringify(...))` escaped validation despite the documented contract. JSON-shaped `res.end` bodies are now validated on the same path. · *keychain `remove` validates a relative fallback path like `store`/`retrieve`* — `remove` checked file existence before validating the fallback path, so a relative path silently no-op'd instead of throwing the config error that `store`/`retrieve` raise. It now validates first, consistent with the other file-fallback paths. · *`bundleAdapterStorage` honors the ambient compliance posture* — `bundleAdapterStorage` only refused an unencrypted strategy when an explicit posture was passed, unlike `create`, which reads the ambient `b.compliance` posture and refuses `encrypt:false` under HIPAA. `bundleAdapterStorage` now falls back to the ambient posture, closing the asymmetry. · *`dev --ignore` pattern refusal returns exit code 2* — A `dev --ignore` pattern that exceeded the length cap or was refused by the regex guard threw out of the CLI promise instead of writing to stderr and returning exit code 2 like every other CLI validation failure. It now returns the standard non-zero exit. **Security:** *SPF `ip4:` with a malformed CIDR mask fails closed instead of throwing (fail-open)* — `_ipv4InCidr` lacked the finite-mask guard its IPv6 sibling has, so an `ip4:` mechanism with a non-numeric prefix (e.g. `ip4:192.0.2.0/`) reached `BigInt(32 - NaN)` and threw an uncaught `RangeError` out of `b.mail.spf.verify` / `b.mail.inbound.verify`. Upstream mail handling catches that throw as a temperror, and an inbound policy configured to accept on temperror would then admit a sender that should have been evaluated. SPF now returns a `permerror` result for a malformed mask, honoring the primitive's documented never-throw-on-message-content contract. · *Authentication-Results builder refuses CR/LF/NUL in the `version` field (header injection)* — `b.mail.authResults.emit` validated the `authserv-id` against control characters but not the operator-supplied `version`, which is interpolated onto the `Authentication-Results:` header line — the last field left unguarded by the earlier mail-header CRLF sweep. A `version` containing CRLF could smuggle an arbitrary header. It is now refused with a typed error, like every other field on that line. · *`res.redirect` rejects horizontal TAB (open-redirect / same-origin bypass)* — The redirect target's control-character guard rejected CR/LF/NUL but not TAB (0x09). Node permits a TAB in a header value, but the WHATWG URL parser strips ASCII TAB/LF/CR from a URL before resolving it, so a `Location: /\t/evil.example` collapses to the protocol-relative `//evil.example` and navigates to the attacker origin — bypassing the same-origin/allowlist heuristic. TAB is now rejected alongside CR/LF/NUL. · *OAuth refresh-token replay gate treats any truthy `seen()` result as seen (fail-open)* — The legacy `seen(refreshToken)` replay gate compared the callback's return with `=== true`, but the documented contract is that it returns a truthy value when the token was presented before. A store returning a truthy non-`true` value — a Redis `EXISTS`/`SISMEMBER` `1`, a SQL `COUNT` — slipped a replayed (stolen) refresh token past the one-time-use gate. Any truthy result now counts as seen; the same normalization was applied to the sibling replay checks in the module. · *SAML Single-Logout over HTTP-POST/SOAP fails closed on a key without its algorithm* — The POST and SOAP SLO parsers gated signature verification with an OR condition and the verify helper early-returned when the algorithm was absent, so supplying a verification key without `idpVerifyAlg` silently accepted a forged, unsigned LogoutRequest — while the HTTP-Redirect binding failed closed. All four SLO parse paths now route through one config check that throws when a key is supplied without its algorithm. · *OAuth authorization-URL / PAR builders refuse `extraParams` that collide with managed parameters* — `authorizationUrl` and `pushAuthorizationRequest` merged operator `extraParams` with `URLSearchParams.set`, which replaces — so an `extraParams` entry for `redirect_uri`, `state`, or `code_challenge` overwrote the framework-generated value while the call still returned the framework's `state`/PKCE verifier, diverging the returned session seed from the emitted URL. Reserved parameter keys in `extraParams` are now refused with a typed error at both builders.
12
14
 
13
15
  - v0.16.1 (2026-07-03) — **Every source file now carries an SPDX license + copyright header; the wiki example emits its full HSTS when it runs behind a TLS-terminating reverse proxy; and a good-first-issue on-ramp lands for new contributors.** This patch stamps a per-file `SPDX-License-Identifier: Apache-2.0` and a copyright line onto every first-party source file, so the license of any single file is unambiguous when it is read, audited, or vendored in isolation. The project stays Apache-2.0 — this is a clarity change, not a license change, and vendored third-party files under lib/vendor/ keep their own upstream headers. The wiki example now passes its trusted-proxy CIDRs to the securityHeaders middleware: a deployment behind a TLS-terminating reverse proxy (Caddy or nginx) that forwards `X-Forwarded-Proto: https` now emits the framework's strong HSTS (two-year max-age, includeSubDomains, preload) instead of suppressing it on the plain-HTTP hop from the proxy — the wiki e2e asserts both the proxied-https and the direct-http cases. CONTRIBUTING gains a good-first-issue on-ramp so new contributors have a clear, small first step. **Added:** *Per-file SPDX license and copyright headers* — Every first-party source file now begins with `// SPDX-License-Identifier: Apache-2.0` and a copyright line, so the license and holder of any individual file are explicit when it is read or extracted on its own. No license change — the project remains Apache-2.0. Vendored files under `lib/vendor/` are untouched and retain their upstream license headers. **Changed:** *Good-first-issue on-ramp for new contributors* — CONTRIBUTING now points new contributors at issues labelled `good first issue` — small, self-contained tasks (a doc or wiki-example fix, a test for an uncovered branch) that are the easiest first step into the codebase. **Security:** *Wiki example emits its full HSTS behind a TLS-terminating reverse proxy* — The wiki example app now hands its trusted-proxy CIDRs to `securityHeaders`, so behind Caddy or nginx forwarding `X-Forwarded-Proto: https` it emits `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload` rather than dropping HSTS on the internal HTTP hop from the proxy. Operators modelling their own app on the example inherit the fix by declaring their proxy CIDRs via `WIKI_ADMIN_TRUSTED_PROXIES` (the production compose already defaults to the private ranges). The framework's `securityHeaders` primitive is unchanged; this closes a wiring gap in the example.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 1,
3
- "frameworkVersion": "0.16.2",
4
- "createdAt": "2026-07-03T12:18:24.287Z",
3
+ "frameworkVersion": "0.16.3",
4
+ "createdAt": "2026-07-03T20:45:25.445Z",
5
5
  "exports": {
6
6
  "a2a": {
7
7
  "type": "object",
@@ -16,7 +16,7 @@
16
16
  }
17
17
  },
18
18
  "node_modules/@blamejs/core": {
19
- "version": "0.16.2",
19
+ "version": "0.16.3",
20
20
  "resolved": "file:../..",
21
21
  "license": "Apache-2.0",
22
22
  "bin": {