@blamejs/blamejs-shop 0.4.31 → 0.4.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +1 -1
- package/lib/asset-manifest.json +1 -1
- package/lib/vendor/MANIFEST.json +400 -282
- package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
- package/lib/vendor/blamejs/.gitignore +6 -0
- package/lib/vendor/blamejs/CHANGELOG.md +28 -0
- package/lib/vendor/blamejs/MIGRATING.md +55 -0
- package/lib/vendor/blamejs/README.md +8 -6
- package/lib/vendor/blamejs/SECURITY.md +19 -3
- package/lib/vendor/blamejs/api-snapshot.json +2190 -664
- package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
- package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
- package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
- package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
- package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
- package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
- package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
- package/lib/vendor/blamejs/index.js +4 -0
- package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
- package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
- package/lib/vendor/blamejs/lib/api-key.js +158 -77
- package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
- package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
- package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
- package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
- package/lib/vendor/blamejs/lib/audit.js +259 -123
- package/lib/vendor/blamejs/lib/auth/elevation-grant.js +6 -2
- package/lib/vendor/blamejs/lib/auth/oauth.js +66 -9
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
- package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +36 -7
- package/lib/vendor/blamejs/lib/backup/index.js +45 -10
- package/lib/vendor/blamejs/lib/break-glass.js +355 -147
- package/lib/vendor/blamejs/lib/cache.js +174 -105
- package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
- package/lib/vendor/blamejs/lib/cli.js +19 -14
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
- package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
- package/lib/vendor/blamejs/lib/cluster.js +119 -71
- package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
- package/lib/vendor/blamejs/lib/compliance.js +210 -4
- package/lib/vendor/blamejs/lib/consent.js +82 -29
- package/lib/vendor/blamejs/lib/constants.js +27 -11
- package/lib/vendor/blamejs/lib/credential-hash.js +9 -0
- package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
- package/lib/vendor/blamejs/lib/db-query.js +882 -260
- package/lib/vendor/blamejs/lib/db-schema.js +228 -44
- package/lib/vendor/blamejs/lib/db.js +249 -99
- package/lib/vendor/blamejs/lib/dsr.js +385 -55
- package/lib/vendor/blamejs/lib/error-page.js +14 -1
- package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
- package/lib/vendor/blamejs/lib/external-db.js +549 -34
- package/lib/vendor/blamejs/lib/file-upload.js +52 -7
- package/lib/vendor/blamejs/lib/framework-error.js +20 -1
- package/lib/vendor/blamejs/lib/framework-files.js +73 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
- package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
- package/lib/vendor/blamejs/lib/guard-all.js +1 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
- package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
- package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
- package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
- package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
- package/lib/vendor/blamejs/lib/guard-email.js +47 -69
- package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
- package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
- package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
- package/lib/vendor/blamejs/lib/guard-html.js +53 -108
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
- package/lib/vendor/blamejs/lib/guard-image.js +46 -103
- package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
- package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
- package/lib/vendor/blamejs/lib/guard-json.js +38 -108
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
- package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
- package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
- package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
- package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
- package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
- package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
- package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
- package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
- package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
- package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
- package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
- package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
- package/lib/vendor/blamejs/lib/guard-template.js +35 -172
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-time.js +32 -154
- package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
- package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
- package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
- package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
- package/lib/vendor/blamejs/lib/http-client.js +37 -9
- package/lib/vendor/blamejs/lib/inbox.js +120 -107
- package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
- package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
- package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
- package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
- package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
- package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
- package/lib/vendor/blamejs/lib/mail-store.js +293 -154
- package/lib/vendor/blamejs/lib/mail.js +8 -4
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
- package/lib/vendor/blamejs/lib/migrations.js +108 -66
- package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
- package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
- package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
- package/lib/vendor/blamejs/lib/observability.js +124 -0
- package/lib/vendor/blamejs/lib/otel-export.js +12 -3
- package/lib/vendor/blamejs/lib/outbox.js +184 -83
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
- package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
- package/lib/vendor/blamejs/lib/queue-local.js +225 -140
- package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
- package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
- package/lib/vendor/blamejs/lib/queue.js +7 -0
- package/lib/vendor/blamejs/lib/redact.js +68 -11
- package/lib/vendor/blamejs/lib/redis-client.js +160 -31
- package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
- package/lib/vendor/blamejs/lib/retention.js +117 -42
- package/lib/vendor/blamejs/lib/router.js +212 -5
- package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
- package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
- package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
- package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
- package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
- package/lib/vendor/blamejs/lib/safe-url.js +170 -3
- package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
- package/lib/vendor/blamejs/lib/scheduler.js +47 -12
- package/lib/vendor/blamejs/lib/seeders.js +122 -74
- package/lib/vendor/blamejs/lib/session-stores.js +42 -14
- package/lib/vendor/blamejs/lib/session.js +175 -77
- package/lib/vendor/blamejs/lib/sql.js +3842 -0
- package/lib/vendor/blamejs/lib/sse.js +26 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +169 -4
- package/lib/vendor/blamejs/lib/static.js +177 -34
- package/lib/vendor/blamejs/lib/subject.js +96 -49
- package/lib/vendor/blamejs/lib/vault/index.js +3 -2
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
- package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
- package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
- package/lib/vendor/blamejs/lib/websocket.js +35 -5
- package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
- package/lib/vendor/blamejs/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
- package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
- package/lib/vendor/blamejs/release-notes/v0.15.7.json +43 -0
- package/lib/vendor/blamejs/scripts/check-services.js +21 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +67 -0
- package/lib/vendor/blamejs/scripts/release.js +398 -38
- package/lib/vendor/blamejs/test/00-primitives.js +168 -0
- package/lib/vendor/blamejs/test/10-state.js +140 -14
- package/lib/vendor/blamejs/test/20-db.js +65 -2
- package/lib/vendor/blamejs/test/helpers/db.js +9 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
- package/lib/vendor/blamejs/test/helpers/services.js +21 -0
- package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
- package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
- package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
- package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
- package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
- package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
- package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
- package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
- package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
- package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
- package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
- package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
- package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
- package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
- package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
- package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1196 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +362 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-watchdog-stale-settle.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
- package/lib/vendor/blamejs/test/smoke.js +79 -21
- package/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
- package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
- package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
- package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
- package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
- package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
- package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
- package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
- package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
- package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
- package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
- package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
- package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
- package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.15.0",
|
|
4
|
+
"date": "2026-06-08",
|
|
5
|
+
"headline": "A chainable SQL builder, MySQL as a first-class data backend, and a keyed lookup-hash default — the data layer goes tri-dialect",
|
|
6
|
+
"summary": "This release makes the framework's data layer dialect-portable. `b.sql` is a new chainable query builder that quotes every identifier by construction, binds every value as a placeholder, and emits dialect-correct SQL for SQLite, Postgres, and MySQL; `b.guardSql` validates result rows against NUL bytes, quote-jump sequences, and per-column / total size boundaries. The entire framework data layer — the signed audit chain, cluster leadership and fencing, sessions, break-glass, the local queue, cache, scheduler, migrations, consent, and mail storage — is rebuilt on `b.sql`, which makes MySQL a supported external-database backend alongside Postgres and SQLite. Running the data layer on a real Postgres server surfaced two latent correctness bugs that only a non-SQLite backend exposes: identifiers were emitted unquoted at DDL time but read back camelCase (Postgres folds unquoted names to lowercase, silently breaking the audit chain, consent chain, cluster fencing, and vault-key consistency), and sealed columns coerced Buffer / object payloads through `String()` before encryption (corrupting non-string ciphertext); both are fixed by quoting every identifier and coercing per the column's declared type. Derived-hash columns — the blind-index lookup columns registered through `registerTable` — now default to a keyed MAC (SHAKE256 under the vault's per-deployment MAC key) instead of an unkeyed salted hash, so an exfiltrated database can no longer be brute-forced or rainbow-tabled to recover the indexed values; existing salted-hash indexes are read through a dual-read path and migrated forward, so the change is non-breaking. Audit-signing key rotation now preserves every historical checkpoint (rotation previously stranded checkpoints signed under the prior key). New cross-border data-residency postures (appi-jp, pdpa-sg, uk-gdpr) enforce a mandatory storage vacuum after erasure. Outbound TLS appends classical X25519 to its key-exchange preference so it can complete a handshake with a peer that advertises no post-quantum hybrid — previously the hybrids-only preference failed those handshakes outright — and emits a `tls.classical_downgrade` audit event whenever a connection lands on a classical group. Outbound HTTP/2 negotiation falls back to HTTP/1.1 against servers that only speak h1, the network heartbeat honors a target's permitted protocols instead of pinning cleartext targets down, a WebSocket connection closes cleanly on a peer's TCP half-close instead of wedging open, and the Azure blob backend encodes object-key path segments correctly.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "b.sql — a dialect-aware chainable SQL builder",
|
|
13
|
+
"body": "`b.sql` composes SELECT / INSERT / UPDATE / UPSERT / DELETE / DDL from a fluent chain. Every identifier is validated and quoted by construction (`\"name\"` on SQLite/Postgres, `` `name` `` on MySQL), every value binds as a placeholder rather than interpolating, and the emitted statement is validated as a single balanced, single-statement query before it leaves the builder. Pass `{ dialect: \"postgres\" | \"mysql\" | \"sqlite\" }` (default SQLite) to target a backend; `upsert` emits the dialect-final conflict syntax. It composes `b.safeSql` for the identifier and placeholder primitives, so a SQL string can no longer be assembled by hand inside the framework."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "b.guardSql — result-row output validation",
|
|
17
|
+
"body": "`b.guardSql` gates query result rows against embedded NUL bytes, quote-jump sequences, and configurable per-column and total-size boundaries, with the standard guard profiles (strict / balanced / permissive) and compliance postures (hipaa / pci-dss / gdpr / soc2). It is the output-side complement to the input-side `b.safeSql`."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"title": "MySQL is a first-class data backend",
|
|
21
|
+
"body": "MySQL joins Postgres and SQLite as a supported external-database backend. The framework's schema reconciler emits MySQL DDL, and the full data layer — signed audit chain (append + verify), cluster leadership and lease fencing, sessions, break-glass, the local queue, cache, scheduler, migrations, and consent — runs against a real MySQL server. Select the backend at `b.cluster.init` / `b.externalDb` configuration via the `dialect` option; `b.clusterStorage.dialect()` exposes the configured backend dialect to composing code."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"title": "Cross-border erasure postures: appi-jp, pdpa-sg, uk-gdpr",
|
|
25
|
+
"body": "Three data-residency compliance postures are added (Japan APPI, Singapore PDPA, UK GDPR). Each requires a mandatory storage vacuum after erasure (so deleted rows are reclaimed from the page store, not just tombstoned), a signed audit chain, encrypted backups, and a minimum TLS version. Pin one with `b.compliance.set`."
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"heading": "Security",
|
|
31
|
+
"items": [
|
|
32
|
+
{
|
|
33
|
+
"title": "Lookup-hash columns default to a keyed MAC",
|
|
34
|
+
"body": "Derived-hash (blind-index) columns registered through `registerTable` now default to `derivedHashMode: \"hmac-shake256\"` — SHAKE256 keyed with the vault's per-deployment MAC key — instead of the previous unkeyed `salted-sha3`. The index value is no longer recomputable from the indexed plaintext alone, so an attacker who exfiltrates the database cannot brute-force or rainbow-table a lookup column (for example a subject-email index) without also holding the vault MAC key (CWE-916 / CWE-759). Existing `salted-sha3` indexes are read through a dual-read path and re-derived on write, so deployments upgrade without re-indexing up front."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"title": "Postgres identifier casing no longer breaks the audit and cluster chains",
|
|
38
|
+
"body": "Identifiers were written unquoted in DDL but read back in camelCase. On Postgres (which folds an unquoted identifier to lowercase) this silently desynchronized the signed audit chain, the consent chain, cluster leadership and fencing, and vault-key consistency — each reads a column the server had stored under a lowercased name. Every framework identifier is now quoted at both DDL and query time so the stored and read names match on every dialect (CWE-670). SQLite deployments were unaffected and remain byte-compatible."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"title": "Sealed columns preserve non-string payloads",
|
|
42
|
+
"body": "A sealed column coerced its value through `String()` before encryption, corrupting Buffer and object payloads (a Buffer became `\"[object Object]\"`-class garbage, an object its `toString`). Sealed values are now encoded per the column's declared type before the seal, so binary and structured payloads round-trip intact (CWE-704)."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"title": "Audit-signing key rotation preserves historical checkpoints",
|
|
46
|
+
"body": "Rotating the audit-signing key stranded every checkpoint signed under the prior key — `verifyCheckpoints` ignored the per-fingerprint history file the rotation writes, so post-rotation verification failed on otherwise-valid historical checkpoints. Verification now resolves each checkpoint's signing key by fingerprint (`getPublicKeyByFingerprint`) across the rotation history, so the full chain verifies after a key rotation."
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"title": "Outbound TLS reaches classical-only peers and audits the downgrade",
|
|
50
|
+
"body": "The framework's outbound TLS offered only ML-KEM hybrid key-exchange groups, so a handshake with a peer that does not advertise a post-quantum hybrid — most of today's internet — failed outright (`handshake_failure`), leaving outbound connections to webhooks, OAuth providers, ACME directories, object stores, and DoT/DoH/SMTP/Redis-over-TLS unable to complete. Classical X25519 is now appended to the group preference so the hybrid is still negotiated whenever the peer supports it, and the connection completes over classical X25519 when it does not. Every connection that lands on a classical group rather than the post-quantum hybrid emits a `tls.classical_downgrade` audit event (carrying the negotiated group) so operators can observe and alert on which peers are not yet post-quantum-capable."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"title": "Transport reachability and correctness",
|
|
54
|
+
"body": "Outbound HTTP/2 negotiation now falls back to HTTP/1.1 when a TLS server offers only h1 (an ALPN protocol_version alert no longer fails the request). The network heartbeat honors a target's permitted protocols instead of dropping non-default ones, so a cleartext `http://` health target is no longer reported permanently down. A WebSocket connection closes cleanly when a peer half-closes its TCP socket (a bare FIN) instead of wedging the connection open. The Azure blob backend percent-encodes each object-key path segment, so a key containing reserved characters can no longer corrupt the request URL."
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"heading": "Fixed",
|
|
60
|
+
"items": [
|
|
61
|
+
{
|
|
62
|
+
"title": "Cross-border erasure performs the mandatory vacuum",
|
|
63
|
+
"body": "Erasure under the uk-gdpr, appi-jp, and pdpa-sg residency postures now runs the storage vacuum the posture mandates, reclaiming erased rows from the page store rather than leaving them recoverable as free-list tombstones."
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"heading": "Migration",
|
|
69
|
+
"items": [
|
|
70
|
+
{
|
|
71
|
+
"title": "Derived-hash default change is non-breaking; MySQL is opt-in",
|
|
72
|
+
"body": "Lookup-hash columns default to the keyed MAC on new writes and migrate existing rows on access through the dual-read path — no upfront re-indexing, no operator action required. To pin the previous unkeyed index (for example to keep a column byte-compatible with an external system), pass `derivedHashMode: \"salted-sha3\"` to `registerTable`. MySQL as a data backend is opt-in: existing SQLite and Postgres deployments are unchanged unless you set `dialect: \"mysql\"`. The Postgres identifier-casing and sealed-column-coercion fixes change the emitted DDL and the at-rest encoding of non-string sealed values; a Postgres deployment created before this release reconciles its schema to the quoted identifiers on the next schema-ensure pass."
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.15.1",
|
|
4
|
+
"date": "2026-06-11",
|
|
5
|
+
"headline": "Sealed-column lookups find rows written before the v0.15.0 hash change, and API-key secrets re-hash to the active algorithm on verify",
|
|
6
|
+
"summary": "v0.15.0 changed the default derived hash — the blind index a sealed column is looked up by — from an unkeyed salted hash to a keyed MAC, and promised a transparent migration via a dual read. But no lookup path actually performed the dual read, so on a deployment that already held data, a lookup by a sealed column (a session's user id, an API key's owner, an audit actor or resource, a consent or data-subject id, a mail thread) computed only the new keyed digest and missed every row written before the upgrade. This release wires the dual read into every lookup: a sealed-column equality now matches both the active keyed digest and the legacy salted digest, so pre-upgrade rows are found again. That restores two correctness guarantees the gap had quietly broken — revoking all of a user's sessions no longer skips sessions created before the upgrade, and a subject erasure no longer leaves pre-upgrade rows behind. Separately, the framework's API-key store now re-hashes a stored secret to the active hash algorithm on the next successful verify, the transparent rotation the credential-hash primitive documented but the store had never performed.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Fixed",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "Sealed-column lookups match rows written before the v0.15.0 keyed-MAC change",
|
|
13
|
+
"body": "After v0.15.0 flipped the default derived-hash mode to a keyed MAC, a lookup by a sealed column computed only the new keyed digest, so rows written under the previous salted-hash default were no longer found — a silent index miss on every existing deployment. Every framework lookup path now dual-reads: `b.db.from(...).where(sealedField, value)` and the framework's own api-key, session, audit, consent, data-subject, and mail-thread lookups match the column against both the active keyed digest and the legacy salted digest (`b.db.hashCandidatesFor` exposes the candidate list for operator code). No migration or operator action is required; rows re-hash to the keyed form on read over time and the candidate set collapses back to a single value. Two correctness consequences are restored: revoking all of a user's sessions now also revokes sessions created before the upgrade, and a data-subject erasure now also deletes (and crypto-shreds) the subject's pre-upgrade rows."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "API-key secret hashes upgrade to the active algorithm on verify",
|
|
17
|
+
"body": "The framework's API-key store now re-hashes a stored secret to the configured hash algorithm on the next successful verify (leader-gated, best-effort, primary-match only), emitting an `apikey.secret_rehash` audit and observability event. This is the transparent rotation `b.credentialHash` documents — a key stored under an older algorithm or parameter set silently moves to the current one as it is used, with no change to the verify result or the returned record."
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.15.2",
|
|
4
|
+
"date": "2026-06-12",
|
|
5
|
+
"headline": "Object keys with a space, +, &, or other reserved characters now sign correctly against S3-compatible stores and Google Cloud Storage",
|
|
6
|
+
"summary": "The SigV4 request signer (and the Google Cloud Storage V4 signer that shares it) URI-encoded the object-key path a second time when building the canonical request it signs, while the request on the wire carried the path encoded once. Amazon S3 — and every S3-compatible store such as MinIO, Cloudflare R2, Wasabi, and Backblaze B2, plus GCS — signs the canonical path encoded exactly once, so any object key containing a space, a +, an &, parentheses, or a non-ASCII character was signed over a path the server never received and the request was rejected with HTTP 403 SignatureDoesNotMatch. Keys built only from unreserved characters were unaffected, which is why the regression went unnoticed. This release makes the canonical-path encoding match the service: S3 and GCS encode the path once, while the AWS services that genuinely require a second encoding (SQS, CloudWatch Logs, SNS) keep it. Object reads, writes, deletes, listings, presigned URLs, and backup or restore through the object-store adapter now succeed for keys with reserved characters. Separately, the bucket-operations key encoder now uses the AWS reserved-character set, so a key containing !, *, ', or ( is escaped to match the bytes the store signs over.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Fixed",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "SigV4 and GCS V4 sign object-key paths with the single encoding S3 and GCS expect",
|
|
13
|
+
"body": "A request to read, write, delete, list, or presign an object whose key contained a space, +, &, (, ), or a non-ASCII character failed with 403 SignatureDoesNotMatch against S3, every S3-compatible store, and Google Cloud Storage, because the canonical request double-encoded the path the wire carried once. The signer is now service-aware: S3 and GCS sign the path encoded once (matching the wire and the store's own canonicalization), while SQS, CloudWatch Logs, and SNS keep the second encoding the AWS spec requires for those services. No configuration change or migration is needed — object operations and presigned URLs for keys with reserved characters simply start working. Object keys made only of unreserved characters are byte-for-byte unchanged."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "Bucket-operations key encoder escapes the AWS reserved set",
|
|
17
|
+
"body": "The bucket-level object operations encoded key path segments with a generic URI encoder that left !, *, ', and ( unescaped. Those now route through the same AWS encoder the read and write paths use, so a key containing one of those characters is escaped consistently and signs correctly."
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.15.3",
|
|
4
|
+
"date": "2026-06-12",
|
|
5
|
+
"headline": "DDL hardening in b.sql, schema-confined column introspection on Postgres and MySQL, and a classical-downgrade audit on proxy-tunneled TLS",
|
|
6
|
+
"summary": "This release hardens the data layer and closes a transport audit gap. The b.sql builder refuses an unrecognised column type that carries a statement terminator, quote, or comment marker - the one position in an otherwise quote-by-construction DDL builder where a verbatim string reached the emitted statement - and routes the finished CREATE TABLE through the same single-statement gate every other verb uses. The schema reconciler's column introspection is now confined to the schema or database the bare-named CREATE TABLE actually writes into (current_schema() on Postgres, DATABASE() on MySQL), so a same-named table in another schema no longer pollutes the column set, silently skipping an ADD COLUMN or fabricating false schema drift that refuses a regulated-posture boot. Two further builder gaps are fixed: a column-level primary key combined with a composite primaryKey now fails at build time with a clear error instead of producing invalid DDL, and a MySQL upsert read-back keyed by a cast or a server-evaluated function now renders the cast (or refuses the function) instead of binding an internal wrapper. Finally, an HTTPS request sent through a configured proxy now emits the tls.classical_downgrade audit when the handshake falls back to a classical group, the same as a direct connection.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Security",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "b.sql refuses an injection-bearing verbatim column type and gates every CREATE TABLE",
|
|
13
|
+
"body": "An unrecognised column type passed to b.sql.createTable / alterTable was emitted into the DDL verbatim - the single raw-emission position in a builder that otherwise quotes every identifier and guards every constraint fragment. A type such as \"text); DROP TABLE secrets; --\" could therefore smuggle a stacked statement. The builder now refuses, at build time, a verbatim type carrying a statement terminator or comment marker, and routes the finished CREATE TABLE / ALTER TABLE statement through the same single-statement / NUL / unterminated-quote / unbalanced-paren gate every SELECT / INSERT / UPDATE / DELETE / UPSERT already used - so an unbalanced quote is caught there. Legitimate types are unaffected: VARCHAR(255), NUMERIC(10,2), DOUBLE PRECISION, TIMESTAMP WITH TIME ZONE, and MySQL ENUM('a','b') / SET(...) (which need balanced quotes) all still pass."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"heading": "Fixed",
|
|
19
|
+
"items": [
|
|
20
|
+
{
|
|
21
|
+
"title": "Schema reconciliation reads columns from the right schema on Postgres and MySQL",
|
|
22
|
+
"body": "The reconciler's column introspection queried information_schema with no schema filter, so on a Postgres instance or MySQL server hosting more than one schema/database with a same-named table, the live column set was the union across schemas. That could silently skip an ADD COLUMN the table needed, or report false drift that refuses a boot under a pinned regulated posture. Introspection is now confined to current_schema() (Postgres) / DATABASE() (MySQL) - the schema the bare-named CREATE TABLE lands in. SQLite (PRAGMA, per-file) is unchanged."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"title": "createTable rejects a contradictory primary-key declaration at build time",
|
|
26
|
+
"body": "Declaring both a column-level primary key (primaryKey / autoIncrement / serial) and a composite opts.primaryKey emitted two PRIMARY KEY clauses, which every dialect rejects at the driver mid-migration. The builder now refuses the contradiction at build time with a clear error; a single column PK, or a composite primaryKey with no column-level PK, is unaffected."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"title": "MySQL upsert read-back resolves a cast or function conflict key instead of binding a wrapper",
|
|
30
|
+
"body": "On MySQL, an upsert whose conflict key was a b.sql.cast(...) or b.sql.fn(...) built a read-back SELECT that bound the wrapper object, so the read-back matched no rows. A cast conflict key now renders as CAST(? AS type) binding the inner value; a server-evaluated function conflict key (which has no stable read-back identity) is refused with a clear error. Plain scalar conflict keys are unchanged."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"title": "Proxy-tunneled TLS emits the classical-downgrade audit",
|
|
34
|
+
"body": "An HTTPS upstream reached through a configured proxy performed its TLS handshake without emitting the tls.classical_downgrade audit on a classical-group fallback, leaving the post-quantum-readiness inventory incomplete for proxied requests. Both the upstream handshake and the proxy-leg handshake now emit the audit on a classical fallback, matching the direct connection path. The handshake itself is unchanged (still hybrid-preferred TLSv1.3)."
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.15.4",
|
|
4
|
+
"date": "2026-06-12",
|
|
5
|
+
"headline": "Telemetry attribute values are redacted before they leave the process, per-row data residency is enforced on every write and export path, DDL routes through the single-statement gate, the DPoP middleware requires its replay store, and session rotation re-keys the device binding",
|
|
6
|
+
"summary": "This release closes a set of egress, data-residency, and session-binding gaps. OTLP span, span-event, and resource attributes are now scrubbed through the telemetry redactor before serialization, on both the JSON and protobuf paths, matching the metric exporter - an attribute value holding a bearer token, password, or API key no longer reaches the collector verbatim. Per-row data residency, previously enforced only at the structured query builder, is now enforced on the three paths that bypassed it: raw SQL writes, read-replica fan-out, and backup export. Every CREATE TABLE / ALTER TABLE the schema reconciler and the DSR store emit now passes through the same single-statement gate the query builder uses. The DPoP middleware now requires its replay store at mount time instead of silently mounting a proof-of-possession gate that performed no replay check. And session rotation re-keys the device fingerprint to the new session id, so a rotated session stays bound to its device instead of falsely reporting drift on the next request.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Security",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "OTLP exporter redacts span, event, and resource attribute values before egress",
|
|
13
|
+
"body": "Span, span-event, and resource attributes were serialized to the OTLP collector verbatim on both the JSON and protobuf encodings - the metric exporter scrubbed its attributes through the telemetry redactor, but the span exporter did not. An attribute value carrying a secret or PII (a bearer token in `authorization`, a `password`, an `api_key`) was therefore shipped in clear to whatever collector the deployment points at (CWE-532). Every attribute-map encoder now runs each value through `b.observability.redactAttrs` (default composes `b.redact.redact`, dropping any attribute whose redactor throws) before the wire payload, so telemetry is redacted like the log and audit egress paths. The new `b.observability.redactAttrs(attrs)` is available for operators building custom exporters."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "Per-row data residency is enforced on raw writes, read replicas, and backups",
|
|
17
|
+
"body": "Per-row residency was enforced only at the structured query builder. Three paths reached storage or left the deployment without it: raw SQL writes (`b.db.runSql` / `b.db.prepare().run()`, INSERT and UPDATE forms) bypassed the local residency check entirely, so a cross-border row could be written under a regulated posture with no refusal; read-replica fan-out dropped the row-residency tag, routing a regulated read with no row region identified to a residency-tagged, non-cross-border replica with no check; and `b.backup.create`'s residency check compared only the single deployment region to the destination, blind to a per-row-residency table that admits rows from several regions. Raw writes now parse the target table and residency value and apply the same gate the builder does; the replica read now fails closed when the row region is unidentified; and backup now emits a per-row cross-border advisory for any declared residency table whose admitted regions differ from the backup destination."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"title": "Schema and DSR DDL routes through the single-statement gate",
|
|
21
|
+
"body": "The CREATE TABLE / ALTER TABLE statements emitted by the schema reconciler and the DSR ticket store were assembled and run without the single-statement / NUL / unterminated-quote / unbalanced-paren gate that every query the builder emits already passes. That gate is now a shared check both the builder's catalog emitter and the schema/DSR DDL path call, so a terminator, comment marker, or unbalanced quote that reached a DDL fragment is refused at emit time on every backend."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"title": "DPoP middleware requires its replay store at mount time",
|
|
25
|
+
"body": "`b.middleware.dpop` documented `replayStore` as required, but the factory read it optionally and gated the jti-replay check behind its presence - omitting it mounted a proof-of-possession gate that performed no replay check, so a captured DPoP proof could be replayed indefinitely (RFC 9449 §11.1). The middleware now requires the store at config time: a missing store, or a store lacking `checkAndInsert`, throws when the middleware is created instead of failing open at request time. The low-level `b.auth.dpop.verify` primitive keeps `replayStore` optional for advanced callers that track jti themselves."
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"heading": "Fixed",
|
|
31
|
+
"items": [
|
|
32
|
+
{
|
|
33
|
+
"title": "Session rotation re-keys the device fingerprint to the new session id",
|
|
34
|
+
"body": "A session's optional device fingerprint is keyed to its session id, so that a stolen database cannot replay the binding. `b.session.rotate` moved the session id but left the stored fingerprint keyed to the old id, so the next `verify` recomputed the fingerprint against the new id and mismatched - reporting a false `fingerprintDrift` (which destroys the session under strict operators, logging the user out on every rotation) or silently breaking the binding. Rotation now re-keys the fingerprint to the new session id from the live request: pass the same `{ req, fingerprintFields }` to `rotate`. A fingerprint-bound session rotated without `req` now throws, because the binding cannot otherwise follow the new id; unbound sessions are unaffected."
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.15.5",
|
|
4
|
+
"date": "2026-06-12",
|
|
5
|
+
"headline": "Legal-hold and subject-restriction PII is sealed at rest, and a guard's compliance-posture forensic and runtime caps are applied on its default gate",
|
|
6
|
+
"summary": "This release closes two data-protection gaps. The legal-hold registry and the subject-restriction flag stored their free-text fields - the legal basis, custodian, ticket citation, and restriction reason that link a data subject to a legal matter - in clear, because those local tables were written through a raw SQL path that bypassed the structured builder's at-rest sealing. Those columns are now sealed on write and unsealed on read, the same way the DSR ticket store already seals subject identifiers. Separately, a content guard built on the standard gate contract and gated with a compliance posture (for example b.guardCidr.gate({ compliancePosture: \"hipaa\" })) silently dropped that posture's forensic-snapshot cap and the profile's runtime cap, because the default gate passed the caller's raw options straight to the gate builder instead of resolving the profile and posture first - so a regulated-posture refusal carried no forensic evidence. The default gate now resolves the profile and posture before building the gate, matching the hand-written guard gates.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Security",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "Legal-hold and subject-restriction PII is sealed at rest",
|
|
13
|
+
"body": "`b.legalHold`'s `_blamejs_legal_hold` registry stored the hold reason, custodian, placed-by, and citation in clear, and `b.subject.restrict`'s `_blamejs_subject_restrictions` stored the restriction reason in clear - free text that ties a data subject to a litigation hold or an Art. 18 processing restriction. Those rows were written through a raw `sql.insert` + `prepare().run()` path that bypassed the structured builder's automatic field sealing (the subject-restrictions table even declared the field as sealed, but the raw write never applied it). Both now seal those columns on write and unseal on read through `cryptoField`, so the legal-basis and custodian text is encrypted at rest under the deployment's vault key. Pre-existing plaintext rows continue to read correctly (the unseal path passes through an already-plaintext value)."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "A guard's default gate applies its compliance-posture forensic and runtime caps",
|
|
17
|
+
"body": "A guard built on `b.gateContract.defineGuard` with the standard gate (no bespoke gate) and gated with a compliance posture dropped that posture's `forensicSnippetBytes` cap and the profile's `maxRuntimeMs` cap: the default gate passed the caller's raw options straight to the gate builder, which reads those caps directly, but the values live on the resolved profile and posture, not the raw options. The effect was that a regulated-posture refusal captured no forensic snapshot (the cap defaulted to 0, i.e. disabled) and the check ran without the profile's runtime bound. The default gate now resolves the profile and posture before building the gate - matching the hand-written guard gates - so `gate({ compliancePosture: \"hipaa\" })` applies the posture's forensic cap and the profile's runtime cap as documented."
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.15.6",
|
|
4
|
+
"date": "2026-06-12",
|
|
5
|
+
"headline": "Closes SAML and OIDC assertion-replay windows, bounds SSE memory under a slow client, restores at-least-once delivery for a crashed outbox publisher, makes sealed-column membership queries work, ships JOSE-conformant SD-JWT signatures, and adds a URL canonicalizer for SSRF-safe comparison",
|
|
6
|
+
"summary": "A security and correctness release. On the identity surface: a SAML Response whose Bearer (or Holder-of-Key) SubjectConfirmation omits NotOnOrAfter is now rejected instead of accepted as fresh-forever, and the OIDC ID-token verifier no longer lets a caller disable expiry validation on a normal token - the exp bypass is restricted to back-channel-logout tokens and bounded by an issued-at freshness floor. SD-JWT-VC ES256/ES384 signatures are now emitted as JOSE raw r||s, so credentials this issuer signs verify in conformant wallets and verifiers. A new b.safeUrl.canonicalize (and b.ssrfGuard.canonicalizeHost) collapses obfuscated host and IP forms to one canonical string so allowlist and SSRF comparisons can't be bypassed by encoding tricks. On the reliability side: server-sent-event channels now cap their per-connection outbound buffer and evict a stalled client instead of growing the heap without bound; the outbox reclaims a job left in-flight by a publisher that crashed mid-delivery; the background worker pool no longer drops a task queued behind one that timed out; a retention preview no longer rewrites the whole database file; an equality / membership query on an encrypted column now hashes each candidate (membership queries previously failed outright); and the on-read re-hash of a legacy lookup digest now runs on Postgres and MySQL handles, not only SQLite.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Security",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "SAML SubjectConfirmation without NotOnOrAfter is rejected",
|
|
13
|
+
"body": "`b.auth.saml` SP response verification treated the `NotOnOrAfter` attribute on a Bearer SubjectConfirmationData as optional: a confirmation that omitted it was accepted with no upper bound on the assertion's freshness - a captured assertion replayable indefinitely. SAML 2.0 Web Browser SSO Profile §4.1.4.2 requires Bearer confirmations to carry NotOnOrAfter. `verifyResponse` now rejects a confirmation that is missing NotOnOrAfter, has an unparseable value, or is expired; the Holder-of-Key path (Profile §3.1) is hardened the same way, including the previously-accepted unparseable-value case."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "ID-token expiry can no longer be disabled on a normal token",
|
|
17
|
+
"body": "`b.auth.oauth`'s `verifyIdToken` honored a `skipExpCheck` option with no constraint, so any caller could verify an expired - or replayed - ID token. That option exists only for OIDC Back-Channel Logout tokens, which carry no `exp`. It is now self-guarding: `verifyIdToken` rejects `skipExpCheck` (`auth-oauth/skip-exp-check-not-allowed`) on any token that lacks the back-channel-logout event claim, and even for a logout token it enforces an `iat` freshness floor (`auth-oauth/logout-token-stale`). The internal logout path is unaffected."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"title": "Server-sent-event channels bound their outbound buffer",
|
|
21
|
+
"body": "An SSE channel wrote to the response with no regard for backpressure and no cap on buffered bytes, so a single stalled client could make the server buffer events until the heap was exhausted (a memory-exhaustion denial of service). Each channel now tracks its unflushed-byte count and, past a per-channel cap (`maxBufferedBytes`, default 1 MiB), closes the connection and throws `sse/backpressure` - evicting the slow consumer instead of buffering without limit. A client that keeps up is never affected."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"title": "URL and host canonicalizer for SSRF-safe comparison",
|
|
25
|
+
"body": "New `b.safeUrl.canonicalize(url, opts?)` and `b.ssrfGuard.canonicalizeHost(host)` return the canonical, comparable form of a URL or host: scheme and host lowercased, IDN hosts emitted as their punycode A-label (a confusable / mixed-script host is rejected), every base of an IP literal (decimal, octal, hex, dotted, IPv4-mapped and zero-compressed IPv6) collapsed to one canonical address, default ports stripped, trailing-dot hosts normalized, and path percent-encoding normalized per RFC 3986. Use it to build host allowlists, deduplicate URLs, or compare a fetch target so an allowlist check can't be bypassed by encoding the same address a different way."
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"heading": "Fixed",
|
|
31
|
+
"items": [
|
|
32
|
+
{
|
|
33
|
+
"title": "SD-JWT-VC ES256 / ES384 signatures are JOSE-conformant",
|
|
34
|
+
"body": "`b.auth.sdJwtVc` signed and verified ES256 / ES384 credentials with node:crypto's default DER ECDSA encoding instead of the raw r||s (`ieee-p1363`) form JOSE and EUDI wallets require, so a credential this issuer signed was rejected by conformant verifiers and the library rejected conformant holders' key-binding JWTs. The issuer JWT and the holder KB-JWT now both sign and verify with `ieee-p1363`, matching the rest of the framework's JOSE signers."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"title": "Membership queries on an encrypted column now work",
|
|
38
|
+
"body": "Querying an encrypted (sealed) column with `IN` / `$in` - `b.db.from(table).whereIn(\"email\", [...])` or `b.db.collection(table).find({ email: { $in: [...] } })` - threw, because the sealed-field-to-derived-hash rewrite passed the whole candidate array to the hash lookup as a single value. Each candidate is now hashed individually and matched against both its active keyed digest and its legacy digest, so membership queries on an encrypted column return the right rows, including rows written before the lookup-hash default changed."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"title": "A timing-out background task no longer drops the task queued behind it",
|
|
42
|
+
"body": "When a `b.workerPool` task timed out or its worker errored, the slot was returned to the idle pool and drained one moment before it was marked for recycling, so a task queued behind it could be dispatched to the worker about to be terminated - and came back as `workerpool/worker-exit` (or hung) instead of running. The slot is now marked recycling before the queue is drained, so the queued task waits for the replacement worker."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"title": "The outbox recovers a job stranded by a crashed publisher",
|
|
46
|
+
"body": "`b.outbox` claims a row by flipping it to in-flight, but the claim scan only selected pending rows, so a publisher that crashed between claiming a row and recording delivery left that row in-flight forever - the event was silently dropped, breaking at-least-once delivery. The outbox now stamps each claim with a timestamp and, at the start of every poll, returns any in-flight row older than the claim lease (`claimReclaimMs`, default 5 minutes) to the pending pool so it is delivered. An existing outbox table gains the new column automatically."
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"title": "A retention preview no longer rewrites the whole database",
|
|
50
|
+
"body": "Previewing a retention rule with `b.retention` (`run(name, { dryRun: true })` or the `retention preview` command) ran a full database VACUUM for every candidate row under a regulated posture (gdpr / hipaa / and similar), because the per-row erase - which schedules the vacuum - ran before the dry-run check. A preview now computes what it would erase without touching the database; the vacuum runs only on a real erase."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"title": "On-read lookup-digest upgrade runs on Postgres and MySQL",
|
|
54
|
+
"body": "When `b.cryptoField.unsealRow` re-hashed a legacy lookup digest to the current keyed form and persisted it, the UPDATE was always built for SQLite, so on a Postgres or MySQL handle the durable rewrite quoted identifiers for the wrong dialect and silently no-opped - the legacy digest stayed on disk and the migration never completed off SQLite. The rewrite now uses the handle's own dialect."
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.15.7",
|
|
4
|
+
"date": "2026-06-13",
|
|
5
|
+
"headline": "Hardens the new URL canonicalizer against an IPv4-mapped allowlist bypass, enforces the OIDC azp authorized-party check, and closes a set of audited correctness gaps in retention, credential rehashing, the scheduler, and SD-JWT key binding",
|
|
6
|
+
"summary": "This release deepens the URL/host canonicalizer shipped in 0.15.6 and clears a batch of audited correctness gaps. The canonicalizer now folds an IPv4-mapped IPv6 address (::ffff:1.2.3.4) to its embedded IPv4 and strips every trailing dot from a host, so a dual-stack peer can no longer slip past an operator's dotted-IPv4 allowlist and host./host.. no longer evade a host comparison. (NAT64 and 6to4 hosts are deliberately kept as IPv6 so canonicalizing then classifying agrees with the SSRF classifier, which treats them as reserved.) On the OIDC side, verifyIdToken now enforces OIDC Core 3.1.3.7: a multi-audience ID token must carry an azp (authorized party) and a present azp must equal the client_id, closing a confused-deputy hole where a token minted for a different client but listing this RP in its audience array verified clean. The rest are audited fixes: retention.complianceFloor now honors the active posture set via applyPosture (the documented inheritance was unimplemented); credentialHash.needsRehash now drives the advertised SHAKE256 length-rotation (raising the output length now triggers a rehash, upgrade-only); the task scheduler no longer lets a run abandoned by its watchdog clobber the next run's state or emit a stale success when its slow promise settles late; and the SD-JWT key-binding JWT compares its audience and nonce in constant time.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Security",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "The URL canonicalizer folds IPv4-mapped IPv6 addresses to IPv4",
|
|
13
|
+
"body": "`b.safeUrl.canonicalize` / `b.ssrfGuard.canonicalizeHost` now fold an IPv4-mapped IPv6 address (`::ffff:1.2.3.4`, the `::ffff:0:0/96` block) to its embedded IPv4 dotted form. Previously it canonicalized to an IPv6 string, so a dual-stack peer never unified with an operator's `1.2.3.4` allow/deny entry — an allowlist bypass. Only the IPv4-mapped block folds, because the SSRF classifier maps it to the embedded v4 verdict directly; NAT64 (`64:ff9b::/96`) and 6to4 (`2002::/16`) are deliberately kept as IPv6, since the classifier treats a NAT64 literal as reserved and folding it would turn a blocked verdict into an allowed public IPv4. The host canonicalizer also now strips every trailing dot, so `host`, `host.`, and `host..` collapse to one form."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "verifyIdToken enforces the OIDC azp (authorized party) check",
|
|
17
|
+
"body": "`b.auth.oauth`'s `verifyIdToken` validated only that its `client_id` was present in the token's `aud`, ignoring `azp`. Per OIDC Core 3.1.3.7, a multi-audience ID token must carry an `azp` and a present `azp` must equal the RP's `client_id`. Without it, a token whose authorized party is a different client — but whose `aud` array also lists this RP — verified clean (a confused-deputy / token-substitution hole). The verifier now rejects a multi-audience token with no `azp` (`auth-oauth/azp-required`) and any token whose `azp` is not the `client_id` (`auth-oauth/azp-mismatch`). A single-audience token with no `azp` (the common case) is unaffected."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"title": "SD-JWT key-binding audience and nonce compare in constant time",
|
|
21
|
+
"body": "`b.auth.sdJwtVc.verify` compared the key-binding JWT's `aud` and `nonce` with a short-circuiting `!==`, while the adjacent `sd_hash` check already used a constant-time compare. The `nonce` is a verifier-issued replay-defense value, so a non-constant-time compare leaks a matching-prefix timing oracle; both checks now use the constant-time helper."
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"heading": "Fixed",
|
|
27
|
+
"items": [
|
|
28
|
+
{
|
|
29
|
+
"title": "retention.complianceFloor honors the active compliance posture",
|
|
30
|
+
"body": "`b.retention.complianceFloor` required an explicit posture and never read the active posture set by `applyPosture` (the `b.compliance.set` cascade), so the documented inheritance was unimplemented dead state. It now inherits the active posture when none is passed — `complianceFloor(candidateTtlMs)` uses the active posture, an explicit posture still overrides it — and `applyPosture(null)` now clears the active posture (it was a silent no-op, so `b.compliance.clear` could not reset it)."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"title": "credentialHash.needsRehash drives the SHAKE256 length-rotation",
|
|
34
|
+
"body": "`b.credentialHash.needsRehash` never compared the stored SHAKE256 digest length against the configured length, so raising the output length never triggered a rehash and the advertised length-rotation was a silent no-op. It now flags a digest shorter than the configured/default length for rehash (upgrade-only — a longer-than-target digest is never shortened, matching the Argon2 convention)."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"title": "The scheduler no longer lets a watchdog-abandoned run corrupt the next run",
|
|
38
|
+
"body": "`b.scheduler`'s watchdog force-clears a task's running flag after `maxJobMs` so a hung handler can't lock out future fires, and the next tick re-fires. The original slow promise then settled late and unconditionally overwrote the task's running / lastFinish / lastError state and emitted a `system.scheduler.task.success`|`failure` for a run the watchdog had already given up on — clobbering the new run and double-counting. Each run is now tagged with a generation that the watchdog and every fire bump; a settle whose tag is stale is ignored."
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
@@ -44,6 +44,7 @@ var BASE_SERVICES = [
|
|
|
44
44
|
{ name: "postgres", port: 5432, kind: "tcp", label: "tcp", group: "plain" },
|
|
45
45
|
{ name: "mysql", port: 3306, kind: "tcp", label: "tcp", group: "plain" },
|
|
46
46
|
{ name: "mongodb", port: 27017, kind: "tcp", label: "tcp", group: "plain" },
|
|
47
|
+
{ name: "postgres-replica",port:5433, kind: "tcp", label: "tcp (hot standby)", group: "plain" },
|
|
47
48
|
{ name: "minio", port: 9000, kind: "http", label: "GET /health/live",
|
|
48
49
|
httpPath: "/minio/health/live", group: "plain" },
|
|
49
50
|
{ name: "rabbitmq", port: 5672, kind: "tcp", label: "tcp", group: "plain" },
|
|
@@ -84,6 +85,26 @@ var BASE_SERVICES = [
|
|
|
84
85
|
httpPath: "/realms/blamejs-test/.well-known/openid-configuration", group: "federation" },
|
|
85
86
|
{ name: "keycloak-health",port:18081,kind: "http", label: "GET /health/ready",
|
|
86
87
|
httpPath: "/health/ready", group: "federation" },
|
|
88
|
+
|
|
89
|
+
// ---- cloud-storage / telemetry / AWS emulators ----
|
|
90
|
+
// Azure Blob (Azurite) + GCS (fake-gcs) + LocalStack (CloudWatch Logs +
|
|
91
|
+
// SQS, TLS-terminated by a Caddy sidecar) all serve a cert chaining to the
|
|
92
|
+
// test CA, so a TLS handshake confirms the listener is up; the integration
|
|
93
|
+
// tests do the authenticated round-trips. The OTel collector's OTLP/HTTP
|
|
94
|
+
// receiver is TLS on 4318; its health_check extension is plain HTTP on 13133.
|
|
95
|
+
{ name: "azurite", port: 10000, kind: "tls", label: "TLS handshake (blob)", group: "emulators" },
|
|
96
|
+
{ name: "fake-gcs", port: 4443, kind: "tls", label: "TLS handshake (gcs)", group: "emulators" },
|
|
97
|
+
{ name: "otel-otlp", port: 4318, kind: "tls", label: "TLS handshake (otlp)", group: "emulators" },
|
|
98
|
+
{ name: "otel-health", port: 13133, kind: "http", label: "GET /",
|
|
99
|
+
httpPath: "/", group: "emulators" },
|
|
100
|
+
{ name: "localstack-tls",port: 4566, kind: "tls", label: "TLS handshake (aws)", group: "emulators" },
|
|
101
|
+
// Toxiproxy fault-injection front-end: the API on 8474, plus transparent
|
|
102
|
+
// passthrough proxies that speak the backend protocol verbatim until a
|
|
103
|
+
// toxic is added (redis PING/PONG through :16379, raw TCP to pg on :15432).
|
|
104
|
+
{ name: "toxiproxy-api", port: 8474, kind: "http", label: "GET /version",
|
|
105
|
+
httpPath: "/version", group: "emulators" },
|
|
106
|
+
{ name: "toxiproxy-redis",port:16379, kind: "redis",label: "PING/PONG (proxied)", group: "emulators" },
|
|
107
|
+
{ name: "toxiproxy-pg", port: 15432, kind: "tcp", label: "tcp (proxied pg)", group: "emulators" },
|
|
87
108
|
];
|
|
88
109
|
|
|
89
110
|
// Expand BASE_SERVICES into IPv4 + IPv6 entries.
|
|
@@ -234,6 +234,73 @@ var OUT_OF_BAND_BREAKS = [
|
|
|
234
234
|
"Cached records in the existing table are not recoverable across the schema break — operators who care about replay continuity warm the new table by retrying the in-flight requests under the new dbStore.",
|
|
235
235
|
].join("\n"),
|
|
236
236
|
},
|
|
237
|
+
{
|
|
238
|
+
release: "v0.15.4",
|
|
239
|
+
surface: "b.middleware.dpop — replayStore now required at mount",
|
|
240
|
+
summary: "`b.middleware.dpop` now REQUIRES a `replayStore` at mount time and throws (`auth-dpop/replay-store-required`) if it is omitted or lacks `checkAndInsert`. Previously the jti-replay check was gated behind store presence, so omitting it silently mounted a DPoP gate with NO replay defense — a captured proof could be replayed indefinitely (RFC 9449 §11.1).",
|
|
241
|
+
migration: [
|
|
242
|
+
"Operators mounting `b.middleware.dpop` without a `replayStore`:",
|
|
243
|
+
"",
|
|
244
|
+
"```js",
|
|
245
|
+
"b.middleware.dpop({",
|
|
246
|
+
" replayStore: b.nonceStore.create({ backend: \"memory\" }), // shared backend on multi-process",
|
|
247
|
+
" // ...other opts",
|
|
248
|
+
"});",
|
|
249
|
+
"```",
|
|
250
|
+
"",
|
|
251
|
+
"Use a process-shared `replayStore` backend (not `\"memory\"`) on a multi-process / multi-node deployment so a proof replayed against a different worker is still caught. The low-level `b.auth.dpop.verify` primitive keeps `replayStore` optional for advanced callers that track `jti` themselves.",
|
|
252
|
+
].join("\n"),
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
release: "v0.15.4",
|
|
256
|
+
surface: "b.session.rotate — { req } required for a fingerprint-bound session",
|
|
257
|
+
summary: "Rotating a session created with a device fingerprint (`{ req, fingerprintFields }`) now requires the same `{ req, fingerprintFields }` at `b.session.rotate()`; a bound session rotated without `req` throws (`ROTATE_FINGERPRINT_REQ_REQUIRED`). The fingerprint is keyed to the session id, so rotation must re-key it to the new id from the live request — previously rotation left the old-id-keyed hash in place, which made the next `verify` false-drift (logging the user out under strict operators) or silently break the binding. Unbound sessions are unaffected.",
|
|
258
|
+
migration: [
|
|
259
|
+
"Operators who rotate fingerprint-bound sessions (login / MFA / role-change transitions):",
|
|
260
|
+
"",
|
|
261
|
+
"```js",
|
|
262
|
+
"// Pass the same { req, fingerprintFields } used at create():",
|
|
263
|
+
"await b.session.rotate(oldToken, { req, fingerprintFields: [\"clientIp\", \"userAgent\"], reason: \"mfa\" });",
|
|
264
|
+
"```",
|
|
265
|
+
"",
|
|
266
|
+
"If you rotate a bound session from a context without the request, you must supply `req` so the binding can follow to the new session id. Sessions created WITHOUT a fingerprint need no change.",
|
|
267
|
+
].join("\n"),
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
release: "v0.15.6",
|
|
271
|
+
surface: "b.auth.sdJwtVc — ES256 / ES384 signatures are now JOSE raw r||s, not DER",
|
|
272
|
+
summary: "`b.auth.sdJwtVc` now signs and verifies ES256 / ES384 with `dsaEncoding: \"ieee-p1363\"` (raw r||s), the encoding JOSE / JWS and EUDI wallets require. Previously it used node:crypto's default DER ECDSA encoding, so a credential this issuer signed was rejected by conformant verifiers and the library rejected conformant holders' key-binding JWTs. The signature bytes change shape (64 bytes for ES256, 96 for ES384, no leading `0x30` SEQUENCE tag).",
|
|
273
|
+
migration: [
|
|
274
|
+
"No code change is needed — interop with conformant JOSE / wallet verifiers now works where it previously failed. Two things to re-check if you integrated with the OLD output:",
|
|
275
|
+
"",
|
|
276
|
+
"- A previously-issued ES256 / ES384 SD-JWT-VC signed by an earlier version is DER-encoded; re-issue it (signatures are not portable across the encodings). Tokens are short-lived, so this clears on the next issuance cycle.",
|
|
277
|
+
"- If you pinned, cached, or asserted on the raw signature bytes of this library's ES256 / ES384 output, update the fixture — the bytes are now `ieee-p1363`. EdDSA / ML-DSA signatures are unchanged.",
|
|
278
|
+
].join("\n"),
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
release: "v0.15.7",
|
|
282
|
+
surface: "b.auth.oauth verifyIdToken — azp (authorized party) is now enforced",
|
|
283
|
+
summary: "verifyIdToken now applies OIDC Core 3.1.3.7: a multi-audience ID token (aud is an array with more than one entry) MUST carry an azp claim, and a present azp MUST equal the configured client_id. A token whose azp is a different client, or a multi-audience token with no azp, now throws (auth-oauth/azp-mismatch / auth-oauth/azp-required). Previously only `aud contains client_id` was checked, so a token authorized for a different party but also listing this RP verified clean.",
|
|
284
|
+
migration: [
|
|
285
|
+
"No change for the common single-audience ID token with no azp. If your IdP issues multi-audience ID tokens, ensure it sets azp to your client_id (it should, per the spec) — otherwise verifyIdToken will now reject them. This is a security fix; a token that fails the new check was authorized for a different client.",
|
|
286
|
+
].join("\n"),
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
release: "v0.15.7",
|
|
290
|
+
surface: "b.safeUrl.canonicalize — IPv4-mapped hosts fold to IPv4",
|
|
291
|
+
summary: "b.safeUrl.canonicalize / b.ssrfGuard.canonicalizeHost now fold an IPv4-mapped IPv6 host (::ffff:1.2.3.4) to its embedded IPv4 dotted form, and strip every trailing dot from a host. In 0.15.6 it canonicalized to an IPv6 string and only one trailing dot was stripped. NAT64 / 6to4 hosts stay IPv6.",
|
|
292
|
+
migration: [
|
|
293
|
+
"No code change is needed — this makes a dual-stack / NAT64 peer unify with a dotted-IPv4 allow/deny entry as intended. If you persisted canonical host strings produced by 0.15.6 (e.g. as cache or dedup keys) and compare them against freshly-canonicalized hosts, recompute them: an IPv4-mapped host now yields the dotted IPv4 instead of the bracketed IPv6, and a multi-trailing-dot host yields the bare name.",
|
|
294
|
+
].join("\n"),
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
release: "v0.15.6",
|
|
298
|
+
surface: "b.auth.oauth verifyIdToken — skipExpCheck is restricted to logout tokens",
|
|
299
|
+
summary: "`verifyIdToken`'s `skipExpCheck` option now throws (`auth-oauth/skip-exp-check-not-allowed`) on any token that is not an OIDC Back-Channel-Logout token (no `http://schemas.openid.net/event/backchannel-logout` event claim), and enforces an `iat` freshness floor on logout tokens (`auth-oauth/logout-token-stale`). Previously any caller could pass `skipExpCheck: true` to verify an expired — or replayed — ID token. The option was undocumented and only used internally by the back-channel-logout path, which is unaffected.",
|
|
300
|
+
migration: [
|
|
301
|
+
"No change for normal ID-token verification or for the framework's back-channel-logout handling. If you called `verifyIdToken(token, { skipExpCheck: true })` directly on a non-logout token (an undocumented use), it now throws: drop the option so expiry is validated, or verify the token through the back-channel-logout path if it really is a logout token.",
|
|
302
|
+
].join("\n"),
|
|
303
|
+
},
|
|
237
304
|
];
|
|
238
305
|
|
|
239
306
|
function _appendOutOfBand(lines) {
|