@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
|
@@ -1833,10 +1833,30 @@ function create(opts) {
|
|
|
1833
1833
|
var now = Math.floor(Date.now() / C.TIME.seconds(1));
|
|
1834
1834
|
var skewSec = Math.floor(clockSkewMs / C.TIME.seconds(1));
|
|
1835
1835
|
// OIDC Back-Channel Logout 1.0 §2.4 — logout tokens have no `exp`
|
|
1836
|
-
// claim; freshness comes from `iat` + jti-replay window.
|
|
1837
|
-
//
|
|
1838
|
-
//
|
|
1839
|
-
|
|
1836
|
+
// claim; freshness comes from `iat` + jti-replay window. `skipExpCheck`
|
|
1837
|
+
// bypasses the exp gate for that path ONLY. It is a public-API option,
|
|
1838
|
+
// so it must be self-guarding: refuse it on any token that is not a
|
|
1839
|
+
// logout token (no back-channel-logout event), or a caller could verify
|
|
1840
|
+
// an expired/replayed id_token clean. Logout tokens then carry no exp,
|
|
1841
|
+
// so `iat` is the only freshness bound — enforce a max-age floor.
|
|
1842
|
+
if (vopts.skipExpCheck) {
|
|
1843
|
+
if (!payload.events || typeof payload.events !== "object" ||
|
|
1844
|
+
!payload.events["http://schemas.openid.net/event/backchannel-logout"]) {
|
|
1845
|
+
throw new OAuthError("auth-oauth/skip-exp-check-not-allowed",
|
|
1846
|
+
"skipExpCheck is only valid for back-channel-logout tokens " +
|
|
1847
|
+
"(OIDC Back-Channel Logout 1.0 §2.4); this token carries no logout event claim");
|
|
1848
|
+
}
|
|
1849
|
+
// Honor the operator's configured replay window. verifyBackchannelLogoutToken
|
|
1850
|
+
// exposes vopts.maxAgeSec (default 5 min) and passes it through here; a
|
|
1851
|
+
// deployment that widened the window must not have this freshness floor
|
|
1852
|
+
// reject a token between the default and its configured max age.
|
|
1853
|
+
var logoutMaxAgeSec = (typeof vopts.maxAgeSec === "number" && isFinite(vopts.maxAgeSec) &&
|
|
1854
|
+
vopts.maxAgeSec > 0) ? vopts.maxAgeSec : DEFAULT_LOGOUT_TOKEN_MAX_AGE_SEC;
|
|
1855
|
+
if (typeof payload.iat !== "number" || payload.iat + logoutMaxAgeSec + skewSec < now) {
|
|
1856
|
+
throw new OAuthError("auth-oauth/logout-token-stale",
|
|
1857
|
+
"logout token iat is older than " + logoutMaxAgeSec + "s (no exp; iat is the freshness bound)");
|
|
1858
|
+
}
|
|
1859
|
+
} else {
|
|
1840
1860
|
if (typeof payload.exp !== "number" || payload.exp + skewSec < now) {
|
|
1841
1861
|
throw new OAuthError("auth-oauth/expired", "ID token expired (exp=" + payload.exp + ", now=" + now + ")");
|
|
1842
1862
|
}
|
|
@@ -1876,6 +1896,19 @@ function create(opts) {
|
|
|
1876
1896
|
throw new OAuthError("auth-oauth/aud-mismatch",
|
|
1877
1897
|
"ID token aud does not contain clientId '" + clientId + "'");
|
|
1878
1898
|
}
|
|
1899
|
+
// OIDC Core §3.1.3.7: a multi-audience ID token MUST carry an azp
|
|
1900
|
+
// (authorized party), and a present azp MUST equal our client_id.
|
|
1901
|
+
// Without this, a token whose authorized party is a DIFFERENT client but
|
|
1902
|
+
// whose aud array also lists this RP would verify clean — a confused-deputy
|
|
1903
|
+
// / token-substitution hole.
|
|
1904
|
+
if (aud.length > 1 && typeof payload.azp !== "string") {
|
|
1905
|
+
throw new OAuthError("auth-oauth/azp-required",
|
|
1906
|
+
"ID token has multiple audiences but no azp (authorized party) claim");
|
|
1907
|
+
}
|
|
1908
|
+
if (payload.azp !== undefined && payload.azp !== clientId) {
|
|
1909
|
+
throw new OAuthError("auth-oauth/azp-mismatch",
|
|
1910
|
+
"ID token azp '" + payload.azp + "' is not clientId '" + clientId + "'");
|
|
1911
|
+
}
|
|
1879
1912
|
if (vopts.nonce && !vopts.skipNonceCheck) {
|
|
1880
1913
|
// Constant-time nonce compare — secret-shaped value matched
|
|
1881
1914
|
// against attacker-controlled payload.
|
|
@@ -2016,13 +2049,19 @@ function create(opts) {
|
|
|
2016
2049
|
if (uopts.prompt) authzParams.prompt = uopts.prompt;
|
|
2017
2050
|
if (uopts.loginHint) authzParams.login_hint = uopts.loginHint;
|
|
2018
2051
|
if (uopts.maxAge != null) authzParams.max_age = String(uopts.maxAge);
|
|
2019
|
-
// RFC 9396 — push the fine-grained authorization request through PAR
|
|
2020
|
-
//
|
|
2052
|
+
// RFC 9396 — push the fine-grained authorization request through PAR.
|
|
2053
|
+
// On the plain-form branch the value is a form parameter (JSON STRING);
|
|
2054
|
+
// on the signed-request-object branch it becomes a JAR claim and MUST
|
|
2055
|
+
// be the native JSON ARRAY (RFC 9101/9396) — a conforming AS rejects a
|
|
2056
|
+
// string-valued authorization_details claim. Carry the validated array
|
|
2057
|
+
// and serialize ONLY when it travels as a form param.
|
|
2021
2058
|
var requestedAuthzDetails = null;
|
|
2022
2059
|
if (uopts.authorizationDetails !== undefined) {
|
|
2023
2060
|
requestedAuthzDetails = _validateAuthorizationDetailsArray(
|
|
2024
2061
|
uopts.authorizationDetails, "pushAuthorizationRequest");
|
|
2025
|
-
authzParams.authorization_details =
|
|
2062
|
+
authzParams.authorization_details = sro
|
|
2063
|
+
? requestedAuthzDetails // JAR claim — native array
|
|
2064
|
+
: JSON.stringify(requestedAuthzDetails); // form param — JSON string
|
|
2026
2065
|
}
|
|
2027
2066
|
if (uopts.extraParams && typeof uopts.extraParams === "object") {
|
|
2028
2067
|
var ek = Object.keys(uopts.extraParams);
|
|
@@ -2160,9 +2199,18 @@ function create(opts) {
|
|
|
2160
2199
|
// store — operators wire b.cache or b.db.
|
|
2161
2200
|
async function verifyBackchannelLogoutToken(logoutToken, vopts) {
|
|
2162
2201
|
vopts = vopts || {};
|
|
2163
|
-
|
|
2202
|
+
// Type / non-empty / length-cap gate, folded into one bounds check.
|
|
2203
|
+
// The cap runs BEFORE the split + base64url decode — an attacker-
|
|
2204
|
+
// reachable endpoint can POST an arbitrarily large logout_token, and
|
|
2205
|
+
// bounding it first stops the decode from allocating unbounded memory.
|
|
2206
|
+
var logoutTokenIsString = typeof logoutToken === "string";
|
|
2207
|
+
if (!logoutTokenIsString || logoutToken.length === 0) {
|
|
2164
2208
|
throw new OAuthError("auth-oauth/bad-logout-token",
|
|
2165
2209
|
"verifyBackchannelLogoutToken: logoutToken must be a non-empty string");
|
|
2210
|
+
} else if (logoutToken.length > OAUTH_MAX_RESPONSE_BYTES) {
|
|
2211
|
+
throw new OAuthError("auth-oauth/logout-token-too-large",
|
|
2212
|
+
"verifyBackchannelLogoutToken: logout_token exceeds " +
|
|
2213
|
+
OAUTH_MAX_RESPONSE_BYTES + " bytes");
|
|
2166
2214
|
}
|
|
2167
2215
|
var parts = logoutToken.split(".");
|
|
2168
2216
|
if (parts.length !== 3) {
|
|
@@ -2170,7 +2218,12 @@ function create(opts) {
|
|
|
2170
2218
|
"verifyBackchannelLogoutToken: logout_token must be a 3-segment JWS");
|
|
2171
2219
|
}
|
|
2172
2220
|
var headerObj;
|
|
2173
|
-
|
|
2221
|
+
// Route the pre-verify header parse through safeJson (size-bounded) like
|
|
2222
|
+
// the in-module id_token / JWS-header siblings — the bare JSON.parse on
|
|
2223
|
+
// an attacker-reachable, not-yet-signature-checked header was the one
|
|
2224
|
+
// unbounded parse on this surface. The JWS signature is verified by
|
|
2225
|
+
// verifyIdToken below.
|
|
2226
|
+
try { headerObj = safeJson.parse(_b64urlDecode(parts[0]).toString("utf8"), { maxBytes: OAUTH_MAX_RESPONSE_BYTES }); }
|
|
2174
2227
|
catch (_e) {
|
|
2175
2228
|
throw new OAuthError("auth-oauth/bad-logout-header",
|
|
2176
2229
|
"verifyBackchannelLogoutToken: malformed header");
|
|
@@ -2192,6 +2245,10 @@ function create(opts) {
|
|
|
2192
2245
|
// Logout tokens have no exp claim per OIDC Back-Channel Logout
|
|
2193
2246
|
// §2.4 — the freshness gate is iat + jti-replay window.
|
|
2194
2247
|
skipExpCheck: true,
|
|
2248
|
+
// Pass the operator's configured replay window through so verifyIdToken's
|
|
2249
|
+
// iat freshness floor uses it, not the 5-min default (the wrapper's own
|
|
2250
|
+
// maxAgeSec check below stays as a belt-and-suspenders bound).
|
|
2251
|
+
maxAgeSec: vopts.maxAgeSec,
|
|
2195
2252
|
});
|
|
2196
2253
|
var claims = verified.claims;
|
|
2197
2254
|
|
|
@@ -334,14 +334,23 @@ function _applyOnePolicy(metadata, policy) {
|
|
|
334
334
|
* @signature b.auth.openidFederation.applyMetadataPolicy(metadata, chain, kind)
|
|
335
335
|
* @since 0.8.62
|
|
336
336
|
*
|
|
337
|
-
* Apply
|
|
337
|
+
* Apply the federation's metadata_policy (top-down) to the leaf's
|
|
338
338
|
* declared metadata for the given entity-kind ("openid_relying_party"
|
|
339
339
|
* / "openid_provider" / "federation_entity" / etc.) and return the
|
|
340
340
|
* effective metadata. Throws on any policy violation.
|
|
341
341
|
*
|
|
342
|
-
*
|
|
343
|
-
* the
|
|
344
|
-
*
|
|
342
|
+
* Per OpenID Federation 1.0 §6.2, an entity's metadata_policy comes
|
|
343
|
+
* from the SUPERIOR-SIGNED subordinate statement about that entity
|
|
344
|
+
* (`chain[i].subordinate.metadata_policy`), NOT from the entity's own
|
|
345
|
+
* self-published configuration. An entity cannot self-declare the
|
|
346
|
+
* policy that constrains it — that would let a leaf widen or drop the
|
|
347
|
+
* trust anchor's value / subset_of / essential constraints. The leaf's
|
|
348
|
+
* own self-config metadata_policy is therefore ignored.
|
|
349
|
+
*
|
|
350
|
+
* The chain is leaf-first; each `chain[i].subordinate` is the statement
|
|
351
|
+
* signed by the superior directly above entity `i`, so walking high
|
|
352
|
+
* index → low index applies the anchor's policy first, then each
|
|
353
|
+
* intermediate's, narrowing down to the leaf (§6.2 narrow-only merge).
|
|
345
354
|
*
|
|
346
355
|
* @example
|
|
347
356
|
* var effective = b.auth.openidFederation.applyMetadataPolicy(
|
|
@@ -361,12 +370,17 @@ function applyMetadataPolicy(metadata, chain, kind) {
|
|
|
361
370
|
"applyMetadataPolicy: chain must be an array");
|
|
362
371
|
}
|
|
363
372
|
var out = Object.assign({}, metadata);
|
|
364
|
-
// Walk top-down (anchor last in leaf-first array).
|
|
373
|
+
// Walk top-down (anchor last in leaf-first array). Read the policy
|
|
374
|
+
// from each node's SUPERIOR-SIGNED subordinate statement — never from
|
|
375
|
+
// the entity's own self-config — so the anchor/intermediate
|
|
376
|
+
// constraints can't be dropped by a self-declared policy. The anchor
|
|
377
|
+
// node carries no `.subordinate` (it terminates the chain) and is
|
|
378
|
+
// skipped; the leaf's self-config policy is never read.
|
|
365
379
|
for (var i = chain.length - 1; i >= 0; i--) {
|
|
366
380
|
var stmt = chain[i];
|
|
367
|
-
if (!stmt || !stmt.
|
|
368
|
-
if (stmt.
|
|
369
|
-
out = _applyOnePolicy(out, stmt.
|
|
381
|
+
if (!stmt || !stmt.subordinate) continue;
|
|
382
|
+
if (stmt.subordinate.metadata_policy && stmt.subordinate.metadata_policy[kind]) {
|
|
383
|
+
out = _applyOnePolicy(out, stmt.subordinate.metadata_policy[kind]);
|
|
370
384
|
}
|
|
371
385
|
}
|
|
372
386
|
return out;
|
|
@@ -433,6 +447,18 @@ async function buildTrustChain(opts) {
|
|
|
433
447
|
};
|
|
434
448
|
var maxDepth = opts.maxDepth || MAX_CHAIN_DEPTH;
|
|
435
449
|
|
|
450
|
+
// ---- Phase 1: collect the chain bottom-up (leaf → anchor) ----------
|
|
451
|
+
// Fetch each entity's self-config + the superior-signed subordinate
|
|
452
|
+
// statement about it, but DEFER cryptographic chain verification to
|
|
453
|
+
// Phase 2. The signature on a subordinate statement must be checked
|
|
454
|
+
// against the keys ATTESTED for the signing authority by ITS superior
|
|
455
|
+
// — flowing down from the operator-pinned anchor — not against the
|
|
456
|
+
// authority's own self-published config jwks. Verifying eagerly here
|
|
457
|
+
// (against self-published keys) is a fetch-time TOCTOU: an attacker
|
|
458
|
+
// controlling the authority's endpoint can serve attacker jwks to the
|
|
459
|
+
// statement-verify fetch while serving genuine config elsewhere, and
|
|
460
|
+
// the only operator-pinned trust (the anchor key) never gates the
|
|
461
|
+
// subordinate links.
|
|
436
462
|
var chain = [];
|
|
437
463
|
var current = opts.leafEntityId;
|
|
438
464
|
var depth = 0;
|
|
@@ -446,6 +472,7 @@ async function buildTrustChain(opts) {
|
|
|
446
472
|
// hostile-federation probes immediately.
|
|
447
473
|
var visited = Object.create(null);
|
|
448
474
|
visited[current] = true;
|
|
475
|
+
var reachedAnchor = false;
|
|
449
476
|
while (depth < maxDepth) {
|
|
450
477
|
var entityConfigUrl = current.replace(/\/$/, "") + "/.well-known/openid-federation";
|
|
451
478
|
var entityConfigJwt = await fetcher(entityConfigUrl);
|
|
@@ -454,21 +481,22 @@ async function buildTrustChain(opts) {
|
|
|
454
481
|
throw new AuthError("auth-openid-federation/bad-self-statement",
|
|
455
482
|
"entity configuration for \"" + current + "\" must have iss==sub==entity_id");
|
|
456
483
|
}
|
|
457
|
-
// Self-
|
|
484
|
+
// Self-statement integrity: a well-formed entity config is self-signed
|
|
485
|
+
// over its own jwks. This proves the document isn't truncated/garbled
|
|
486
|
+
// — it is NOT the trust decision. Trust flows from the anchor through
|
|
487
|
+
// the subordinate statements verified top-down in Phase 2.
|
|
458
488
|
verifyEntityStatement(entityConfigJwt, parsedEC.claims.jwks || {});
|
|
459
489
|
|
|
460
490
|
// Is this entity a trust anchor?
|
|
461
491
|
if (Object.prototype.hasOwnProperty.call(opts.trustAnchors, current)) {
|
|
462
492
|
// Verify the anchor's self-statement using the operator-pinned
|
|
463
493
|
// JWKS — defends against a compromised anchor key by trusting
|
|
464
|
-
// the configured one over what the anchor publishes today.
|
|
494
|
+
// the configured one over what the anchor publishes today. The
|
|
495
|
+
// pinned keys become the root of the top-down verification.
|
|
465
496
|
verifyEntityStatement(entityConfigJwt, opts.trustAnchors[current]);
|
|
466
497
|
chain.push({ jwt: entityConfigJwt, claims: parsedEC.claims, role: "trust_anchor" });
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
});
|
|
470
|
-
_emitMetric("chain-built");
|
|
471
|
-
return chain;
|
|
498
|
+
reachedAnchor = true;
|
|
499
|
+
break;
|
|
472
500
|
}
|
|
473
501
|
// Not the anchor — add to chain, ascend via authority_hints.
|
|
474
502
|
chain.push({
|
|
@@ -481,16 +509,15 @@ async function buildTrustChain(opts) {
|
|
|
481
509
|
throw new AuthError("auth-openid-federation/no-authority-hints",
|
|
482
510
|
"entity \"" + current + "\" has no authority_hints; cannot ascend to a trust anchor");
|
|
483
511
|
}
|
|
484
|
-
// Pick the FIRST authority_hint that
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
// signature-verify failure (cryptographic refusal is a hard stop).
|
|
512
|
+
// Pick the FIRST authority_hint that yields a fetchable subordinate
|
|
513
|
+
// statement with matching iss/sub. We continue past 404 / fetch /
|
|
514
|
+
// parse errors (acceptable "try the next hint") and surface every
|
|
515
|
+
// failure reason on `no-ascent` rather than masking it — silently
|
|
516
|
+
// swallowing `catch (_e) {}` lets a hostile intermediate that serves
|
|
517
|
+
// a malformed-then-valid pair shape-walk the verifier. Cryptographic
|
|
518
|
+
// verification is NOT done here; the selected statement is verified
|
|
519
|
+
// against the superior-attested keys in Phase 2, so a forged
|
|
520
|
+
// signature fails the whole chain regardless of which hint picked it.
|
|
494
521
|
var ascended = false;
|
|
495
522
|
var ascentErrors = [];
|
|
496
523
|
for (var ai = 0; ai < parsedEC.claims.authority_hints.length; ai++) {
|
|
@@ -502,49 +529,83 @@ async function buildTrustChain(opts) {
|
|
|
502
529
|
ascentErrors.push({ authority: authority, code: "iss-sub-mismatch" });
|
|
503
530
|
continue;
|
|
504
531
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
// Cryptographic verification — any throw here is a hard
|
|
508
|
-
// refusal, NOT a "try next authority" signal. A malformed-
|
|
509
|
-
// signature subordinate from an authority listed by the
|
|
510
|
-
// entity means that authority is hostile or compromised;
|
|
511
|
-
// moving on lets a chain-shaping attacker bypass the gate.
|
|
512
|
-
verifyEntityStatement(subordinateJwt, authorityCfgClaims.jwks || {});
|
|
513
|
-
chain[chain.length - 1].claims.jwks = parsedSub.claims.jwks || chain[chain.length - 1].claims.jwks;
|
|
514
|
-
chain[chain.length - 1].subordinateJwt = subordinateJwt;
|
|
515
|
-
chain[chain.length - 1].subordinate = parsedSub.claims;
|
|
516
|
-
// Refuse revisit. A trust anchor terminates the loop
|
|
517
|
-
// before re-entry, so a revisit here ALWAYS means a cyclic
|
|
532
|
+
// Refuse revisit. A trust anchor terminates the loop before
|
|
533
|
+
// re-entry, so a revisit here ALWAYS means a cyclic
|
|
518
534
|
// authority_hints graph.
|
|
519
535
|
if (visited[authority]) {
|
|
520
536
|
throw new AuthError("auth-openid-federation/chain-cycle",
|
|
521
537
|
"buildTrustChain: authority \"" + authority + "\" already visited — " +
|
|
522
538
|
"cyclic authority_hints graph refused");
|
|
523
539
|
}
|
|
540
|
+
// Stash the superior-signed subordinate statement on the entity
|
|
541
|
+
// it is ABOUT. Phase 2 verifies its signature against the
|
|
542
|
+
// attested keys for `authority` and applies its metadata_policy.
|
|
543
|
+
chain[chain.length - 1].subordinateJwt = subordinateJwt;
|
|
544
|
+
chain[chain.length - 1].subordinate = parsedSub.claims;
|
|
524
545
|
visited[authority] = true;
|
|
525
546
|
current = authority;
|
|
526
547
|
ascended = true;
|
|
527
548
|
break;
|
|
528
549
|
} catch (err) {
|
|
550
|
+
// A cycle refusal is a hard stop, not a "try next hint" signal.
|
|
551
|
+
if (err && err.code === "auth-openid-federation/chain-cycle") throw err;
|
|
529
552
|
var errCode = (err && err.code) || "unknown";
|
|
530
|
-
// Network / 404 / parse errors at the AUTHORITY-fetch step
|
|
531
|
-
// are acceptable "try the next hint" signals. Verify-side
|
|
532
|
-
// failures (crypto) are NOT — surface them and abort.
|
|
533
|
-
if (/^auth-openid-federation\/(?:bad-jwk|alg-kty-mismatch|bad-signature|signature-failed)$/.test(errCode)) {
|
|
534
|
-
throw err;
|
|
535
|
-
}
|
|
536
553
|
ascentErrors.push({ authority: authority, code: errCode, message: (err && err.message) || String(err) });
|
|
537
554
|
}
|
|
538
555
|
}
|
|
539
556
|
if (!ascended) {
|
|
540
557
|
throw new AuthError("auth-openid-federation/no-ascent",
|
|
541
|
-
"entity \"" + current + "\" has authority_hints but none yielded a
|
|
558
|
+
"entity \"" + current + "\" has authority_hints but none yielded a fetchable subordinate statement: " +
|
|
542
559
|
JSON.stringify(ascentErrors));
|
|
543
560
|
}
|
|
544
561
|
depth += 1;
|
|
545
562
|
}
|
|
546
|
-
|
|
547
|
-
|
|
563
|
+
if (!reachedAnchor) {
|
|
564
|
+
throw new AuthError("auth-openid-federation/chain-too-deep",
|
|
565
|
+
"buildTrustChain: max depth " + maxDepth + " exceeded; refused");
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// ---- Phase 2: verify top-down against attested keys ----------------
|
|
569
|
+
// Trust flows from the operator-pinned anchor downward. Each
|
|
570
|
+
// subordinate statement is signed by the superior directly above the
|
|
571
|
+
// entity it describes, and pins that entity's jwks. We start with the
|
|
572
|
+
// anchor's pinned keys and, for each step down, verify the subordinate
|
|
573
|
+
// statement with the keys attested for its SIGNER (never the signer's
|
|
574
|
+
// self-published config), then adopt the statement's attested jwks as
|
|
575
|
+
// the trusted keys for the next step. This closes the fetch-time TOCTOU
|
|
576
|
+
// and makes the anchor key gate every link, not just the anchor's own
|
|
577
|
+
// self-config.
|
|
578
|
+
var anchorEntityId = chain[chain.length - 1].claims.iss;
|
|
579
|
+
var attestedJwks = opts.trustAnchors[anchorEntityId];
|
|
580
|
+
for (var ci = chain.length - 2; ci >= 0; ci--) {
|
|
581
|
+
var node = chain[ci];
|
|
582
|
+
if (!node.subordinate || !node.subordinateJwt) {
|
|
583
|
+
// Every non-anchor node must carry the superior-signed statement
|
|
584
|
+
// collected in Phase 1; its absence is an internal invariant break.
|
|
585
|
+
throw new AuthError("auth-openid-federation/no-subordinate",
|
|
586
|
+
"buildTrustChain: entity \"" + node.claims.iss + "\" has no superior-signed subordinate statement");
|
|
587
|
+
}
|
|
588
|
+
// Verify against the SIGNER's attested keys (flowed down), not the
|
|
589
|
+
// signer's self-published config jwks.
|
|
590
|
+
verifyEntityStatement(node.subordinateJwt, attestedJwks || {});
|
|
591
|
+
// The subordinate statement pins this entity's jwks — adopt the
|
|
592
|
+
// attested keys for the next link down, and reflect them on the node.
|
|
593
|
+
if (node.subordinate.jwks && Array.isArray(node.subordinate.jwks.keys)) {
|
|
594
|
+
node.claims.jwks = node.subordinate.jwks;
|
|
595
|
+
attestedJwks = node.subordinate.jwks;
|
|
596
|
+
} else {
|
|
597
|
+
// A subordinate statement that pins no keys cannot attest the next
|
|
598
|
+
// link — refuse rather than fall back to self-published keys.
|
|
599
|
+
throw new AuthError("auth-openid-federation/no-attested-jwks",
|
|
600
|
+
"subordinate statement for \"" + node.claims.iss + "\" pins no jwks; cannot attest the chain downward");
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
_emitAudit("chain_built", "success", {
|
|
605
|
+
leaf: opts.leafEntityId, depth: chain.length, anchor: anchorEntityId,
|
|
606
|
+
});
|
|
607
|
+
_emitMetric("chain-built");
|
|
608
|
+
return chain;
|
|
548
609
|
}
|
|
549
610
|
|
|
550
611
|
/**
|
|
@@ -684,10 +684,11 @@ function create(opts) {
|
|
|
684
684
|
// as Bearer (Profile §3.1 incorporates §3 by reference).
|
|
685
685
|
var nbHok = _attr(scdHok, "NotBefore");
|
|
686
686
|
var noaHok = _attr(scdHok, "NotOnOrAfter");
|
|
687
|
+
if (!noaHok) continue; // §3.1 (incorporates §3) — time-bound required
|
|
688
|
+
var noaHokSec = Date.parse(noaHok) / 1000; // ms→s
|
|
689
|
+
if (!isFinite(noaHokSec) || noaHokSec < nowSec - clockSkewSec) continue; // unparseable or expired
|
|
687
690
|
if (nbHok && isFinite(Date.parse(nbHok) / 1000) && // ms→s
|
|
688
691
|
Date.parse(nbHok) / 1000 > nowSec + clockSkewSec) continue; // ms→s
|
|
689
|
-
if (noaHok && isFinite(Date.parse(noaHok) / 1000) && // ms→s
|
|
690
|
-
Date.parse(noaHok) / 1000 < nowSec - clockSkewSec) continue; // ms→s
|
|
691
692
|
var recipHok = _attr(scdHok, "Recipient");
|
|
692
693
|
if (recipHok && recipHok !== opts.assertionConsumerServiceUrl) continue;
|
|
693
694
|
hokOk = true;
|
|
@@ -697,12 +698,9 @@ function create(opts) {
|
|
|
697
698
|
var scd = _findChild(sc, "SubjectConfirmationData", SAML_NS.assertion);
|
|
698
699
|
if (!scd) continue;
|
|
699
700
|
var notOnOrAfter = _attr(scd, "NotOnOrAfter");
|
|
700
|
-
if (notOnOrAfter)
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
continue; // expired confirmation — try next
|
|
704
|
-
}
|
|
705
|
-
}
|
|
701
|
+
if (!notOnOrAfter) continue; // §4.1.4.2 — Bearer requires NotOnOrAfter (no unbounded freshness)
|
|
702
|
+
var t = Date.parse(notOnOrAfter) / 1000; // ms→s
|
|
703
|
+
if (!isFinite(t) || t < nowSec - clockSkewSec) continue; // unparseable or expired confirmation — try next
|
|
706
704
|
var notBefore = _attr(scd, "NotBefore");
|
|
707
705
|
if (notBefore) {
|
|
708
706
|
var nb = Date.parse(notBefore) / 1000; // ms→s
|
|
@@ -126,12 +126,25 @@ function _hashDisclosure(disclosureStr, hashAlg) {
|
|
|
126
126
|
return h.digest().toString("base64url");
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// JOSE/JWS — and EUDI wallets — require ECDSA signatures as raw r||s
|
|
130
|
+
// ("ieee-p1363"); node:crypto defaults to DER (ASN.1 SEQUENCE). Wrap the EC
|
|
131
|
+
// key so ES256/ES384 sign + verify emit/expect the JOSE encoding. Without it
|
|
132
|
+
// a token this library signs is rejected by every conformant verifier (and
|
|
133
|
+
// the library rejects their raw-r||s KB-JWTs). EdDSA / ML-DSA keys pass
|
|
134
|
+
// through unchanged. Mirrors oauth.js / dpop.js / jwt-external.js.
|
|
135
|
+
function _ecKeyParam(algorithm, key) {
|
|
136
|
+
if (algorithm === "ES256" || algorithm === "ES384") {
|
|
137
|
+
return { key: key, dsaEncoding: "ieee-p1363" };
|
|
138
|
+
}
|
|
139
|
+
return key;
|
|
140
|
+
}
|
|
141
|
+
|
|
129
142
|
function _signJwt(header, payload, privateKey, algorithm) {
|
|
130
143
|
var headerStr = _b64uEncode(safeJson.stringify(header));
|
|
131
144
|
var payloadStr = _b64uEncode(safeJson.stringify(payload));
|
|
132
145
|
var signingInput = headerStr + "." + payloadStr;
|
|
133
146
|
var sigAlgo = _resolveSigAlgo(algorithm);
|
|
134
|
-
var sig = nodeCrypto.sign(sigAlgo, Buffer.from(signingInput, "ascii"), privateKey);
|
|
147
|
+
var sig = nodeCrypto.sign(sigAlgo, Buffer.from(signingInput, "ascii"), _ecKeyParam(algorithm, privateKey));
|
|
135
148
|
return signingInput + "." + sig.toString("base64url");
|
|
136
149
|
}
|
|
137
150
|
|
|
@@ -144,7 +157,7 @@ function _verifyJwt(token, publicKey, algorithm) {
|
|
|
144
157
|
var signingInput = parts[0] + "." + parts[1];
|
|
145
158
|
var sig = _b64uDecodeBuf(parts[2]);
|
|
146
159
|
var sigAlgo = _resolveSigAlgo(algorithm);
|
|
147
|
-
var ok = nodeCrypto.verify(sigAlgo, Buffer.from(signingInput, "ascii"), publicKey, sig);
|
|
160
|
+
var ok = nodeCrypto.verify(sigAlgo, Buffer.from(signingInput, "ascii"), _ecKeyParam(algorithm, publicKey), sig);
|
|
148
161
|
if (!ok) {
|
|
149
162
|
throw new AuthError("auth-sd-jwt-vc/bad-signature",
|
|
150
163
|
"JWT signature verification failed");
|
|
@@ -482,9 +495,13 @@ async function verify(presentation, opts) {
|
|
|
482
495
|
"verify: issuerKeyResolver returned no key");
|
|
483
496
|
}
|
|
484
497
|
// CVE-2026-22817 — when issuerKeyResolver returns a JWK object,
|
|
485
|
-
// cross-check alg/kty BEFORE handing it to
|
|
486
|
-
// KeyObject / PEM shapes can't surface kty so
|
|
487
|
-
// JWK
|
|
498
|
+
// cross-check the issuer JWS alg/kty BEFORE handing it to
|
|
499
|
+
// node:crypto.verify. KeyObject / PEM shapes can't surface kty, so this
|
|
500
|
+
// guard only fires when the resolver hands back a JWK (the common path).
|
|
501
|
+
// The holder KB-JWT path applies its OWN _assertAlgKtyMatch against the
|
|
502
|
+
// cnf.jwk below — note that the holder key is issuer-ATTESTED (it comes
|
|
503
|
+
// from the cryptographically-verified issuer payload's cnf claim), not
|
|
504
|
+
// header-resolved, so the two cross-checks defend different trust edges.
|
|
488
505
|
if (typeof issuerKey === "object" &&
|
|
489
506
|
!(issuerKey instanceof nodeCrypto.KeyObject) &&
|
|
490
507
|
!Buffer.isBuffer(issuerKey) &&
|
|
@@ -610,13 +627,25 @@ async function verify(presentation, opts) {
|
|
|
610
627
|
throw new AuthError("auth-sd-jwt-vc/unsupported-alg",
|
|
611
628
|
"verify: KB-JWT alg unsupported");
|
|
612
629
|
}
|
|
630
|
+
// CVE-2026-22817 — cross-check the KB-JWT header alg against the holder
|
|
631
|
+
// key type BEFORE importing the key / verifying. The issuer path does
|
|
632
|
+
// this for issuerKey (above); the holder KB-JWT path must too. The
|
|
633
|
+
// KB-JWT header alg is attacker-controllable (the holder mints the
|
|
634
|
+
// KB-JWT), and holderKey is a cnf.jwk with a kty, so an alg/kty
|
|
635
|
+
// mismatch (e.g. a header claiming EdDSA against an EC cnf key) is
|
|
636
|
+
// refused with the precise alg-mismatch error rather than handed to
|
|
637
|
+
// node:crypto.verify.
|
|
638
|
+
jwtExternal._assertAlgKtyMatch(kbAlg, holderKey);
|
|
613
639
|
var holderKeyObj = nodeCrypto.createPublicKey({ key: holderKey, format: "jwk" });
|
|
614
640
|
var kbParsed = _verifyJwt(maybeKbJwt, holderKeyObj, kbAlg);
|
|
615
|
-
|
|
641
|
+
// Constant-time compares: the nonce is a verifier-issued replay-defense
|
|
642
|
+
// value, so a short-circuiting !== leaks a matching-prefix timing oracle.
|
|
643
|
+
// Matches the sd_hash check below (the framework's hash/token discipline).
|
|
644
|
+
if (opts.audience && !_timingSafeEqStr(kbParsed.payload.aud, opts.audience)) {
|
|
616
645
|
throw new AuthError("auth-sd-jwt-vc/wrong-audience",
|
|
617
646
|
"verify: KB-JWT aud mismatch");
|
|
618
647
|
}
|
|
619
|
-
if (opts.nonce && kbParsed.payload.nonce
|
|
648
|
+
if (opts.nonce && !_timingSafeEqStr(kbParsed.payload.nonce, opts.nonce)) {
|
|
620
649
|
throw new AuthError("auth-sd-jwt-vc/wrong-nonce",
|
|
621
650
|
"verify: KB-JWT nonce mismatch (replay defense)");
|
|
622
651
|
}
|
|
@@ -54,6 +54,7 @@ var nodePath = require("node:path");
|
|
|
54
54
|
var bCrypto = require("../crypto");
|
|
55
55
|
var atomicFile = require("../atomic-file");
|
|
56
56
|
var backupBundle = require("./bundle");
|
|
57
|
+
var frameworkFiles = require("../framework-files");
|
|
57
58
|
var backupManifest = require("./manifest");
|
|
58
59
|
var lazyRequire = require("../lazy-require");
|
|
59
60
|
var validateOpts = require("../validate-opts");
|
|
@@ -65,6 +66,7 @@ var compliance = lazyRequire(function () { return require("../compliance"); });
|
|
|
65
66
|
// module graph (CLI tools, stand-alone backup runners). The db()
|
|
66
67
|
// callable resolves on first access.
|
|
67
68
|
var dbModuleLazy = lazyRequire(function () { return require("../db"); });
|
|
69
|
+
var cryptoField = lazyRequire(function () { return require("../crypto-field"); });
|
|
68
70
|
var archiveLazy = lazyRequire(function () { return require("../archive"); });
|
|
69
71
|
var archiveAdaptersLazy = lazyRequire(function () { return require("../archive-adapters"); });
|
|
70
72
|
var { defineClass } = require("../framework-error");
|
|
@@ -416,6 +418,37 @@ function create(opts) {
|
|
|
416
418
|
});
|
|
417
419
|
} catch (_e) { /* drop-silent */ }
|
|
418
420
|
}
|
|
421
|
+
// Per-row residency blind spot: the deployment-level check above only
|
|
422
|
+
// compares the single DB region to the destination. A per-row-residency
|
|
423
|
+
// table is DECLARED (cryptoField.declarePerRowResidency) to admit rows in
|
|
424
|
+
// several regions; rows tagged to a region other than the backup
|
|
425
|
+
// destination are a per-row cross-border transfer the deployment compare
|
|
426
|
+
// cannot see. Surface the declared cross-border regions (policy-based —
|
|
427
|
+
// no row scan) so the bundle's residency exposure is visible.
|
|
428
|
+
if (backupResidencyTag) {
|
|
429
|
+
try {
|
|
430
|
+
var perRowTables = cryptoField().listPerRowResidency();
|
|
431
|
+
var perRowCrossBorder = [];
|
|
432
|
+
for (var pri = 0; pri < perRowTables.length; pri++) {
|
|
433
|
+
var prt = perRowTables[pri];
|
|
434
|
+
var offending = (prt.allowedTags || []).filter(function (tag) {
|
|
435
|
+
return tag !== "global" && tag !== "unrestricted" && tag !== backupResidencyTag;
|
|
436
|
+
});
|
|
437
|
+
if (offending.length) perRowCrossBorder.push({ table: prt.table, regions: offending });
|
|
438
|
+
}
|
|
439
|
+
if (perRowCrossBorder.length) {
|
|
440
|
+
audit().safeEmit({
|
|
441
|
+
action: "backup.residency.per_row_cross_border",
|
|
442
|
+
outcome: "success",
|
|
443
|
+
metadata: {
|
|
444
|
+
severity: "warning", scope: "per-row", posture: posture,
|
|
445
|
+
destination: backupResidencyTag, tables: perRowCrossBorder,
|
|
446
|
+
recommendation: "a per-row-residency table admits rows in regions other than the backup destination; confirm the cross-border transfer is permitted (allowCrossBorder + documented legalBasis) or restrict the destination region",
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
} catch (_e) { /* drop-silent — advisory only */ }
|
|
451
|
+
}
|
|
419
452
|
}
|
|
420
453
|
|
|
421
454
|
var dataDir = opts.dataDir;
|
|
@@ -887,21 +920,23 @@ function recommendedFiles(opts) {
|
|
|
887
920
|
var files = [];
|
|
888
921
|
|
|
889
922
|
if (atRest === "encrypted") {
|
|
890
|
-
files.push({ relativePath:
|
|
891
|
-
files.push({ relativePath:
|
|
923
|
+
files.push({ relativePath: frameworkFiles.fileName("dbEnc"), kind: "raw", required: true });
|
|
924
|
+
files.push({ relativePath: frameworkFiles.fileName("dbKeyEnc"), kind: "raw", required: true });
|
|
892
925
|
} else {
|
|
893
926
|
files.push({ relativePath: dbName, kind: "raw", required: true });
|
|
894
927
|
}
|
|
895
928
|
|
|
896
929
|
if (vaultMode === "wrapped") {
|
|
897
|
-
files.push({ relativePath: "
|
|
930
|
+
files.push({ relativePath: frameworkFiles.fileName("vaultKey") + ".sealed", kind: "raw", required: true });
|
|
898
931
|
} else {
|
|
899
|
-
files.push({ relativePath:
|
|
932
|
+
files.push({ relativePath: frameworkFiles.fileName("vaultKey"), kind: "raw", required: true });
|
|
900
933
|
}
|
|
901
934
|
|
|
902
935
|
// Audit-signing key (always present; sealed in wrapped mode)
|
|
903
936
|
files.push({
|
|
904
|
-
relativePath: vaultMode === "wrapped"
|
|
937
|
+
relativePath: vaultMode === "wrapped"
|
|
938
|
+
? frameworkFiles.fileName("auditSignKey") + ".sealed"
|
|
939
|
+
: frameworkFiles.fileName("auditSignKey"),
|
|
905
940
|
kind: "raw", required: false,
|
|
906
941
|
});
|
|
907
942
|
|
|
@@ -2368,7 +2403,7 @@ bundleAdapterStorage.objectStoreAdapter = function (client, osOpts) {
|
|
|
2368
2403
|
// b.objectStore surfaces NOT_FOUND via the framework's
|
|
2369
2404
|
// err.code === "NOT_FOUND" convention — translate to the
|
|
2370
2405
|
// backup adapter contract's no-key error.
|
|
2371
|
-
if (e && (e.code === "NOT_FOUND" || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2406
|
+
if (e && (e.code === "NOT_FOUND" || e.statusCode === 404 || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2372
2407
|
throw new BackupError("backup/no-key",
|
|
2373
2408
|
"objectStoreAdapter: key not found: " + JSON.stringify(key));
|
|
2374
2409
|
}
|
|
@@ -2425,7 +2460,7 @@ bundleAdapterStorage.objectStoreAdapter = function (client, osOpts) {
|
|
|
2425
2460
|
} catch (e) {
|
|
2426
2461
|
// drop-silent on NOT_FOUND — adapter contract is idempotent
|
|
2427
2462
|
// delete (fsAdapter same shape).
|
|
2428
|
-
if (e && (e.code === "NOT_FOUND" || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2463
|
+
if (e && (e.code === "NOT_FOUND" || e.statusCode === 404 || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2429
2464
|
return;
|
|
2430
2465
|
}
|
|
2431
2466
|
throw e;
|
|
@@ -2436,7 +2471,7 @@ bundleAdapterStorage.objectStoreAdapter = function (client, osOpts) {
|
|
|
2436
2471
|
await client.head(_scopedKey(key));
|
|
2437
2472
|
return true;
|
|
2438
2473
|
} catch (e) {
|
|
2439
|
-
if (e && (e.code === "NOT_FOUND" || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2474
|
+
if (e && (e.code === "NOT_FOUND" || e.statusCode === 404 || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2440
2475
|
return false;
|
|
2441
2476
|
}
|
|
2442
2477
|
throw e;
|
|
@@ -2453,7 +2488,7 @@ bundleAdapterStorage.objectStoreAdapter = function (client, osOpts) {
|
|
|
2453
2488
|
var buf = Buffer.isBuffer(body) ? body : Buffer.from(body);
|
|
2454
2489
|
return buf.slice(0, length);
|
|
2455
2490
|
} catch (e) {
|
|
2456
|
-
if (e && (e.code === "NOT_FOUND" || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2491
|
+
if (e && (e.code === "NOT_FOUND" || e.statusCode === 404 || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2457
2492
|
throw new BackupError("backup/no-key",
|
|
2458
2493
|
"objectStoreAdapter.readPartial: key not found: " + JSON.stringify(key));
|
|
2459
2494
|
}
|
|
@@ -2466,7 +2501,7 @@ bundleAdapterStorage.objectStoreAdapter = function (client, osOpts) {
|
|
|
2466
2501
|
if (!meta || typeof meta.size !== "number") return null;
|
|
2467
2502
|
return { size: meta.size, mtimeMs: meta.lastModified || null };
|
|
2468
2503
|
} catch (e) {
|
|
2469
|
-
if (e && (e.code === "NOT_FOUND" || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2504
|
+
if (e && (e.code === "NOT_FOUND" || e.statusCode === 404 || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2470
2505
|
return null;
|
|
2471
2506
|
}
|
|
2472
2507
|
throw e;
|