@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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// #126: SSE _writeRaw wrote to the response with no regard for backpressure
|
|
3
|
+
// and no bound on the outbound buffer. res.write() returns false when the
|
|
4
|
+
// socket's send buffer is full; a slow/stalled client never drains, so the
|
|
5
|
+
// server's writable buffer (res.writableLength) grows without bound — one
|
|
6
|
+
// stuck connection can exhaust the server heap (memory-exhaustion DoS).
|
|
7
|
+
//
|
|
8
|
+
// The fix caps the per-channel buffered bytes (maxBufferedBytes) and evicts
|
|
9
|
+
// the slow consumer (closes the channel + throws sse/backpressure) once the
|
|
10
|
+
// buffer blows the cap, instead of buffering indefinitely.
|
|
11
|
+
//
|
|
12
|
+
// RED on the buggy tree: send() can be called unbounded against a stalled
|
|
13
|
+
// client and never refuses; the channel stays open. GREEN after the fix:
|
|
14
|
+
// send() throws sse/backpressure once the buffer exceeds the cap and the
|
|
15
|
+
// channel is closed.
|
|
16
|
+
|
|
17
|
+
var EventEmitter = require("events");
|
|
18
|
+
var helpers = require("../helpers");
|
|
19
|
+
var b = helpers.b;
|
|
20
|
+
var check = helpers.check;
|
|
21
|
+
|
|
22
|
+
// A response whose socket never drains: every write accumulates into
|
|
23
|
+
// writableLength and write() reports backpressure (false) past the highwater.
|
|
24
|
+
function _stalledRes(highWaterBytes) {
|
|
25
|
+
var res = new EventEmitter();
|
|
26
|
+
var buffered = 0;
|
|
27
|
+
res.writableLength = 0;
|
|
28
|
+
res.setHeader = function () {};
|
|
29
|
+
res.flushHeaders = function () {};
|
|
30
|
+
res.write = function (chunk) {
|
|
31
|
+
buffered += Buffer.byteLength(chunk, "utf8");
|
|
32
|
+
res.writableLength = buffered; // stalled client: nothing is ever flushed
|
|
33
|
+
return buffered < highWaterBytes; // false once over the socket highwater
|
|
34
|
+
};
|
|
35
|
+
res.end = function () {};
|
|
36
|
+
return res;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function run() {
|
|
40
|
+
var req = { method: "GET", url: "/events", headers: {}, httpVersionMajor: 1, on: function () {} };
|
|
41
|
+
var res = _stalledRes(b.constants.BYTES.kib(64));
|
|
42
|
+
|
|
43
|
+
var capBytes = b.constants.BYTES.kib(8);
|
|
44
|
+
var channel = b.sse.create(req, res, {
|
|
45
|
+
heartbeatMs: 0, // no heartbeat noise
|
|
46
|
+
audit: false,
|
|
47
|
+
maxBufferedBytes: capBytes,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
var threw = null;
|
|
51
|
+
var sends = 0;
|
|
52
|
+
var event = { data: "x".repeat(512) }; // ~520 bytes serialized
|
|
53
|
+
try {
|
|
54
|
+
for (var i = 0; i < 100000 && threw === null; i++) {
|
|
55
|
+
channel.send(event);
|
|
56
|
+
sends += 1;
|
|
57
|
+
}
|
|
58
|
+
} catch (e) { threw = e; }
|
|
59
|
+
|
|
60
|
+
check("#126 send() refuses once the outbound buffer blows maxBufferedBytes",
|
|
61
|
+
threw !== null && threw.code === "sse/backpressure");
|
|
62
|
+
check("#126 the slow-consumer channel is closed on eviction",
|
|
63
|
+
channel.closed === true);
|
|
64
|
+
// The buffer cap is ~8 KiB and events are ~520 B, so eviction must fire
|
|
65
|
+
// within a few dozen sends — never the full 100k unbounded loop.
|
|
66
|
+
check("#126 buffering is bounded (evicted well before the unbounded loop ended)",
|
|
67
|
+
sends > 0 && sends < 1000);
|
|
68
|
+
check("#126 res.writableLength stayed near the cap, not unbounded",
|
|
69
|
+
res.writableLength < capBytes + b.constants.BYTES.kib(2));
|
|
70
|
+
|
|
71
|
+
// A HEALTHY client (write() drains, writableLength stays ~0) is never evicted.
|
|
72
|
+
var healthyRes = new EventEmitter();
|
|
73
|
+
healthyRes.writableLength = 0;
|
|
74
|
+
healthyRes.setHeader = function () {};
|
|
75
|
+
healthyRes.flushHeaders = function () {};
|
|
76
|
+
healthyRes.write = function () { healthyRes.writableLength = 0; return true; };
|
|
77
|
+
healthyRes.end = function () {};
|
|
78
|
+
var healthy = b.sse.create(req, healthyRes, { heartbeatMs: 0, audit: false, maxBufferedBytes: capBytes });
|
|
79
|
+
for (var j = 0; j < 5000; j++) healthy.send(event);
|
|
80
|
+
check("#126 a healthy (draining) client is never evicted by the cap",
|
|
81
|
+
healthy.closed === false);
|
|
82
|
+
healthy.close();
|
|
83
|
+
|
|
84
|
+
console.log("OK — sse backpressure / bounded-buffer tests");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { run: run };
|
|
88
|
+
if (require.main === module) {
|
|
89
|
+
run().then(function () { process.exit(0); })
|
|
90
|
+
.catch(function (err) { process.stderr.write(String(err && err.stack || err) + "\n"); process.exit(1); });
|
|
91
|
+
}
|
|
@@ -206,6 +206,75 @@ async function run() {
|
|
|
206
206
|
check("checkUrl: public IP passes",
|
|
207
207
|
r2 && r2.ips[0].address === "93.184.216.34");
|
|
208
208
|
|
|
209
|
+
// ---- cloud-metadata IPv6 matched on canonical bytes, not string ----
|
|
210
|
+
// The IMDS-over-IPv6 address fd00:ec2::254 has many textual spellings.
|
|
211
|
+
// A non-canonical / mixed-case spelling must still classify as
|
|
212
|
+
// cloud-metadata — a string-equality membership test caught one
|
|
213
|
+
// spelling only, and the DoH resolver decodes AAAA to the expanded
|
|
214
|
+
// fd00:ec2:0:0:0:0:0:254 form, which slipped past as "private".
|
|
215
|
+
check("classify fd00:ec2:0:0:0:0:0:254 → cloud-metadata (expanded zero-run)",
|
|
216
|
+
ssrf.classify("fd00:ec2:0:0:0:0:0:254") === "cloud-metadata");
|
|
217
|
+
check("classify FD00:EC2::254 → cloud-metadata (mixed-case compressed)",
|
|
218
|
+
ssrf.classify("FD00:EC2::254") === "cloud-metadata");
|
|
219
|
+
check("classify fd00:0ec2::0254 → cloud-metadata (leading-zero groups)",
|
|
220
|
+
ssrf.classify("fd00:0ec2::0254") === "cloud-metadata");
|
|
221
|
+
// A genuine ULA private address that is NOT metadata must stay "private".
|
|
222
|
+
check("classify fd00::1 → private (NOT metadata)",
|
|
223
|
+
ssrf.classify("fd00::1") === "private");
|
|
224
|
+
|
|
225
|
+
// PoC: allowInternal:true MUST NOT override the unconditional metadata
|
|
226
|
+
// block, even when DNS resolves to the non-canonical IMDS-over-IPv6
|
|
227
|
+
// spelling. Pre-fix this RESOLVED (credentials reachable); now refused.
|
|
228
|
+
threw = null;
|
|
229
|
+
try {
|
|
230
|
+
await ssrf.checkUrl("http://imds.attacker.example/latest/meta-data/iam/", {
|
|
231
|
+
allowInternal: true,
|
|
232
|
+
dnsLookup: _stubLookup("fd00:ec2:0:0:0:0:0:254", 6),
|
|
233
|
+
});
|
|
234
|
+
} catch (e) { threw = e; }
|
|
235
|
+
check("checkUrl: allowInternal:true does NOT override non-canonical IMDS-over-IPv6",
|
|
236
|
+
threw && threw.code === "ssrf-guard/blocked-cloud-metadata");
|
|
237
|
+
|
|
238
|
+
// Compressed + mixed-case spellings refused too (wire not reached).
|
|
239
|
+
threw = null;
|
|
240
|
+
try {
|
|
241
|
+
await ssrf.checkUrl("http://imds.attacker.example/", {
|
|
242
|
+
allowInternal: true,
|
|
243
|
+
dnsLookup: _stubLookup("FD00:EC2::254", 6),
|
|
244
|
+
});
|
|
245
|
+
} catch (e) { threw = e; }
|
|
246
|
+
check("checkUrl: allowInternal:true does NOT override mixed-case IMDS-over-IPv6",
|
|
247
|
+
threw && threw.code === "ssrf-guard/blocked-cloud-metadata");
|
|
248
|
+
|
|
249
|
+
// A genuine private IPv6 (fd00::1, NOT metadata) still respects
|
|
250
|
+
// allowInternal — the metadata gate is the only unconditional class.
|
|
251
|
+
threw = null;
|
|
252
|
+
var rPriv6 = null;
|
|
253
|
+
try {
|
|
254
|
+
rPriv6 = await ssrf.checkUrl("http://internal.mesh/", {
|
|
255
|
+
allowInternal: true,
|
|
256
|
+
dnsLookup: _stubLookup("fd00::1", 6),
|
|
257
|
+
});
|
|
258
|
+
} catch (e) { threw = e; }
|
|
259
|
+
check("checkUrl: genuine private IPv6 still honors allowInternal:true",
|
|
260
|
+
threw === null && rPriv6 && rPriv6.ips[0].address === "fd00::1");
|
|
261
|
+
|
|
262
|
+
// checkUrlTextual: the same byte-canonical gate at the textual layer.
|
|
263
|
+
threw = null;
|
|
264
|
+
try { ssrf.checkUrlTextual("http://[fd00:ec2:0:0:0:0:0:254]/x"); }
|
|
265
|
+
catch (e) { threw = e; }
|
|
266
|
+
check("checkUrlTextual: non-canonical IMDS-over-IPv6 refused",
|
|
267
|
+
threw && threw.code === "ssrf-guard/blocked-cloud-metadata");
|
|
268
|
+
threw = null;
|
|
269
|
+
try { ssrf.checkUrlTextual("http://[FD00:EC2::254]/x"); }
|
|
270
|
+
catch (e) { threw = e; }
|
|
271
|
+
check("checkUrlTextual: mixed-case IMDS-over-IPv6 refused",
|
|
272
|
+
threw && threw.code === "ssrf-guard/blocked-cloud-metadata");
|
|
273
|
+
// A non-metadata internal host stays accepted at the textual layer.
|
|
274
|
+
var txtOk = ssrf.checkUrlTextual("http://[fd00::1]/x");
|
|
275
|
+
check("checkUrlTextual: non-metadata IPv6 literal accepted",
|
|
276
|
+
txtOk && txtOk.host === "fd00::1");
|
|
277
|
+
|
|
209
278
|
// ---- checkUrl: bad input ----
|
|
210
279
|
threw = null;
|
|
211
280
|
try { await ssrf.checkUrl("not-a-url"); }
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* static — forceAttachmentForNonText
|
|
4
|
-
* user-upload directories
|
|
3
|
+
* static — forceAttachmentForNonText defense for stored-XSS via
|
|
4
|
+
* user-upload directories, plus the mountType typing that drives its
|
|
5
|
+
* default (v0.15.0: "user-content" mounts force-download by default;
|
|
6
|
+
* "curated" mounts keep inline render).
|
|
5
7
|
*
|
|
6
8
|
* Run standalone: `node test/layer-0-primitives/static.test.js`
|
|
7
9
|
* Or via smoke: `node test/smoke.js`
|
|
@@ -233,6 +235,73 @@ async function testForceAttachmentPdfOptInInline() {
|
|
|
233
235
|
}
|
|
234
236
|
}
|
|
235
237
|
|
|
238
|
+
// mountType drives the forceAttachmentForNonText default (v0.15.0).
|
|
239
|
+
// A mount TYPED "user-content" forces risky inline MIMEs to download with
|
|
240
|
+
// NO explicit forceAttachmentForNonText (the new default-on behavior); a
|
|
241
|
+
// "curated" mount (or unset) keeps inline render (the opt-out preserved).
|
|
242
|
+
async function testMountTypeUserContentForcesDownloadByDefault() {
|
|
243
|
+
var ctx = await _server();
|
|
244
|
+
_writeFile(ctx.dir, "evil.html", "<script>alert(1)</script>");
|
|
245
|
+
// No forceAttachmentForNonText passed — the default comes from mountType.
|
|
246
|
+
var srv = await ctx.start({ contentSafety: null, mountType: "user-content" });
|
|
247
|
+
try {
|
|
248
|
+
var r = await _get(srv.port, "/evil.html");
|
|
249
|
+
check("mountType user-content: html forced to attachment by default",
|
|
250
|
+
r.statusCode === 200 &&
|
|
251
|
+
/^attachment;/.test(r.headers["content-disposition"] || ""));
|
|
252
|
+
check("mountType user-content: nosniff added by default",
|
|
253
|
+
r.headers["x-content-type-options"] === "nosniff");
|
|
254
|
+
} finally {
|
|
255
|
+
srv.close();
|
|
256
|
+
ctx.cleanup();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function testMountTypeCuratedKeepsInline() {
|
|
261
|
+
var ctx = await _server();
|
|
262
|
+
_writeFile(ctx.dir, "evil.html", "<script>alert(1)</script>");
|
|
263
|
+
// Explicit "curated" is the documented opt-out — inline render preserved.
|
|
264
|
+
var srv = await ctx.start({ contentSafety: null, mountType: "curated" });
|
|
265
|
+
try {
|
|
266
|
+
var r = await _get(srv.port, "/evil.html");
|
|
267
|
+
check("mountType curated: html served inline (opt-out preserved)",
|
|
268
|
+
r.statusCode === 200 && !r.headers["content-disposition"]);
|
|
269
|
+
} finally {
|
|
270
|
+
srv.close();
|
|
271
|
+
ctx.cleanup();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function testMountTypeExplicitOverrideWins() {
|
|
276
|
+
var ctx = await _server();
|
|
277
|
+
_writeFile(ctx.dir, "evil.html", "<script>alert(1)</script>");
|
|
278
|
+
// An explicit forceAttachmentForNonText:false overrides the
|
|
279
|
+
// user-content-derived default either way.
|
|
280
|
+
var srv = await ctx.start({
|
|
281
|
+
contentSafety: null, mountType: "user-content",
|
|
282
|
+
forceAttachmentForNonText: false,
|
|
283
|
+
});
|
|
284
|
+
try {
|
|
285
|
+
var r = await _get(srv.port, "/evil.html");
|
|
286
|
+
check("explicit forceAttachmentForNonText:false overrides user-content default",
|
|
287
|
+
r.statusCode === 200 && !r.headers["content-disposition"]);
|
|
288
|
+
} finally {
|
|
289
|
+
srv.close();
|
|
290
|
+
ctx.cleanup();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function testMountTypeBadValueThrows() {
|
|
295
|
+
var threw = null;
|
|
296
|
+
var dir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-static-mt-"));
|
|
297
|
+
try {
|
|
298
|
+
b.staticServe.create({ root: dir, mountType: "uploads" });
|
|
299
|
+
} catch (e) { threw = e; }
|
|
300
|
+
finally { fs.rmSync(dir, { recursive: true, force: true }); }
|
|
301
|
+
check("bad mountType value throws at config time",
|
|
302
|
+
threw && /mountType must be 'curated'/.test(threw.message));
|
|
303
|
+
}
|
|
304
|
+
|
|
236
305
|
function testRejectsUnknownOpts() {
|
|
237
306
|
var threw = null;
|
|
238
307
|
var dir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-static-fa-"));
|
|
@@ -299,6 +368,122 @@ function testOnErrorRejectsNonFunction() {
|
|
|
299
368
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
300
369
|
}
|
|
301
370
|
|
|
371
|
+
// Send a raw (un-normalized-by-node) request path. http.request normalizes
|
|
372
|
+
// "/../" on the client side, so build the request line by hand over a raw
|
|
373
|
+
// socket to deliver the traversal bytes verbatim to the server.
|
|
374
|
+
function _getRaw(port, rawPath) {
|
|
375
|
+
var net = require("node:net");
|
|
376
|
+
return new Promise(function (resolve, reject) {
|
|
377
|
+
var sock = net.connect(port, "127.0.0.1", function () {
|
|
378
|
+
sock.write("GET " + rawPath + " HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n");
|
|
379
|
+
});
|
|
380
|
+
var chunks = [];
|
|
381
|
+
sock.on("data", function (c) { chunks.push(c); });
|
|
382
|
+
sock.on("end", function () {
|
|
383
|
+
var raw = Buffer.concat(chunks).toString("utf8");
|
|
384
|
+
var statusLine = raw.split("\r\n")[0] || "";
|
|
385
|
+
var m = /^HTTP\/1\.\d (\d{3})/.exec(statusLine);
|
|
386
|
+
var sep = raw.indexOf("\r\n\r\n");
|
|
387
|
+
var body = sep === -1 ? "" : raw.slice(sep + 4);
|
|
388
|
+
resolve({ statusCode: m ? parseInt(m[1], 10) : 0, body: body, raw: raw });
|
|
389
|
+
});
|
|
390
|
+
sock.on("error", reject);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Path-traversal refusal — a file OUTSIDE the served root must never be
|
|
395
|
+
// reachable via `../` escapes, percent-encoded escapes, or NUL injection.
|
|
396
|
+
// The secret is written to the parent of `root` so any successful escape
|
|
397
|
+
// would disclose it.
|
|
398
|
+
async function testPathTraversalRefused() {
|
|
399
|
+
var ctx = await _server();
|
|
400
|
+
// Sibling secret one level above the served root.
|
|
401
|
+
var parent = path.dirname(ctx.dir);
|
|
402
|
+
var secretName = "blamejs-static-secret-" + process.pid + ".txt";
|
|
403
|
+
var secretAbs = path.join(parent, secretName);
|
|
404
|
+
var secretBody = "TOP-SECRET-" + Date.now();
|
|
405
|
+
fs.writeFileSync(secretAbs, secretBody);
|
|
406
|
+
// A legit in-root file to prove the server is otherwise functional.
|
|
407
|
+
_writeFile(ctx.dir, "ok.txt", "in-root");
|
|
408
|
+
var srv = await ctx.start({ contentSafety: null });
|
|
409
|
+
try {
|
|
410
|
+
// Raw `../` traversal (delivered verbatim over a raw socket).
|
|
411
|
+
var rawTrav = await _getRaw(srv.port, "/../" + secretName);
|
|
412
|
+
check("traversal: raw ../ does not disclose the sibling secret",
|
|
413
|
+
rawTrav.body.indexOf(secretBody) === -1);
|
|
414
|
+
check("traversal: raw ../ refused (not 200-with-secret)",
|
|
415
|
+
rawTrav.statusCode !== 200 || rawTrav.body.indexOf(secretBody) === -1);
|
|
416
|
+
|
|
417
|
+
// Deeper raw traversal.
|
|
418
|
+
var rawDeep = await _getRaw(srv.port, "/sub/../../" + secretName);
|
|
419
|
+
check("traversal: nested raw ../../ does not disclose the secret",
|
|
420
|
+
rawDeep.body.indexOf(secretBody) === -1);
|
|
421
|
+
|
|
422
|
+
// Percent-encoded `..%2f` traversal (the server decodes, then must
|
|
423
|
+
// still refuse).
|
|
424
|
+
var encTrav = await _get(srv.port, "/%2e%2e%2f" + secretName);
|
|
425
|
+
check("traversal: percent-encoded %2e%2e%2f refused (404/next)",
|
|
426
|
+
encTrav.statusCode === 404);
|
|
427
|
+
check("traversal: percent-encoded escape does not disclose the secret",
|
|
428
|
+
encTrav.body.toString("utf8").indexOf(secretBody) === -1);
|
|
429
|
+
|
|
430
|
+
// Double percent-encoded dot-dot.
|
|
431
|
+
var encTrav2 = await _get(srv.port, "/%2e%2e/%2e%2e/" + secretName);
|
|
432
|
+
check("traversal: %2e%2e/%2e%2e/ does not disclose the secret",
|
|
433
|
+
encTrav2.body.toString("utf8").indexOf(secretBody) === -1);
|
|
434
|
+
|
|
435
|
+
// NUL-byte injection — refused before any fs op.
|
|
436
|
+
var nul = await _get(srv.port, "/ok.txt%00.png");
|
|
437
|
+
check("traversal: NUL-byte path refused (404/next)",
|
|
438
|
+
nul.statusCode === 404);
|
|
439
|
+
|
|
440
|
+
// Sanity: the legit in-root file still serves.
|
|
441
|
+
var ok = await _get(srv.port, "/ok.txt");
|
|
442
|
+
check("traversal: in-root file still serves after refusals",
|
|
443
|
+
ok.statusCode === 200 && ok.body.toString("utf8") === "in-root");
|
|
444
|
+
} finally {
|
|
445
|
+
srv.close();
|
|
446
|
+
fs.rmSync(secretAbs, { force: true });
|
|
447
|
+
ctx.cleanup();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Success path — a legitimately nested file under root is served with its
|
|
452
|
+
// bytes intact. Confirms the confinement barrier does not over-refuse.
|
|
453
|
+
async function testNestedFileServed() {
|
|
454
|
+
var ctx = await _server();
|
|
455
|
+
_writeFile(ctx.dir, "assets/css/site.css", "body{color:#000}");
|
|
456
|
+
var srv = await ctx.start({ contentSafety: null });
|
|
457
|
+
try {
|
|
458
|
+
var r = await _get(srv.port, "/assets/css/site.css");
|
|
459
|
+
check("nested: deep in-root file serves 200",
|
|
460
|
+
r.statusCode === 200);
|
|
461
|
+
check("nested: bytes intact",
|
|
462
|
+
r.body.toString("utf8") === "body{color:#000}");
|
|
463
|
+
check("nested: correct content-type",
|
|
464
|
+
/text\/css/.test(r.headers["content-type"] || ""));
|
|
465
|
+
} finally {
|
|
466
|
+
srv.close();
|
|
467
|
+
ctx.cleanup();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// A directory-index request resolves to the index file inside root and
|
|
472
|
+
// serves it — exercises the re-confinement after the index-file join.
|
|
473
|
+
async function testDirectoryIndexServed() {
|
|
474
|
+
var ctx = await _server();
|
|
475
|
+
_writeFile(ctx.dir, "docs/index.html", "<h1>docs</h1>");
|
|
476
|
+
var srv = await ctx.start({ contentSafety: null });
|
|
477
|
+
try {
|
|
478
|
+
var r = await _get(srv.port, "/docs/");
|
|
479
|
+
check("dir-index: index.html served for a directory request",
|
|
480
|
+
r.statusCode === 200 && /docs/.test(r.body.toString("utf8")));
|
|
481
|
+
} finally {
|
|
482
|
+
srv.close();
|
|
483
|
+
ctx.cleanup();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
302
487
|
async function run() {
|
|
303
488
|
await testForceAttachmentDefaultOff();
|
|
304
489
|
await testForceAttachmentOnHtml();
|
|
@@ -309,10 +494,17 @@ async function run() {
|
|
|
309
494
|
await testForceAttachmentSvgWithSanitizerInlineAllowed();
|
|
310
495
|
await testForceAttachmentPdfDefaultDownload();
|
|
311
496
|
await testForceAttachmentPdfOptInInline();
|
|
497
|
+
await testMountTypeUserContentForcesDownloadByDefault();
|
|
498
|
+
await testMountTypeCuratedKeepsInline();
|
|
499
|
+
await testMountTypeExplicitOverrideWins();
|
|
500
|
+
testMountTypeBadValueThrows();
|
|
312
501
|
testRejectsUnknownOpts();
|
|
313
502
|
await testOnErrorFiresOnRefusal();
|
|
314
503
|
await testOnErrorThrowDoesNotCorruptResponse();
|
|
315
504
|
testOnErrorRejectsNonFunction();
|
|
505
|
+
await testPathTraversalRefused();
|
|
506
|
+
await testNestedFileServed();
|
|
507
|
+
await testDirectoryIndexServed();
|
|
316
508
|
}
|
|
317
509
|
|
|
318
510
|
module.exports = { run: run };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* websocket _parseExtensionHeader — Sec-WebSocket-Extensions parser
|
|
4
|
+
* (RFC 7692 permessage-deflate negotiation feeds off this).
|
|
5
|
+
*
|
|
6
|
+
* Focus: the extension-parameter name is client-controlled, so the
|
|
7
|
+
* params map is built from [name, value] pairs through Object.fromEntries
|
|
8
|
+
* onto a null-prototype object — never a computed-write (`params[name] =
|
|
9
|
+
* value`) sink. Verifies a hostile `__proto__` / `constructor` /
|
|
10
|
+
* `prototype` parameter name does NOT pollute Object.prototype
|
|
11
|
+
* (CWE-915 / CWE-1321) and that the legitimate parameter shape is
|
|
12
|
+
* unchanged.
|
|
13
|
+
*
|
|
14
|
+
* Run standalone: `node test/layer-0-primitives/websocket-extension-header.test.js`
|
|
15
|
+
* Or via smoke: `node test/smoke.js`
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
var helpers = require("../helpers");
|
|
19
|
+
var b = helpers.b;
|
|
20
|
+
var check = helpers.check;
|
|
21
|
+
|
|
22
|
+
var _parseExtensionHeader = b.websocket._parseExtensionHeader;
|
|
23
|
+
|
|
24
|
+
function testParsesParamsSuccessPath() {
|
|
25
|
+
var out = _parseExtensionHeader(
|
|
26
|
+
"permessage-deflate; client_max_window_bits=12; server_no_context_takeover, foo; bar=baz"
|
|
27
|
+
);
|
|
28
|
+
check("ext: two extensions parsed", Array.isArray(out) && out.length === 2);
|
|
29
|
+
check("ext: first name lower-cased", out[0].name === "permessage-deflate");
|
|
30
|
+
check("ext: param value parsed", out[0].params.client_max_window_bits === "12");
|
|
31
|
+
check("ext: valueless param is boolean true",
|
|
32
|
+
out[0].params.server_no_context_takeover === true);
|
|
33
|
+
check("ext: second extension name", out[1].name === "foo");
|
|
34
|
+
check("ext: second extension param", out[1].params.bar === "baz");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function testParamsMapHasNullPrototype() {
|
|
38
|
+
var out = _parseExtensionHeader("permessage-deflate; client_max_window_bits=15");
|
|
39
|
+
check("ext: params map has null prototype",
|
|
40
|
+
Object.getPrototypeOf(out[0].params) === null);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function testRejectsPoisonedParamNames() {
|
|
44
|
+
var out = _parseExtensionHeader(
|
|
45
|
+
"permessage-deflate; __proto__=evil; constructor=evil2; prototype=evil3; client_max_window_bits=10"
|
|
46
|
+
);
|
|
47
|
+
var params = out[0].params;
|
|
48
|
+
check("ext: __proto__ param dropped",
|
|
49
|
+
!Object.prototype.hasOwnProperty.call(params, "__proto__"));
|
|
50
|
+
check("ext: constructor param dropped",
|
|
51
|
+
!Object.prototype.hasOwnProperty.call(params, "constructor"));
|
|
52
|
+
check("ext: prototype param dropped",
|
|
53
|
+
!Object.prototype.hasOwnProperty.call(params, "prototype"));
|
|
54
|
+
check("ext: legitimate param still present alongside dropped ones",
|
|
55
|
+
params.client_max_window_bits === "10");
|
|
56
|
+
check("ext: Object.prototype not polluted",
|
|
57
|
+
({}).evil === undefined && Object.prototype.evil === undefined);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function testProtoValueViaFromEntriesDoesNotEscalate() {
|
|
61
|
+
// Even if a future grammar change let `__proto__` reach the pair list,
|
|
62
|
+
// a null-prototype accumulator + Object.fromEntries materialization
|
|
63
|
+
// can only create an OWN `__proto__` property — it can never mutate the
|
|
64
|
+
// prototype chain. The current parser drops the name outright; this is
|
|
65
|
+
// the structural backstop.
|
|
66
|
+
var before = Object.prototype.toString;
|
|
67
|
+
_parseExtensionHeader("permessage-deflate; __proto__=x");
|
|
68
|
+
check("ext: Object.prototype.toString intact after parse",
|
|
69
|
+
Object.prototype.toString === before);
|
|
70
|
+
check("ext: a fresh object still inherits toString",
|
|
71
|
+
typeof ({}).toString === "function");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function run() {
|
|
75
|
+
testParsesParamsSuccessPath();
|
|
76
|
+
testParamsMapHasNullPrototype();
|
|
77
|
+
testRejectsPoisonedParamNames();
|
|
78
|
+
testProtoValueViaFromEntriesDoesNotEscalate();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { run: run };
|
|
82
|
+
|
|
83
|
+
if (require.main === module) {
|
|
84
|
+
run().then(
|
|
85
|
+
function () { console.log("OK — " + helpers.getChecks() + " checks passed"); },
|
|
86
|
+
function (e) { console.error("FAIL:", e.message); process.exit(1); }
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// #127: a task queued behind a timing-out (or erroring) task must not be
|
|
3
|
+
// dropped by slot recycling. _onTaskTimeout / _onWorkerError called
|
|
4
|
+
// _finishTask BEFORE marking the slot recycling. _finishTask sets slot.busy
|
|
5
|
+
// = false and drains the queue, so _findIdleSlot handed the freshly-queued
|
|
6
|
+
// task to the very slot whose worker was about to be terminated — the task's
|
|
7
|
+
// message went to a dying worker and came back as workerpool/worker-exit
|
|
8
|
+
// (or hung), even though a healthy replacement worker was about to spawn.
|
|
9
|
+
//
|
|
10
|
+
// This is deterministic (not a flaky race): _finishTask -> _drainQueue runs
|
|
11
|
+
// synchronously inside _onTaskTimeout, before _recycleWorker. The fix marks
|
|
12
|
+
// the slot recycling BEFORE _finishTask, so the drain skips the dying slot
|
|
13
|
+
// and the queued task waits for the replacement worker.
|
|
14
|
+
//
|
|
15
|
+
// RED on the buggy tree: the queued echo rejects with workerpool/worker-exit.
|
|
16
|
+
// GREEN after the fix: it runs on the replacement worker and returns its
|
|
17
|
+
// result.
|
|
18
|
+
|
|
19
|
+
var path = require("path");
|
|
20
|
+
var helpers = require("../helpers");
|
|
21
|
+
var b = helpers.b;
|
|
22
|
+
var check = helpers.check;
|
|
23
|
+
|
|
24
|
+
var FIXTURE = path.resolve(__dirname, "..", "fixtures", "worker-pool", "echo.js");
|
|
25
|
+
|
|
26
|
+
async function run() {
|
|
27
|
+
// Single worker so the second task MUST queue behind the first, and a
|
|
28
|
+
// short timeout so the busy-loop task is reaped quickly.
|
|
29
|
+
var pool = b.workerPool.create(FIXTURE, { size: 1, taskTimeoutMs: 400 });
|
|
30
|
+
try {
|
|
31
|
+
// Task A occupies the only worker and busy-loops until it is reaped by
|
|
32
|
+
// taskTimeoutMs. Its rejection (workerpool/timeout) is expected.
|
|
33
|
+
var aErr = null;
|
|
34
|
+
var aPromise = pool.run({ kind: "loop" }).catch(function (e) { aErr = e; });
|
|
35
|
+
|
|
36
|
+
// Task B queues behind A (worker busy). It is a trivial echo that must
|
|
37
|
+
// succeed once a healthy worker is available.
|
|
38
|
+
var bResult = null;
|
|
39
|
+
var bError = null;
|
|
40
|
+
var bPromise = pool.run({ kind: "echo", payload: { v: 42 } })
|
|
41
|
+
.then(function (r) { bResult = r; })
|
|
42
|
+
.catch(function (e) { bError = e; });
|
|
43
|
+
|
|
44
|
+
// Bound the wait so a hung B fails the test rather than hanging the suite.
|
|
45
|
+
await Promise.race([
|
|
46
|
+
Promise.all([aPromise, bPromise]),
|
|
47
|
+
helpers.waitUntil(function () { return bResult !== null || bError !== null; },
|
|
48
|
+
{ timeoutMs: 8000, label: "#127: queued task settles after the slot recycles" }),
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
check("#127 the timing-out task A is reaped (timeout, as expected)",
|
|
52
|
+
aErr && aErr.code === "workerpool/timeout");
|
|
53
|
+
check("#127 the task queued behind it is NOT dropped by slot recycling",
|
|
54
|
+
bError === null);
|
|
55
|
+
check("#127 the queued task runs on the replacement worker and returns its result",
|
|
56
|
+
bResult && bResult.v === 42);
|
|
57
|
+
} finally {
|
|
58
|
+
pool.terminate();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { run: run };
|
|
63
|
+
if (require.main === module) {
|
|
64
|
+
run().then(function () { process.exit(0); })
|
|
65
|
+
.catch(function (err) { process.stderr.write(String(err && err.stack || err) + "\n"); process.exit(1); });
|
|
66
|
+
}
|
|
@@ -259,6 +259,88 @@ async function testHashAlgoOptArgon2id() {
|
|
|
259
259
|
check("argon2id: wrong secret returns null", wrong === null);
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
async function testRotateOnVerifyUpgradesHashAlgo() {
|
|
263
|
+
// A row stored under a non-active hash algorithm is transparently
|
|
264
|
+
// re-hashed to the active algorithm on the next successful (leader)
|
|
265
|
+
// verify — the rotate-on-next-verify that credentialHash documents.
|
|
266
|
+
var ns = "rehash-mig";
|
|
267
|
+
// A store configured for argon2id issues the key (the "legacy" shape).
|
|
268
|
+
var legacy = b.apiKey.create({ namespace: ns, hashAlgo: "argon2id" });
|
|
269
|
+
var issued = await legacy.issue({ ownerId: "u1" });
|
|
270
|
+
var pre = await b.clusterStorage.executeOne(
|
|
271
|
+
"SELECT secretHash FROM _blamejs_api_keys WHERE id = ?", [ns + ":" + issued.id]);
|
|
272
|
+
check("rotate-on-verify: stored under argon2id before verify",
|
|
273
|
+
b.credentialHash.inspect(pre.secretHash).algoName === "argon2id");
|
|
274
|
+
|
|
275
|
+
// A default (shake256) store verifies the SAME key on the same table.
|
|
276
|
+
var active = b.apiKey.create({ namespace: ns }); // hashAlgo defaults to shake256
|
|
277
|
+
var record = await active.verify(issued.key);
|
|
278
|
+
check("rotate-on-verify: credential still verifies under the legacy hash",
|
|
279
|
+
record !== null && record.id === issued.id);
|
|
280
|
+
|
|
281
|
+
// The at-rest hash is now the active shake256 envelope — upgraded in place,
|
|
282
|
+
// with no API/return-value change for the caller.
|
|
283
|
+
var post = await b.clusterStorage.executeOne(
|
|
284
|
+
"SELECT secretHash FROM _blamejs_api_keys WHERE id = ?", [ns + ":" + issued.id]);
|
|
285
|
+
var info = b.credentialHash.inspect(post.secretHash);
|
|
286
|
+
check("rotate-on-verify: stored hash upgraded to shake256", info.algoName === "shake256");
|
|
287
|
+
check("rotate-on-verify: upgraded envelope no longer needs rehash",
|
|
288
|
+
b.credentialHash.needsRehash(post.secretHash, { algo: "shake256" }) === false);
|
|
289
|
+
|
|
290
|
+
// The original secret still verifies under the upgraded hash.
|
|
291
|
+
var again = await active.verify(issued.key);
|
|
292
|
+
check("rotate-on-verify: re-verify under the upgraded hash succeeds",
|
|
293
|
+
again !== null && again.id === issued.id);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function testRehashOnVerifyDoesNotClobberConcurrentRotate() {
|
|
297
|
+
// Rotate-on-verify reads the stored hash, then writes the upgraded hash in a
|
|
298
|
+
// later UPDATE. If a rotate() lands between the read and the write, a
|
|
299
|
+
// row-id-only UPDATE would overwrite the freshly rotated secret with the OLD
|
|
300
|
+
// secret's re-hash — invalidating the new token while the old one keeps
|
|
301
|
+
// working. The upgrade write carries a compare-and-swap on the exact hash
|
|
302
|
+
// that was verified, so a row whose secretHash changed under it is untouched.
|
|
303
|
+
var credentialHashMod = require("../../lib/credential-hash");
|
|
304
|
+
var ns = "rehash-race";
|
|
305
|
+
var legacy = b.apiKey.create({ namespace: ns, hashAlgo: "argon2id" });
|
|
306
|
+
var issued = await legacy.issue({ ownerId: "u1" });
|
|
307
|
+
var rowId = ns + ":" + issued.id;
|
|
308
|
+
|
|
309
|
+
// The hash a concurrent rotate() would install mid-flight (a distinct envelope).
|
|
310
|
+
var rotatedHash = await credentialHashMod.hash("ab".repeat(32), { algo: "shake256" });
|
|
311
|
+
|
|
312
|
+
var active = b.apiKey.create({ namespace: ns }); // active algo shake256 → triggers the re-hash
|
|
313
|
+
var realHash = credentialHashMod.hash;
|
|
314
|
+
var injected = false;
|
|
315
|
+
// Land a concurrent rotation inside the re-hash's async window: overwrite the
|
|
316
|
+
// stored secretHash after the OLD value was read but before the upgrade UPDATE.
|
|
317
|
+
// api-key reads credentialHash.hash off this module object at call time, so the
|
|
318
|
+
// patch intercepts exactly the rotate-on-verify re-hash (issue() above ran
|
|
319
|
+
// before the patch and is unaffected).
|
|
320
|
+
credentialHashMod.hash = async function (secretHex, opts) {
|
|
321
|
+
var out = await realHash.call(credentialHashMod, secretHex, opts);
|
|
322
|
+
if (!injected) {
|
|
323
|
+
injected = true;
|
|
324
|
+
await b.clusterStorage.execute(
|
|
325
|
+
"UPDATE _blamejs_api_keys SET secretHash = ? WHERE id = ?", [rotatedHash, rowId]);
|
|
326
|
+
}
|
|
327
|
+
return out;
|
|
328
|
+
};
|
|
329
|
+
var record;
|
|
330
|
+
try {
|
|
331
|
+
record = await active.verify(issued.key);
|
|
332
|
+
} finally {
|
|
333
|
+
credentialHashMod.hash = realHash;
|
|
334
|
+
}
|
|
335
|
+
check("rehash-race: the credential still verifies under the legacy hash",
|
|
336
|
+
record !== null && record.id === issued.id);
|
|
337
|
+
|
|
338
|
+
var post = await b.clusterStorage.executeOne(
|
|
339
|
+
"SELECT secretHash FROM _blamejs_api_keys WHERE id = ?", [rowId]);
|
|
340
|
+
check("rehash-race: the concurrent rotation's hash survives — re-hash did NOT clobber it",
|
|
341
|
+
post.secretHash === rotatedHash);
|
|
342
|
+
}
|
|
343
|
+
|
|
262
344
|
async function testGetById() {
|
|
263
345
|
var keys = b.apiKey.create({ namespace: "get-test" });
|
|
264
346
|
var issued = await keys.issue({ ownerId: "u1" });
|
|
@@ -690,6 +772,8 @@ async function run() {
|
|
|
690
772
|
await testHardRotateClearsSecondary();
|
|
691
773
|
await testEnvelopeFormatPersisted();
|
|
692
774
|
await testHashAlgoOptArgon2id();
|
|
775
|
+
await testRotateOnVerifyUpgradesHashAlgo();
|
|
776
|
+
await testRehashOnVerifyDoesNotClobberConcurrentRotate();
|
|
693
777
|
await testListForOwner();
|
|
694
778
|
await testGetById();
|
|
695
779
|
await testTrackLastUsedAt();
|