@blamejs/blamejs-shop 0.4.30 → 0.4.32
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/lib/asset-manifest.json +1 -1
- package/lib/checkout.js +8 -0
- package/lib/order.js +71 -11
- package/lib/vendor/MANIFEST.json +392 -278
- 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 +26 -0
- package/lib/vendor/blamejs/MIGRATING.md +43 -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/oauth.js +53 -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 +31 -5
- 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 +206 -4
- package/lib/vendor/blamejs/lib/consent.js +82 -29
- package/lib/vendor/blamejs/lib/constants.js +27 -11
- 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 +101 -40
- 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 +35 -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 +151 -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/scripts/check-services.js +21 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +51 -0
- package/lib/vendor/blamejs/scripts/release.js +398 -38
- package/lib/vendor/blamejs/test/00-primitives.js +117 -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 +1120 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -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/router-use-path-scope.test.js +255 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +309 -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/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 +1 -1
- 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
|
@@ -43,6 +43,7 @@ var cluster = require("./cluster");
|
|
|
43
43
|
var cryptoField = require("./crypto-field");
|
|
44
44
|
var requestHelpers = require("./request-helpers");
|
|
45
45
|
var validateOpts = require("./validate-opts");
|
|
46
|
+
var sql = require("./sql");
|
|
46
47
|
var C = require("./constants");
|
|
47
48
|
var numericChecks = require("./numeric-checks");
|
|
48
49
|
var { ApiKeyError } = require("./framework-error");
|
|
@@ -53,16 +54,28 @@ function _emitEvent(n, v, l) { observability().safeEvent(n, v, l || {}); }
|
|
|
53
54
|
|
|
54
55
|
var _err = ApiKeyError.factory;
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// the
|
|
60
|
-
//
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
57
|
+
// Logical framework table name. Self-mapped in LOCAL_TO_EXTERNAL, so it is
|
|
58
|
+
// passed BARE to b.sql: clusterStorage.execute rewrites it to the configured
|
|
59
|
+
// prefix and placeholderizes the `?` markers, so one query text runs against
|
|
60
|
+
// the local SQLite single-node backend and the operator's external DB in
|
|
61
|
+
// cluster mode.
|
|
62
|
+
var TABLE = "_blamejs_api_keys"; // allow:hand-rolled-sql — bare logical name, passed to b.sql for clusterStorage rewrite
|
|
63
|
+
|
|
64
|
+
// b.sql opts for every _blamejs_api_keys statement: thread the ACTIVE backend
|
|
65
|
+
// dialect (clusterStorage.dialect() — "sqlite" single-node, "postgres" |
|
|
66
|
+
// "mysql" in cluster mode) so the emitted identifier quoting + dialect idioms
|
|
67
|
+
// match the backend the SQL dispatches to. Defaulting to "sqlite" works on
|
|
68
|
+
// Postgres only by accident (both double-quote identifiers) and emits the
|
|
69
|
+
// wrong quoting on MySQL, so this is the canonical resolver threaded into
|
|
70
|
+
// b.sql. clusterStorage.execute still rewrites the bare table name +
|
|
71
|
+
// translates `?` placeholders at dispatch; this controls only the builder-
|
|
72
|
+
// side quoting + idiom selection. The table name stays BARE (no quoteName)
|
|
73
|
+
// so clusterStorage's prefix rewrite still fires.
|
|
74
|
+
function _sqlOpts() { return { dialect: clusterStorage.dialect() }; }
|
|
75
|
+
|
|
76
|
+
// Column order used for INSERT — kept as a constant so the column list and
|
|
77
|
+
// the row object stay in sync. Must match _blamejs_api_keys' schema in
|
|
78
|
+
// db.js (single-node) and framework-schema.js (cluster mode).
|
|
66
79
|
var COLS = [
|
|
67
80
|
"id", "namespace", "ownerId", "ownerIdHash", "secretHash",
|
|
68
81
|
"secondarySecretHash", "secondaryExpiresAt",
|
|
@@ -305,10 +318,11 @@ function create(opts) {
|
|
|
305
318
|
);
|
|
306
319
|
}
|
|
307
320
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
321
|
+
// Fresh SELECT builder over the full column set. BARE logical table name
|
|
322
|
+
// (_blamejs_api_keys) — clusterStorage rewrites it to the configured
|
|
323
|
+
// prefix and placeholderizes. Callers chain the WHERE family + .toSql().
|
|
324
|
+
function _selectBuilder() {
|
|
325
|
+
return sql.select(TABLE, _sqlOpts()).columns(COLS); // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
|
|
312
326
|
}
|
|
313
327
|
|
|
314
328
|
function _scrubRecord(row) {
|
|
@@ -369,14 +383,13 @@ function create(opts) {
|
|
|
369
383
|
lastUsedAt: null,
|
|
370
384
|
prefix: prefix,
|
|
371
385
|
});
|
|
372
|
-
var
|
|
373
|
-
var
|
|
374
|
-
var
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
);
|
|
386
|
+
var insertRow = {};
|
|
387
|
+
for (var ci = 0; ci < COLS.length; ci++) insertRow[COLS[ci]] = sealed[COLS[ci]];
|
|
388
|
+
var insertBuilt = sql.insert(TABLE, _sqlOpts()) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
|
|
389
|
+
.columns(COLS)
|
|
390
|
+
.values(insertRow)
|
|
391
|
+
.toSql();
|
|
392
|
+
await clusterStorage.execute(insertBuilt.sql, insertBuilt.params);
|
|
380
393
|
|
|
381
394
|
_emit("apikey.issue", {
|
|
382
395
|
actor: _actor(issueOpts, issueOpts.ownerId),
|
|
@@ -402,10 +415,8 @@ function create(opts) {
|
|
|
402
415
|
if (parsed.prefix !== prefix || parsed.namespace !== namespace) return null;
|
|
403
416
|
|
|
404
417
|
var compositeId = _composedId(namespace, parsed.idHex);
|
|
405
|
-
var
|
|
406
|
-
|
|
407
|
-
[compositeId]
|
|
408
|
-
);
|
|
418
|
+
var verifyBuilt = _selectBuilder().where("id", compositeId).toSql();
|
|
419
|
+
var row = await clusterStorage.executeOne(verifyBuilt.sql, verifyBuilt.params);
|
|
409
420
|
if (!row) {
|
|
410
421
|
if (auditFailures) {
|
|
411
422
|
_emit("apikey.verify", {
|
|
@@ -472,13 +483,55 @@ function create(opts) {
|
|
|
472
483
|
return null;
|
|
473
484
|
}
|
|
474
485
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
486
|
+
// Leader-gated best-effort writes on a successful verify: bump
|
|
487
|
+
// lastUsedAt when tracked, and transparently re-hash the stored secret
|
|
488
|
+
// when its envelope no longer matches the active algorithm — the
|
|
489
|
+
// rotate-on-next-verify that credentialHash documents but, until now,
|
|
490
|
+
// no consumer wired. Primary match only: the secondary (graceful-
|
|
491
|
+
// rotation) slot is not the active secret, so it must not overwrite
|
|
492
|
+
// secretHash. The whole block is best-effort — the credential already
|
|
493
|
+
// verified under the stored hash and stays valid even if the write
|
|
494
|
+
// fails; the row re-upgrades on the next leader verify.
|
|
495
|
+
if (cluster.isLeader()) {
|
|
496
|
+
var touchFields = trackLastUsedAt ? { lastUsedAt: nowMs } : null;
|
|
497
|
+
var didRehash = false;
|
|
498
|
+
if (primaryMatch && credentialHash.needsRehash(row.secretHash, { algo: hashAlgo })) {
|
|
499
|
+
try {
|
|
500
|
+
var freshSecretHash = await credentialHash.hash(parsed.secretHex, { algo: hashAlgo });
|
|
501
|
+
touchFields = touchFields || {};
|
|
502
|
+
touchFields.secretHash = freshSecretHash;
|
|
503
|
+
didRehash = true;
|
|
504
|
+
} catch (_e) { /* re-hash is best-effort; verify success stands */ }
|
|
505
|
+
}
|
|
506
|
+
if (touchFields) {
|
|
507
|
+
try {
|
|
508
|
+
var touchQb = sql.update(TABLE, _sqlOpts()) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
|
|
509
|
+
.set(touchFields)
|
|
510
|
+
.where("id", compositeId);
|
|
511
|
+
if (didRehash) {
|
|
512
|
+
// Compare-and-swap on the exact hash we verified against: only land
|
|
513
|
+
// the re-hash if the stored primary is STILL that value. A verify
|
|
514
|
+
// that races rotate()/hardRotate (which already installed a new
|
|
515
|
+
// secretHash) must not clobber the rotated secret back to the old
|
|
516
|
+
// one — the predicate then matches no rows and the upgrade no-ops,
|
|
517
|
+
// which is correct because the row is already on a fresh hash.
|
|
518
|
+
touchQb.where("secretHash", row.secretHash);
|
|
519
|
+
}
|
|
520
|
+
var touchBuilt = touchQb.toSql();
|
|
521
|
+
var touchResult = await clusterStorage.execute(touchBuilt.sql, touchBuilt.params);
|
|
522
|
+
// Only record the migration when the CAS actually swapped a row (a
|
|
523
|
+
// rowCount of 0 means a concurrent rotation won the race).
|
|
524
|
+
if (didRehash && !(touchResult && touchResult.rowCount === 0)) {
|
|
525
|
+
_emitEvent("apikey.secret_rehash", 1, { namespace: namespace, algo: hashAlgo });
|
|
526
|
+
_emit("apikey.secret_rehash", {
|
|
527
|
+
actor: _actor(verifyOpts, rowOwnerId),
|
|
528
|
+
resource: { kind: "apikey", id: compositeId },
|
|
529
|
+
outcome: "success",
|
|
530
|
+
metadata: { algo: hashAlgo },
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
} catch (_e) { /* best-effort; verify success not blocked by the write */ }
|
|
534
|
+
}
|
|
482
535
|
}
|
|
483
536
|
|
|
484
537
|
if (auditSuccess) {
|
|
@@ -501,10 +554,12 @@ function create(opts) {
|
|
|
501
554
|
if (typeof idHex !== "string" || idHex.length === 0) return false;
|
|
502
555
|
var compositeId = _composedId(namespace, idHex);
|
|
503
556
|
var nowMs = clock();
|
|
504
|
-
var
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
557
|
+
var revokeBuilt = sql.update(TABLE, _sqlOpts()) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
|
|
558
|
+
.set({ revokedAt: nowMs })
|
|
559
|
+
.where("id", compositeId)
|
|
560
|
+
.whereNull("revokedAt")
|
|
561
|
+
.toSql();
|
|
562
|
+
var result = await clusterStorage.execute(revokeBuilt.sql, revokeBuilt.params);
|
|
508
563
|
var changed = (result.rowCount || 0) > 0;
|
|
509
564
|
if (changed) {
|
|
510
565
|
_emit("apikey.revoke", {
|
|
@@ -542,10 +597,8 @@ function create(opts) {
|
|
|
542
597
|
}
|
|
543
598
|
|
|
544
599
|
var compositeId = _composedId(namespace, idHex);
|
|
545
|
-
var
|
|
546
|
-
|
|
547
|
-
[compositeId]
|
|
548
|
-
);
|
|
600
|
+
var rotateSelBuilt = _selectBuilder().where("id", compositeId).toSql();
|
|
601
|
+
var existing = await clusterStorage.executeOne(rotateSelBuilt.sql, rotateSelBuilt.params);
|
|
549
602
|
if (!existing) {
|
|
550
603
|
throw _err("NOT_FOUND", "apiKey.rotate: id '" + idHex + "' not found in namespace '" + namespace + "'");
|
|
551
604
|
}
|
|
@@ -558,19 +611,27 @@ function create(opts) {
|
|
|
558
611
|
|
|
559
612
|
if (gracePeriodMs > 0) {
|
|
560
613
|
// Move current hash → secondary slot, install new hash as primary.
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
614
|
+
var graceBuilt = sql.update(TABLE, _sqlOpts()) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
|
|
615
|
+
.set({
|
|
616
|
+
secretHash: newHash,
|
|
617
|
+
secondarySecretHash: existing.secretHash,
|
|
618
|
+
secondaryExpiresAt: nowMs + gracePeriodMs,
|
|
619
|
+
})
|
|
620
|
+
.where("id", compositeId)
|
|
621
|
+
.toSql();
|
|
622
|
+
await clusterStorage.execute(graceBuilt.sql, graceBuilt.params);
|
|
566
623
|
} else {
|
|
567
624
|
// Hard cutover — old secret stops working immediately. Clears
|
|
568
|
-
// any prior secondary slot too.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
625
|
+
// any prior secondary slot too (bound NULL via the set map).
|
|
626
|
+
var cutoverBuilt = sql.update(TABLE, _sqlOpts()) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
|
|
627
|
+
.set({
|
|
628
|
+
secretHash: newHash,
|
|
629
|
+
secondarySecretHash: null,
|
|
630
|
+
secondaryExpiresAt: null,
|
|
631
|
+
})
|
|
632
|
+
.where("id", compositeId)
|
|
633
|
+
.toSql();
|
|
634
|
+
await clusterStorage.execute(cutoverBuilt.sql, cutoverBuilt.params);
|
|
574
635
|
}
|
|
575
636
|
|
|
576
637
|
_emit("apikey.rotate", {
|
|
@@ -598,17 +659,30 @@ function create(opts) {
|
|
|
598
659
|
var lookup = cryptoField.lookupHash(TABLE, "ownerId", ownerId);
|
|
599
660
|
if (!lookup) {
|
|
600
661
|
throw _err("MISCONFIGURED",
|
|
601
|
-
"
|
|
662
|
+
TABLE + " schema is missing the ownerIdHash derived hash — framework misconfigured");
|
|
602
663
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
664
|
+
// Dual-read across the keyed-MAC flip: match the active digest AND the
|
|
665
|
+
// legacy salted-sha3 digest a pre-v0.15.0 row carries (whereIn with a
|
|
666
|
+
// single value emits `IN (?)`, equivalent to `=`).
|
|
667
|
+
var ownerHashes = [lookup.value];
|
|
668
|
+
if (lookup.legacyValue != null && lookup.legacyValue !== lookup.value) {
|
|
669
|
+
ownerHashes.push(lookup.legacyValue);
|
|
670
|
+
}
|
|
671
|
+
var listQb = _selectBuilder()
|
|
672
|
+
.where("namespace", namespace)
|
|
673
|
+
.whereIn("ownerIdHash", ownerHashes);
|
|
674
|
+
if (!includeRevoked) listQb.whereNull("revokedAt");
|
|
606
675
|
if (!includeExpired) {
|
|
607
|
-
|
|
608
|
-
|
|
676
|
+
var nowForExpiry = clock();
|
|
677
|
+
// (expiresAt IS NULL OR expiresAt >= now) — an OR group ANDed onto
|
|
678
|
+
// the chain so the optional clause keeps its own precedence.
|
|
679
|
+
listQb.whereGroup(function (g) {
|
|
680
|
+
g.whereNull("expiresAt").orWhereOp("expiresAt", ">=", nowForExpiry);
|
|
681
|
+
});
|
|
609
682
|
}
|
|
610
|
-
|
|
611
|
-
var
|
|
683
|
+
listQb.orderBy("createdAt", "desc");
|
|
684
|
+
var listBuilt = listQb.toSql();
|
|
685
|
+
var rows = await clusterStorage.execute(listBuilt.sql, listBuilt.params);
|
|
612
686
|
var list = (rows.rows || []).map(_scrubRecord);
|
|
613
687
|
_emitEvent("apikey.list", 1, { namespace: namespace, count: list.length });
|
|
614
688
|
// Read-access audit: "who listed whose keys at time T" — gated by
|
|
@@ -635,10 +709,8 @@ function create(opts) {
|
|
|
635
709
|
async function getById(idHex, getOpts) {
|
|
636
710
|
if (typeof idHex !== "string" || idHex.length === 0) return null;
|
|
637
711
|
var compositeId = _composedId(namespace, idHex);
|
|
638
|
-
var
|
|
639
|
-
|
|
640
|
-
[compositeId]
|
|
641
|
-
);
|
|
712
|
+
var getBuilt = _selectBuilder().where("id", compositeId).toSql();
|
|
713
|
+
var row = await clusterStorage.executeOne(getBuilt.sql, getBuilt.params);
|
|
642
714
|
var record = _scrubRecord(row);
|
|
643
715
|
_emitEvent("apikey.get", 1,
|
|
644
716
|
{ namespace: namespace, found: record !== null });
|
|
@@ -659,13 +731,24 @@ function create(opts) {
|
|
|
659
731
|
// Compliance auditors expect "key X was purged at time T" — a count-
|
|
660
732
|
// only audit is too coarse for forensic reconstruction. Cost is one
|
|
661
733
|
// extra round-trip per purge call which runs on a schedule (not
|
|
662
|
-
// request-rate), so the cost is irrelevant.
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
734
|
+
// request-rate), so the cost is irrelevant. The purge predicate
|
|
735
|
+
// (namespace match + an OR of the two "past-threshold" age groups) is
|
|
736
|
+
// applied identically to the SELECT and the DELETE via _applyPurgeWhere.
|
|
737
|
+
function _applyPurgeWhere(qb) {
|
|
738
|
+
return qb
|
|
739
|
+
.where("namespace", namespace)
|
|
740
|
+
.whereGroup(function (g) {
|
|
741
|
+
g.whereGroup(function (a) {
|
|
742
|
+
a.whereNotNull("revokedAt").where("revokedAt", "<", threshold);
|
|
743
|
+
}).orWhereGroup(function (b2) {
|
|
744
|
+
b2.whereNotNull("expiresAt").where("expiresAt", "<", threshold);
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
var purgeSelBuilt = _applyPurgeWhere(
|
|
749
|
+
sql.select(TABLE, _sqlOpts()).columns(["id"]) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
|
|
750
|
+
).toSql();
|
|
751
|
+
var idRows = await clusterStorage.execute(purgeSelBuilt.sql, purgeSelBuilt.params);
|
|
669
752
|
var purgedCompositeIds = (idRows.rows || []).map(function (r) { return r.id; });
|
|
670
753
|
|
|
671
754
|
if (purgedCompositeIds.length === 0) {
|
|
@@ -673,12 +756,10 @@ function create(opts) {
|
|
|
673
756
|
return 0;
|
|
674
757
|
}
|
|
675
758
|
|
|
676
|
-
var
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
[namespace, threshold, threshold]
|
|
681
|
-
);
|
|
759
|
+
var purgeDelBuilt = _applyPurgeWhere(
|
|
760
|
+
sql.delete(TABLE, _sqlOpts()) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
|
|
761
|
+
).toSql();
|
|
762
|
+
var result = await clusterStorage.execute(purgeDelBuilt.sql, purgeDelBuilt.params);
|
|
682
763
|
var count = result.rowCount || purgedCompositeIds.length;
|
|
683
764
|
|
|
684
765
|
_emit("apikey.purge", {
|
|
@@ -167,6 +167,30 @@ function fsyncDir(dirPath) {
|
|
|
167
167
|
function _fsync(fd) { return fsync(fd); }
|
|
168
168
|
function _fsyncDir(dirPath) { return fsyncDir(dirPath); }
|
|
169
169
|
|
|
170
|
+
// Exclusive, no-follow create of the sibling temp file that every
|
|
171
|
+
// atomic write stages bytes into before the rename. CWE-377
|
|
172
|
+
// (insecure temporary file) / CWE-59 (symlink-following): the legacy
|
|
173
|
+
// "w" flag is O_WRONLY|O_CREAT|O_TRUNC — it happily opens (and
|
|
174
|
+
// truncates, or writes through) a file an attacker pre-created at the
|
|
175
|
+
// temp path, including a symlink pointing at a victim file the process
|
|
176
|
+
// can write but the attacker can't. O_EXCL makes the open fail with
|
|
177
|
+
// EEXIST if anything already exists at tmpPath, so a planted file /
|
|
178
|
+
// symlink / FIFO is refused instead of followed; O_NOFOLLOW rejects a
|
|
179
|
+
// symlink in the final path component on platforms that define it
|
|
180
|
+
// (Windows leaves it undefined, hence the `|| 0`). The temp name
|
|
181
|
+
// already carries a CSPRNG token (generateToken), so EEXIST is a
|
|
182
|
+
// hostile-collision signal, not a benign retry. The fd is returned for
|
|
183
|
+
// the caller to write + fsync; mode is applied at create time so the
|
|
184
|
+
// bytes are never world-readable even briefly.
|
|
185
|
+
function _openExclTemp(tmpPath, fileMode) {
|
|
186
|
+
return nodeFs.openSync(
|
|
187
|
+
tmpPath,
|
|
188
|
+
nodeFs.constants.O_WRONLY | nodeFs.constants.O_CREAT |
|
|
189
|
+
nodeFs.constants.O_EXCL | (nodeFs.constants.O_NOFOLLOW || 0),
|
|
190
|
+
fileMode
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
170
194
|
/**
|
|
171
195
|
* @primitive b.atomicFile.ensureDir
|
|
172
196
|
* @signature b.atomicFile.ensureDir(dirPath, mode)
|
|
@@ -392,6 +416,34 @@ function conflictPath(originalPath, opts) {
|
|
|
392
416
|
* );
|
|
393
417
|
* // → { bytesWritten: 7, hash: "<sha3-512 hex>" }
|
|
394
418
|
*/
|
|
419
|
+
// Synchronous bounded sleep (writeSync is a sync primitive, so no await).
|
|
420
|
+
// Uses Atomics.wait on a throwaway shared buffer; falls back to a short spin
|
|
421
|
+
// if SharedArrayBuffer is unavailable.
|
|
422
|
+
function _sleepSync(ms) {
|
|
423
|
+
try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); return; }
|
|
424
|
+
catch (_e) { /* fall through to spin */ }
|
|
425
|
+
var end = Date.now() + ms;
|
|
426
|
+
while (Date.now() < end) { /* spin */ }
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Atomic rename with a bounded retry on Windows-transient lock errors. On
|
|
430
|
+
// Windows a rename target is briefly held by AV / the search indexer / a
|
|
431
|
+
// file-sync client (Dropbox, OneDrive), surfacing as EPERM / EACCES / EBUSY
|
|
432
|
+
// even though the freshly-written temp file is fine; the lock clears in a few
|
|
433
|
+
// ms. POSIX rename is atomic and never hits this, so the first attempt
|
|
434
|
+
// succeeds there. Surface the error if it is not transient or persists.
|
|
435
|
+
function _renameWithRetry(from, to) {
|
|
436
|
+
var delays = [0, 5, 15, 40, 100];
|
|
437
|
+
for (var i = 0; i < delays.length; i += 1) {
|
|
438
|
+
if (delays[i] > 0) _sleepSync(delays[i]);
|
|
439
|
+
try { nodeFs.renameSync(from, to); return; }
|
|
440
|
+
catch (e) {
|
|
441
|
+
var transient = e && (e.code === "EPERM" || e.code === "EACCES" || e.code === "EBUSY");
|
|
442
|
+
if (!transient || i === delays.length - 1) throw e;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
395
447
|
function writeSync(filepath, data, opts) {
|
|
396
448
|
opts = Object.assign({}, DEFAULTS, opts || {});
|
|
397
449
|
var buf = safeBuffer.toBuffer(data, {
|
|
@@ -406,7 +458,7 @@ function writeSync(filepath, data, opts) {
|
|
|
406
458
|
var tmpPath = filepath + ".tmp-" + generateToken(C.BYTES.bytes(8));
|
|
407
459
|
var renamed = false;
|
|
408
460
|
try {
|
|
409
|
-
var fd =
|
|
461
|
+
var fd = _openExclTemp(tmpPath, opts.fileMode);
|
|
410
462
|
try {
|
|
411
463
|
var pos = 0;
|
|
412
464
|
while (pos < buf.length) {
|
|
@@ -416,7 +468,7 @@ function writeSync(filepath, data, opts) {
|
|
|
416
468
|
} finally {
|
|
417
469
|
try { nodeFs.closeSync(fd); } catch (_e) { /* already closed? */ }
|
|
418
470
|
}
|
|
419
|
-
|
|
471
|
+
_renameWithRetry(tmpPath, filepath);
|
|
420
472
|
renamed = true;
|
|
421
473
|
_fsyncDir(dir);
|
|
422
474
|
} finally {
|
|
@@ -530,7 +582,7 @@ async function write(filepath, data, opts) {
|
|
|
530
582
|
var tmpPath = filepath + ".tmp-" + generateToken(C.BYTES.bytes(8));
|
|
531
583
|
var renamed = false;
|
|
532
584
|
try {
|
|
533
|
-
var fd =
|
|
585
|
+
var fd = _openExclTemp(tmpPath, opts.fileMode);
|
|
534
586
|
try {
|
|
535
587
|
var pos = 0;
|
|
536
588
|
while (pos < buf.length) {
|
|
@@ -659,9 +711,15 @@ function _readSyncCore(filepath, opts) {
|
|
|
659
711
|
// can't swap the file between size-check and read because the fd
|
|
660
712
|
// is anchored to the original inode. ENOENT surfaces from open()
|
|
661
713
|
// rather than the previous existsSync() pre-check.
|
|
714
|
+
//
|
|
715
|
+
// The third argument pins an owner-only mode (0o600). The flag is
|
|
716
|
+
// read-only ("r" → O_RDONLY, no O_CREAT) so the mode is inert on
|
|
717
|
+
// disk, but specifying it keeps this open out of the insecure-temp-
|
|
718
|
+
// file class (CWE-377): the read can never create a world/group-
|
|
719
|
+
// accessible file even when `filepath` is rooted under a temp dir.
|
|
662
720
|
var fd;
|
|
663
721
|
try {
|
|
664
|
-
fd = nodeFs.openSync(filepath, "r");
|
|
722
|
+
fd = nodeFs.openSync(filepath, "r", 0o600);
|
|
665
723
|
} catch (openErr) {
|
|
666
724
|
if (openErr && openErr.code === "ENOENT") {
|
|
667
725
|
var e = new AtomicFileError("file not found: " + filepath, "atomic-file/not-found");
|
|
@@ -35,8 +35,23 @@
|
|
|
35
35
|
*/
|
|
36
36
|
var canonicalJson = require("./canonical-json");
|
|
37
37
|
var C = require("./constants");
|
|
38
|
+
var clusterStorage = require("./cluster-storage");
|
|
39
|
+
var frameworkSchema = require("./framework-schema");
|
|
40
|
+
var sql = require("./sql");
|
|
38
41
|
var { sha3Hash } = require("./crypto");
|
|
39
42
|
|
|
43
|
+
// b.sql opts for the chain read SQL these primitives compose. The reader
|
|
44
|
+
// (queryAllAsync / queryOneAsync, normally clusterStorage.execute*) rewrites
|
|
45
|
+
// the bare framework table name + translates `?` placeholders at dispatch,
|
|
46
|
+
// but the IDENTIFIER QUOTING + ORDER-BY column reference are baked into the
|
|
47
|
+
// b.sql output at build time — so they must carry the ACTIVE backend dialect
|
|
48
|
+
// (clusterStorage.dialect() — "sqlite" single-node, "postgres" | "mysql" in
|
|
49
|
+
// cluster mode). Defaulting to "sqlite" double-quotes `monotonicCounter`,
|
|
50
|
+
// which MySQL reads as a STRING LITERAL: `ORDER BY '<constant>'` imposes no
|
|
51
|
+
// ordering, so verifyChain walks the rows out of order and falsely reports a
|
|
52
|
+
// chain break. Backtick-quoting on MySQL makes it an identifier again.
|
|
53
|
+
function _sqlOpts() { return { dialect: clusterStorage.dialect() }; }
|
|
54
|
+
|
|
40
55
|
// SHA3-512 outputs 64 bytes; routed through C.BYTES so the file's byte
|
|
41
56
|
// arithmetic has one source of truth. Hex-encoded width is twice the
|
|
42
57
|
// byte count.
|
|
@@ -140,11 +155,20 @@ function computeRowHash(prevHash, rowFields, nonce) {
|
|
|
140
155
|
* // → { prevHash: "<128-char hex>", counter: 4217 }
|
|
141
156
|
*/
|
|
142
157
|
async function getChainTip(queryOneAsync, tableName) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
// Emit a BARE logical table name — the operator-supplied reader routes
|
|
159
|
+
// through clusterStorage, which rewrites bare framework names to the
|
|
160
|
+
// configured-prefix form and placeholderizes. b.sql quotes the camelCase
|
|
161
|
+
// columns + runs the output validator.
|
|
162
|
+
var built = sql.select(tableName, _sqlOpts())
|
|
163
|
+
.columns(["rowHash", "monotonicCounter"])
|
|
164
|
+
.orderBy("monotonicCounter", "desc")
|
|
165
|
+
.limit(1)
|
|
166
|
+
.toSql();
|
|
167
|
+
var row = await queryOneAsync(built.sql, built.params);
|
|
147
168
|
if (!row) return { prevHash: ZERO_HASH, counter: 0 };
|
|
169
|
+
// Normalize driver shape (Postgres returns BIGINT monotonicCounter as a
|
|
170
|
+
// string) so callers get a numeric counter on every backend.
|
|
171
|
+
frameworkSchema.coerceRow(row);
|
|
148
172
|
return { prevHash: row.rowHash, counter: row.monotonicCounter };
|
|
149
173
|
}
|
|
150
174
|
|
|
@@ -186,10 +210,15 @@ async function verifyChain(queryAllAsync, tableName, opts) {
|
|
|
186
210
|
if (tableName === "audit_log") {
|
|
187
211
|
var anchor;
|
|
188
212
|
try {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
213
|
+
// External-only table whose LOGICAL name IS the `_blamejs_`-prefixed
|
|
214
|
+
// name (self-mapped in LOCAL_TO_EXTERNAL), passed bare so the reader's
|
|
215
|
+
// clusterStorage rewrites it; the 'audit' scope binds as a ? param.
|
|
216
|
+
// allow:hand-rolled-sql — bare logical key.
|
|
217
|
+
var anchorBuilt = sql.select("_blamejs_audit_purge_anchor", _sqlOpts()) // allow:hand-rolled-sql
|
|
218
|
+
.columns(["lastPurgedCounter", "lastPurgedRowHash"])
|
|
219
|
+
.where("scope", "audit")
|
|
220
|
+
.toSql();
|
|
221
|
+
anchor = await queryAllAsync(anchorBuilt.sql, anchorBuilt.params);
|
|
193
222
|
} catch (_e) {
|
|
194
223
|
// Anchor table may not exist on a deployment that has never been
|
|
195
224
|
// through a purge. Treat as no anchor.
|
|
@@ -201,9 +230,16 @@ async function verifyChain(queryAllAsync, tableName, opts) {
|
|
|
201
230
|
}
|
|
202
231
|
}
|
|
203
232
|
|
|
204
|
-
var
|
|
205
|
-
|
|
206
|
-
|
|
233
|
+
var rowsBuilt = sql.select(tableName, _sqlOpts())
|
|
234
|
+
.orderBy("monotonicCounter", "asc")
|
|
235
|
+
.toSql();
|
|
236
|
+
var rows = await queryAllAsync(rowsBuilt.sql, rowsBuilt.params);
|
|
237
|
+
// Normalize driver shape before hashing: node-postgres returns BIGINT
|
|
238
|
+
// columns (recordedAt / monotonicCounter) as strings, which would hash
|
|
239
|
+
// differently from the numbers the chain-writer signed — the chain only
|
|
240
|
+
// verified on SQLite without this. coerceRow makes the recompute
|
|
241
|
+
// type-stable across backends (no-op on already-numeric SQLite rows).
|
|
242
|
+
rows = frameworkSchema.coerceRows(rows);
|
|
207
243
|
if (skipBeforeCounter > 0) {
|
|
208
244
|
rows = rows.filter(function (r) {
|
|
209
245
|
return Number(r.monotonicCounter) > skipBeforeCounter;
|
|
@@ -63,6 +63,7 @@ var nodePath = require("node:path");
|
|
|
63
63
|
var nodeCrypto = require("node:crypto");
|
|
64
64
|
var atomicFile = require("./atomic-file");
|
|
65
65
|
var { sha3Hash } = require("./crypto");
|
|
66
|
+
var frameworkFiles = require("./framework-files");
|
|
66
67
|
var { defineClass } = require("./framework-error");
|
|
67
68
|
var { boot } = require("./log");
|
|
68
69
|
var safeBuffer = require("./safe-buffer");
|
|
@@ -118,11 +119,73 @@ var log = boot("audit-sign");
|
|
|
118
119
|
function resolvePaths(dataDir) {
|
|
119
120
|
return {
|
|
120
121
|
dataDir: dataDir,
|
|
121
|
-
plaintext: nodePath.join(dataDir,
|
|
122
|
-
sealed: nodePath.join(dataDir, "
|
|
122
|
+
plaintext: nodePath.join(dataDir, frameworkFiles.fileName("auditSignKey")),
|
|
123
|
+
sealed: nodePath.join(dataDir, frameworkFiles.fileName("auditSignKey") + ".sealed"),
|
|
124
|
+
// Unsealed registry of rotated-out PUBLIC keys (public keys are not
|
|
125
|
+
// secret). It lets verify-time code (b.audit.verifyCheckpoints) resolve
|
|
126
|
+
// the public key for a checkpoint signed under a now-rotated key WITHOUT
|
|
127
|
+
// the old passphrase, so a rotation does not strand historical checkpoints.
|
|
128
|
+
publicHistory: nodePath.join(dataDir, "audit-sign.pubkeys.json"),
|
|
123
129
|
};
|
|
124
130
|
}
|
|
125
131
|
|
|
132
|
+
// Append a rotated-out public key to the unsealed public-key history. Public
|
|
133
|
+
// keys carry no secret, so storing them in the clear is safe and is what
|
|
134
|
+
// makes passphrase-free historical verification possible. De-duplicated by
|
|
135
|
+
// fingerprint; best-effort (a write failure must not abort the rotation, the
|
|
136
|
+
// sealed private-key history is the durable archive of record).
|
|
137
|
+
function _appendPublicHistory(entry) {
|
|
138
|
+
if (!paths || !paths.publicHistory) return;
|
|
139
|
+
var list = [];
|
|
140
|
+
try {
|
|
141
|
+
if (nodeFs.existsSync(paths.publicHistory)) {
|
|
142
|
+
var parsed = safeJson.parse(atomicFile.readSync(paths.publicHistory));
|
|
143
|
+
if (Array.isArray(parsed)) list = parsed;
|
|
144
|
+
}
|
|
145
|
+
} catch (_e) { list = []; } // corrupt registry — rebuild from this entry
|
|
146
|
+
for (var i = 0; i < list.length; i += 1) {
|
|
147
|
+
if (list[i] && list[i].fingerprint === entry.fingerprint) return; // already recorded
|
|
148
|
+
}
|
|
149
|
+
list.push(entry);
|
|
150
|
+
try {
|
|
151
|
+
atomicFile.writeSync(paths.publicHistory, JSON.stringify(list, null, 2), { fileMode: 0o600 });
|
|
152
|
+
} catch (_e) { /* best-effort */ }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @primitive b.auditSign.getPublicKeyByFingerprint
|
|
157
|
+
* @signature b.auditSign.getPublicKeyByFingerprint(fingerprint)
|
|
158
|
+
* @since 0.14.29
|
|
159
|
+
* @status stable
|
|
160
|
+
* @related b.auditSign.getPublicKey, b.auditSign.verify, b.auditSign.rotateSigningKey
|
|
161
|
+
*
|
|
162
|
+
* Resolve the audit-signing public key (SPKI PEM) for a fingerprint: the
|
|
163
|
+
* live key, or a rotated-out key recorded in the unsealed public-key history
|
|
164
|
+
* that `rotateSigningKey` maintains. Returns `null` when no key matches. Only
|
|
165
|
+
* public material is consulted, so no passphrase is needed - this is what
|
|
166
|
+
* lets `b.audit.verifyCheckpoints` verify a checkpoint signed under a
|
|
167
|
+
* now-rotated key without stranding history.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* var pem = b.auditSign.getPublicKeyByFingerprint(checkpoint.publicKeyFingerprint);
|
|
171
|
+
* // -> "-----BEGIN PUBLIC KEY-----\n..." (or null if the key is unknown)
|
|
172
|
+
*/
|
|
173
|
+
function getPublicKeyByFingerprint(fp) {
|
|
174
|
+
_requireInit();
|
|
175
|
+
if (fp === keys.fingerprint) return keys.publicKey;
|
|
176
|
+
if (!paths || !paths.publicHistory || !nodeFs.existsSync(paths.publicHistory)) return null;
|
|
177
|
+
var list;
|
|
178
|
+
try { list = safeJson.parse(atomicFile.readSync(paths.publicHistory)); }
|
|
179
|
+
catch (_e) { return null; }
|
|
180
|
+
if (!Array.isArray(list)) return null;
|
|
181
|
+
for (var i = 0; i < list.length; i += 1) {
|
|
182
|
+
if (list[i] && list[i].fingerprint === fp && typeof list[i].publicKey === "string") {
|
|
183
|
+
return list[i].publicKey;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
126
189
|
function _computeFingerprint(publicKeyPem) {
|
|
127
190
|
return sha3Hash(publicKeyPem);
|
|
128
191
|
}
|
|
@@ -677,6 +740,17 @@ async function rotateSigningKey(rotOpts) {
|
|
|
677
740
|
catch (_e) { /* history copy is best-effort */ }
|
|
678
741
|
}
|
|
679
742
|
|
|
743
|
+
// Record the rotated-out PUBLIC key (unsealed) so b.audit.verifyCheckpoints
|
|
744
|
+
// can verify a checkpoint signed under it after rotation without the old
|
|
745
|
+
// passphrase. Without this the public key only lives inside the sealed
|
|
746
|
+
// history archive and verification of pre-rotation checkpoints is stranded.
|
|
747
|
+
_appendPublicHistory({
|
|
748
|
+
fingerprint: prevFingerprint,
|
|
749
|
+
publicKey: prevPublicKey,
|
|
750
|
+
algorithm: prevAlgorithm,
|
|
751
|
+
rotatedAt: new Date().toISOString(),
|
|
752
|
+
});
|
|
753
|
+
|
|
680
754
|
// Persist the new keypair through the same path as boot — sealed
|
|
681
755
|
// mode re-wraps with the operator's passphrase; plaintext mode
|
|
682
756
|
// writes JSON. We don't accept a passphrase override here; the
|
|
@@ -740,6 +814,7 @@ module.exports = {
|
|
|
740
814
|
reSignAll: reSignAll,
|
|
741
815
|
getPublicKey: getPublicKey,
|
|
742
816
|
getPublicKeyFingerprint: getPublicKeyFingerprint,
|
|
817
|
+
getPublicKeyByFingerprint: getPublicKeyByFingerprint,
|
|
743
818
|
getMode: getMode,
|
|
744
819
|
getAlgorithm: getAlgorithm,
|
|
745
820
|
DEFAULT_SIGNING_ALG: DEFAULT_SIGNING_ALG,
|