@blamejs/blamejs-shop 0.4.31 → 0.4.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +1 -1
- package/lib/asset-manifest.json +1 -1
- package/lib/vendor/MANIFEST.json +400 -282
- package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
- package/lib/vendor/blamejs/.gitignore +6 -0
- package/lib/vendor/blamejs/CHANGELOG.md +28 -0
- package/lib/vendor/blamejs/MIGRATING.md +55 -0
- package/lib/vendor/blamejs/README.md +8 -6
- package/lib/vendor/blamejs/SECURITY.md +19 -3
- package/lib/vendor/blamejs/api-snapshot.json +2190 -664
- package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
- package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
- package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
- package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
- package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
- package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
- package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
- package/lib/vendor/blamejs/index.js +4 -0
- package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
- package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
- package/lib/vendor/blamejs/lib/api-key.js +158 -77
- package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
- package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
- package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
- package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
- package/lib/vendor/blamejs/lib/audit.js +259 -123
- package/lib/vendor/blamejs/lib/auth/elevation-grant.js +6 -2
- package/lib/vendor/blamejs/lib/auth/oauth.js +66 -9
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
- package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +36 -7
- package/lib/vendor/blamejs/lib/backup/index.js +45 -10
- package/lib/vendor/blamejs/lib/break-glass.js +355 -147
- package/lib/vendor/blamejs/lib/cache.js +174 -105
- package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
- package/lib/vendor/blamejs/lib/cli.js +19 -14
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
- package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
- package/lib/vendor/blamejs/lib/cluster.js +119 -71
- package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
- package/lib/vendor/blamejs/lib/compliance.js +210 -4
- package/lib/vendor/blamejs/lib/consent.js +82 -29
- package/lib/vendor/blamejs/lib/constants.js +27 -11
- package/lib/vendor/blamejs/lib/credential-hash.js +9 -0
- package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
- package/lib/vendor/blamejs/lib/db-query.js +882 -260
- package/lib/vendor/blamejs/lib/db-schema.js +228 -44
- package/lib/vendor/blamejs/lib/db.js +249 -99
- package/lib/vendor/blamejs/lib/dsr.js +385 -55
- package/lib/vendor/blamejs/lib/error-page.js +14 -1
- package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
- package/lib/vendor/blamejs/lib/external-db.js +549 -34
- package/lib/vendor/blamejs/lib/file-upload.js +52 -7
- package/lib/vendor/blamejs/lib/framework-error.js +20 -1
- package/lib/vendor/blamejs/lib/framework-files.js +73 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
- package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
- package/lib/vendor/blamejs/lib/guard-all.js +1 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
- package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
- package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
- package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
- package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
- package/lib/vendor/blamejs/lib/guard-email.js +47 -69
- package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
- package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
- package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
- package/lib/vendor/blamejs/lib/guard-html.js +53 -108
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
- package/lib/vendor/blamejs/lib/guard-image.js +46 -103
- package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
- package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
- package/lib/vendor/blamejs/lib/guard-json.js +38 -108
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
- package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
- package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
- package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
- package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
- package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
- package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
- package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
- package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
- package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
- package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
- package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
- package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
- package/lib/vendor/blamejs/lib/guard-template.js +35 -172
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-time.js +32 -154
- package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
- package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
- package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
- package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
- package/lib/vendor/blamejs/lib/http-client.js +37 -9
- package/lib/vendor/blamejs/lib/inbox.js +120 -107
- package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
- package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
- package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
- package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
- package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
- package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
- package/lib/vendor/blamejs/lib/mail-store.js +293 -154
- package/lib/vendor/blamejs/lib/mail.js +8 -4
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
- package/lib/vendor/blamejs/lib/migrations.js +108 -66
- package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
- package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
- package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
- package/lib/vendor/blamejs/lib/observability.js +124 -0
- package/lib/vendor/blamejs/lib/otel-export.js +12 -3
- package/lib/vendor/blamejs/lib/outbox.js +184 -83
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
- package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
- package/lib/vendor/blamejs/lib/queue-local.js +225 -140
- package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
- package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
- package/lib/vendor/blamejs/lib/queue.js +7 -0
- package/lib/vendor/blamejs/lib/redact.js +68 -11
- package/lib/vendor/blamejs/lib/redis-client.js +160 -31
- package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
- package/lib/vendor/blamejs/lib/retention.js +117 -42
- package/lib/vendor/blamejs/lib/router.js +212 -5
- package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
- package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
- package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
- package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
- package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
- package/lib/vendor/blamejs/lib/safe-url.js +170 -3
- package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
- package/lib/vendor/blamejs/lib/scheduler.js +47 -12
- package/lib/vendor/blamejs/lib/seeders.js +122 -74
- package/lib/vendor/blamejs/lib/session-stores.js +42 -14
- package/lib/vendor/blamejs/lib/session.js +175 -77
- package/lib/vendor/blamejs/lib/sql.js +3842 -0
- package/lib/vendor/blamejs/lib/sse.js +26 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +169 -4
- package/lib/vendor/blamejs/lib/static.js +177 -34
- package/lib/vendor/blamejs/lib/subject.js +96 -49
- package/lib/vendor/blamejs/lib/vault/index.js +3 -2
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
- package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
- package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
- package/lib/vendor/blamejs/lib/websocket.js +35 -5
- package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
- package/lib/vendor/blamejs/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
- package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
- package/lib/vendor/blamejs/release-notes/v0.15.7.json +43 -0
- package/lib/vendor/blamejs/scripts/check-services.js +21 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +67 -0
- package/lib/vendor/blamejs/scripts/release.js +398 -38
- package/lib/vendor/blamejs/test/00-primitives.js +168 -0
- package/lib/vendor/blamejs/test/10-state.js +140 -14
- package/lib/vendor/blamejs/test/20-db.js +65 -2
- package/lib/vendor/blamejs/test/helpers/db.js +9 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
- package/lib/vendor/blamejs/test/helpers/services.js +21 -0
- package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
- package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
- package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
- package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
- package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
- package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
- package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
- package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
- package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
- package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
- package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
- package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
- package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
- package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
- package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
- package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1196 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +362 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-watchdog-stale-settle.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
- package/lib/vendor/blamejs/test/smoke.js +79 -21
- package/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
- package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
- package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
- package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
- package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
- package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
- package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
- package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
- package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
- package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
- package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
- package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
- package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
- package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
|
@@ -247,6 +247,21 @@ function create(req, res, opts) {
|
|
|
247
247
|
// proxyBuffer: false to suppress the nginx-specific header.
|
|
248
248
|
var proxyBuffer = opts.proxyBuffer !== false;
|
|
249
249
|
|
|
250
|
+
// Slow-consumer bound. SSE is server-push: when a client stalls, res.write()
|
|
251
|
+
// returns false but the app keeps pushing, so Node buffers the unsent bytes
|
|
252
|
+
// in res.writableLength without limit — one stuck connection grows the heap
|
|
253
|
+
// until exhaustion (memory-exhaustion DoS). Cap the per-channel buffer and
|
|
254
|
+
// evict the slow consumer past it. A healthy client (writableLength ~0) is
|
|
255
|
+
// never affected. Config-time input → throw on a bad value.
|
|
256
|
+
var maxBufferedBytes = opts.maxBufferedBytes;
|
|
257
|
+
if (maxBufferedBytes === undefined) maxBufferedBytes = C.BYTES.mib(1);
|
|
258
|
+
if (typeof maxBufferedBytes !== "number" || !isFinite(maxBufferedBytes) ||
|
|
259
|
+
maxBufferedBytes <= 0 || Math.floor(maxBufferedBytes) !== maxBufferedBytes) {
|
|
260
|
+
throw errorClass.factory("sse/bad-opts",
|
|
261
|
+
"sse.create: maxBufferedBytes must be a positive integer byte count (got " +
|
|
262
|
+
JSON.stringify(maxBufferedBytes) + ")");
|
|
263
|
+
}
|
|
264
|
+
|
|
250
265
|
var lastEventId = _readLastEventId(req);
|
|
251
266
|
|
|
252
267
|
// Headers. text/event-stream is the contract; Cache-Control: no-cache
|
|
@@ -275,6 +290,17 @@ function create(req, res, opts) {
|
|
|
275
290
|
"sse.send: channel closed");
|
|
276
291
|
}
|
|
277
292
|
res.write(s);
|
|
293
|
+
// res.writableLength is the count of bytes Node has buffered but not yet
|
|
294
|
+
// flushed to the socket. A healthy client drains it (≈0); a stalled one
|
|
295
|
+
// lets it climb. Past the per-channel cap, evict the slow consumer rather
|
|
296
|
+
// than buffer without bound. h2 streams + h1 responses both expose it.
|
|
297
|
+
var buffered = (typeof res.writableLength === "number") ? res.writableLength : 0;
|
|
298
|
+
if (buffered > maxBufferedBytes) {
|
|
299
|
+
close({ reason: "backpressure-exceeded" });
|
|
300
|
+
throw errorClass.factory("sse/backpressure",
|
|
301
|
+
"sse.send: client too slow — buffered " + buffered +
|
|
302
|
+
" bytes exceeds maxBufferedBytes " + maxBufferedBytes + "; channel closed");
|
|
303
|
+
}
|
|
278
304
|
}
|
|
279
305
|
|
|
280
306
|
function send(eventOpts) {
|
|
@@ -148,12 +148,27 @@ var IPV6_6TO4_PREFIX = _ipv6ToBytes("2002::");
|
|
|
148
148
|
// or attempted exfil to a sinkhole.
|
|
149
149
|
var IPV6_DISCARD_PREFIX = _ipv6ToBytes("100::");
|
|
150
150
|
|
|
151
|
-
// ---- Cloud metadata addresses (
|
|
151
|
+
// ---- Cloud metadata addresses (matched on CANONICAL bytes, not string) ----
|
|
152
|
+
// The documentation strings below are the human-readable canonical forms.
|
|
153
|
+
// Matching is byte-canonical (see _isCloudMetadataAddr): an IPv6 address has
|
|
154
|
+
// many textual representations (compressed `::`, fully-expanded
|
|
155
|
+
// `fd00:ec2:0:0:0:0:0:254`, mixed-case) that all decode to the same 16 bytes.
|
|
156
|
+
// A string-equality membership test matched only ONE spelling, so a hostile
|
|
157
|
+
// (or merely DoH-decoded — network-dns.js emits the expanded form) answer of
|
|
158
|
+
// `fd00:ec2:0:0:0:0:0:254` slipped past as "private" and rode the documented
|
|
159
|
+
// `allowInternal:true` waiver straight into the IMDS credential endpoint.
|
|
152
160
|
var CLOUD_METADATA_IPS = [
|
|
153
161
|
"169.254.169.254", // AWS, GCP, Azure, OpenStack, DO
|
|
154
162
|
"169.254.170.2", // AWS ECS task role
|
|
155
163
|
"fd00:ec2::254", // AWS IMDS over IPv6
|
|
156
164
|
];
|
|
165
|
+
// Canonical byte forms of the metadata IPs — v4 as a 4-byte Buffer, v6 as a
|
|
166
|
+
// 16-byte Buffer. Built once at load via the same parsers classify() uses,
|
|
167
|
+
// so every textual representation that decodes to these bytes is caught.
|
|
168
|
+
var CLOUD_METADATA_BYTES = CLOUD_METADATA_IPS.map(function (ip) {
|
|
169
|
+
var fam = net.isIP(ip);
|
|
170
|
+
return fam === 4 ? _ipv4ToBytes(ip) : _ipv6ToBytes(ip);
|
|
171
|
+
});
|
|
157
172
|
|
|
158
173
|
// ---- Helpers ----
|
|
159
174
|
|
|
@@ -180,6 +195,14 @@ function _ipv4ToInt(ip) {
|
|
|
180
195
|
nums[3];
|
|
181
196
|
}
|
|
182
197
|
|
|
198
|
+
function _ipv4ToBytes(ip) {
|
|
199
|
+
// Canonical 4-byte form of an IPv4 address. Returns null on malformed
|
|
200
|
+
// input so a metadata-membership test never matches garbage.
|
|
201
|
+
var n = _ipv4ToInt(ip);
|
|
202
|
+
if (!Number.isFinite(n)) return null;
|
|
203
|
+
return Buffer.from([(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff]);
|
|
204
|
+
}
|
|
205
|
+
|
|
183
206
|
function _ipv6ToBytes(ip) {
|
|
184
207
|
// Node's net.isIPv6 returns 6 for valid IPv6; we then expand
|
|
185
208
|
// shorthand via manual parsing. node:net doesn't export an
|
|
@@ -224,6 +247,123 @@ function _expandIpv6(ip) {
|
|
|
224
247
|
return left.concat(fill).concat(right);
|
|
225
248
|
}
|
|
226
249
|
|
|
250
|
+
function _ipv6BytesToString(bytes) {
|
|
251
|
+
// RFC 5952 §4 canonical textual form from 16 canonical bytes: lower-hex,
|
|
252
|
+
// no leading zeros per group, the LONGEST run of two-or-more zero groups
|
|
253
|
+
// compressed to "::" (leftmost run on a length tie — §4.2.3), and the
|
|
254
|
+
// shortened-but-not-IPv4-dotted form (the framework keeps IPv4-mapped as
|
|
255
|
+
// pure hex so every mapped spelling collapses to one string). Driven off
|
|
256
|
+
// the same 16-byte buffer classify() matches on, so the emitted string and
|
|
257
|
+
// the security verdict can never disagree about which address this is.
|
|
258
|
+
var groups = [];
|
|
259
|
+
for (var i = 0; i < IPV6_GROUPS; i++) {
|
|
260
|
+
groups.push(((bytes[i * 2] << 8) | bytes[i * 2 + 1]) & 0xffff);
|
|
261
|
+
}
|
|
262
|
+
var bestStart = -1, bestLen = 0, curStart = -1, curLen = 0;
|
|
263
|
+
for (var g = 0; g < IPV6_GROUPS; g++) {
|
|
264
|
+
if (groups[g] === 0) {
|
|
265
|
+
if (curStart === -1) { curStart = g; curLen = 1; } else { curLen += 1; }
|
|
266
|
+
if (curLen > bestLen) { bestLen = curLen; bestStart = curStart; }
|
|
267
|
+
} else {
|
|
268
|
+
curStart = -1;
|
|
269
|
+
curLen = 0;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// A single zero group is NOT compressed (RFC 5952 §4.2.2).
|
|
273
|
+
if (bestLen < 2) bestStart = -1;
|
|
274
|
+
var parts = [];
|
|
275
|
+
for (var k = 0; k < IPV6_GROUPS; k++) {
|
|
276
|
+
if (bestStart !== -1 && k === bestStart) {
|
|
277
|
+
parts.push("");
|
|
278
|
+
k += bestLen - 1;
|
|
279
|
+
// A run reaching the final group needs a trailing empty part so the
|
|
280
|
+
// join yields the "::"-terminated form (e.g. fe80:: not fe80:).
|
|
281
|
+
if (k === IPV6_GROUPS - 1) parts.push("");
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
parts.push(groups[k].toString(HEX_RADIX));
|
|
285
|
+
}
|
|
286
|
+
var out = parts.join(":");
|
|
287
|
+
// A run starting at group 0 needs a leading empty part ("::1", "::").
|
|
288
|
+
if (bestStart === 0) out = ":" + out;
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @primitive b.ssrfGuard.canonicalizeHost
|
|
294
|
+
* @signature b.ssrfGuard.canonicalizeHost(host)
|
|
295
|
+
* @since 0.15.6
|
|
296
|
+
* @status stable
|
|
297
|
+
* @related b.ssrfGuard.classify, b.safeUrl.canonicalize
|
|
298
|
+
*
|
|
299
|
+
* Canonicalize a bare host string to its single comparable form for
|
|
300
|
+
* host allowlists, dedup keys, and SSRF pre-checks. A `net.isIP`-valid
|
|
301
|
+
* IP literal collapses to one canonical string: a dotted-quad IPv4
|
|
302
|
+
* stays dotted-quad; IPv6 in any zero-compression / mixed-case /
|
|
303
|
+
* IPv4-mapped spelling (`[0:0:0:0:0:ffff:7f00:1]`, `::FFFF:7F00:1`)
|
|
304
|
+
* becomes the RFC 5952 lower-hex compressed form. The IP bytes are
|
|
305
|
+
* parsed by the SAME routines `classify` matches on, so the canonical
|
|
306
|
+
* string and the SSRF verdict can never disagree about which address a
|
|
307
|
+
* host is.
|
|
308
|
+
*
|
|
309
|
+
* The numeric-base IPv4 decode (octal `0177.0.0.1`, hex `0x7f000001`,
|
|
310
|
+
* single-integer `2130706433`, shorthand `127.1`) is the WHATWG URL
|
|
311
|
+
* parser's job — `b.safeUrl.canonicalize` runs that FIRST and hands this
|
|
312
|
+
* primitive the already-decoded dotted-quad. This is the IP-byte + case
|
|
313
|
+
* layer, not the base decoder.
|
|
314
|
+
*
|
|
315
|
+
* A DNS name (not an IP literal) is lowercased and any trailing dot is
|
|
316
|
+
* stripped — `Example.COM.` → `example.com`. IDN A-label / U-label
|
|
317
|
+
* normalization is NOT done here (the WHATWG URL parser owns that via
|
|
318
|
+
* `b.safeUrl.canonicalize`).
|
|
319
|
+
* `[...]`-bracketed IPv6 input is accepted (brackets stripped); the
|
|
320
|
+
* returned IPv6 string is UNbracketed (the URL layer re-adds brackets).
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* var b = require("blamejs");
|
|
324
|
+
* b.ssrfGuard.canonicalizeHost("[0:0:0:0:0:0:0:1]"); // → "::1"
|
|
325
|
+
* b.ssrfGuard.canonicalizeHost("::FFFF:7F00:1"); // → "::ffff:7f00:1"
|
|
326
|
+
* b.ssrfGuard.canonicalizeHost("Example.COM."); // → "example.com"
|
|
327
|
+
*/
|
|
328
|
+
function canonicalizeHost(host) {
|
|
329
|
+
if (typeof host !== "string" || host.length === 0) return host;
|
|
330
|
+
var bare = host.replace(/^\[|\]$/g, "");
|
|
331
|
+
var family = net.isIP(bare);
|
|
332
|
+
if (family === 4) {
|
|
333
|
+
var v4 = _ipv4ToBytes(bare);
|
|
334
|
+
if (v4) return v4[0] + "." + v4[1] + "." + v4[2] + "." + v4[3];
|
|
335
|
+
return bare.toLowerCase();
|
|
336
|
+
}
|
|
337
|
+
if (family === 6) {
|
|
338
|
+
var v6bytes = _ipv6ToBytes(bare);
|
|
339
|
+
// An IPv4-mapped IPv6 address (::ffff:a.b.c.d, the ::ffff:0:0/96 block) IS
|
|
340
|
+
// the IPv4 address a.b.c.d for routing / access control — classify() already
|
|
341
|
+
// re-classifies it by the embedded v4, and a dual-stack peer arriving on
|
|
342
|
+
// ::ffff:1.2.3.4 reaches the same host as 1.2.3.4. Fold it to the dotted
|
|
343
|
+
// IPv4 form so a dual-stack peer and an operator's IPv4 allowlist entry
|
|
344
|
+
// canonicalize equal. ONLY the IPv4-mapped block (::ffff:0:0/96) folds,
|
|
345
|
+
// because classify(::ffff:x) === classify(x) — its classify branch returns
|
|
346
|
+
// the embedded-v4 verdict with no reserved fallback, so folding can't change
|
|
347
|
+
// an SSRF verdict. NAT64 (64:ff9b::/96) and 6to4 (2002::/16) are NOT folded:
|
|
348
|
+
// classify treats a NAT64 literal as `classify(v4) || "reserved"`, so a
|
|
349
|
+
// public NAT64 address classifies as "reserved" while its embedded v4 is
|
|
350
|
+
// null — folding would flip a blocked verdict to an allowed public IPv4.
|
|
351
|
+
// classify still reaches the embedded v4 for the deny side; the canonical
|
|
352
|
+
// form keeps NAT64 / 6to4 as IPv6 so canonicalize-then-classify agrees with
|
|
353
|
+
// classify alone.
|
|
354
|
+
if (_ipv6PrefixMatch(IPV6_V4_MAPPED_PREFIX, C.BYTES.bytes(96), v6bytes)) {
|
|
355
|
+
return v6bytes[12] + "." + v6bytes[13] + "." + v6bytes[14] + "." + v6bytes[15];
|
|
356
|
+
}
|
|
357
|
+
return _ipv6BytesToString(v6bytes);
|
|
358
|
+
}
|
|
359
|
+
// Not an IP literal — DNS name. Lowercase + strip ALL trailing dots: a
|
|
360
|
+
// hostname's trailing-dot count is not significant for identity (the root
|
|
361
|
+
// label is empty), so host / host. / host.. must collapse to one form or a
|
|
362
|
+
// trailing-dot count bypasses a host allow/deny comparison.
|
|
363
|
+
var name = bare.toLowerCase().replace(/\.+$/, "");
|
|
364
|
+
return name;
|
|
365
|
+
}
|
|
366
|
+
|
|
227
367
|
function _cidrIpv4Match(cidr, ip) {
|
|
228
368
|
var slash = cidr.indexOf("/");
|
|
229
369
|
if (slash === -1) return false;
|
|
@@ -309,7 +449,10 @@ function classify(ip) {
|
|
|
309
449
|
var family = net.isIP(ip);
|
|
310
450
|
if (family === 0) return null;
|
|
311
451
|
|
|
312
|
-
|
|
452
|
+
// Cloud-metadata IPs are matched on their canonical byte form so every
|
|
453
|
+
// textual spelling (compressed `::`, fully-expanded zero-runs, mixed
|
|
454
|
+
// case) is caught — a string-equality test matched one spelling only.
|
|
455
|
+
if (_isCloudMetadataAddr(ip, family)) return "cloud-metadata";
|
|
313
456
|
|
|
314
457
|
if (family === 4) {
|
|
315
458
|
var ipInt = _ipv4ToInt(ip);
|
|
@@ -349,6 +492,24 @@ function classify(ip) {
|
|
|
349
492
|
return null;
|
|
350
493
|
}
|
|
351
494
|
|
|
495
|
+
// Canonical-bytes membership test for the cloud-metadata IP set. An IP
|
|
496
|
+
// matches iff its parsed bytes equal one of CLOUD_METADATA_BYTES, regardless
|
|
497
|
+
// of textual representation. This is the unconditional metadata gate — it
|
|
498
|
+
// must NOT be string-based, because IPv6 has many spellings of the same
|
|
499
|
+
// address (the DoH resolver in network-dns.js, for instance, emits the
|
|
500
|
+
// fully-expanded `fd00:ec2:0:0:0:0:0:254` rather than the compressed form).
|
|
501
|
+
function _isCloudMetadataAddr(ip, family) {
|
|
502
|
+
var fam = typeof family === "number" ? family : net.isIP(ip);
|
|
503
|
+
if (fam === 0) return false;
|
|
504
|
+
var bytes = fam === 4 ? _ipv4ToBytes(ip) : _ipv6ToBytes(ip);
|
|
505
|
+
if (!bytes) return false;
|
|
506
|
+
for (var i = 0; i < CLOUD_METADATA_BYTES.length; i++) {
|
|
507
|
+
var ref = CLOUD_METADATA_BYTES[i];
|
|
508
|
+
if (ref && ref.length === bytes.length && _bufEqual(bytes, ref)) return true;
|
|
509
|
+
}
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
|
|
352
513
|
function _bufEqual(a, b) {
|
|
353
514
|
// Compares Buffer-like byte arrays for equality. The buffers here
|
|
354
515
|
// are IP addresses, not secrets, so the comparison doesn't need
|
|
@@ -766,8 +927,11 @@ function checkUrlTextual(url, opts) {
|
|
|
766
927
|
// If the textual hostname IS an IP literal AND matches a cloud-
|
|
767
928
|
// metadata IP, refuse — even with `allowInternal: true` and a proxy.
|
|
768
929
|
// Metadata IPs leak instance credentials (AWS IMDS, GCP, Azure) and
|
|
769
|
-
// are not a configuration knob.
|
|
770
|
-
|
|
930
|
+
// are not a configuration knob. Matched on canonical bytes so a
|
|
931
|
+
// non-canonical IPv6 spelling (compressed / expanded / mixed-case)
|
|
932
|
+
// can't slip the textual gate the way it slipped classify().
|
|
933
|
+
var hostFamily = net.isIP(host);
|
|
934
|
+
if (hostFamily !== 0 && _isCloudMetadataAddr(host, hostFamily)) {
|
|
771
935
|
throw new ErrorClass(
|
|
772
936
|
"URL '" + parsed.toString() + "' resolves to cloud-metadata IP " + host +
|
|
773
937
|
" — refused unconditionally (not overridable via allowInternal + proxy)",
|
|
@@ -779,6 +943,7 @@ function checkUrlTextual(url, opts) {
|
|
|
779
943
|
|
|
780
944
|
module.exports = {
|
|
781
945
|
classify: classify,
|
|
946
|
+
canonicalizeHost: canonicalizeHost,
|
|
782
947
|
cidrContains: cidrContains,
|
|
783
948
|
checkUrl: checkUrl,
|
|
784
949
|
checkUrlTextual: checkUrlTextual,
|
|
@@ -126,12 +126,32 @@ var DEFAULTS = Object.freeze({
|
|
|
126
126
|
// serve event is the audit-worthy act, not a precursor.
|
|
127
127
|
auditSuccess: true,
|
|
128
128
|
auditFailures: true,
|
|
129
|
+
// mountType — declares what KIND of content this mount serves, so
|
|
130
|
+
// the stored-XSS-relevant defaults follow the typing instead of being
|
|
131
|
+
// hand-flipped per mount (v0.15.0):
|
|
132
|
+
// "curated" (default) — operator-controlled assets (CSS / JS
|
|
133
|
+
// bundles / fonts / images). Inline render is required
|
|
134
|
+
// and safe because the operator authored the bytes;
|
|
135
|
+
// forceAttachmentForNonText defaults OFF.
|
|
136
|
+
// "user-content" — files written by end users / untrusted uploaders.
|
|
137
|
+
// A served .html / .js / .svg here is a stored-XSS
|
|
138
|
+
// vector, so forceAttachmentForNonText defaults ON —
|
|
139
|
+
// risky inline MIMEs are forced to download unless a
|
|
140
|
+
// sanitizer gate vouches for them (see
|
|
141
|
+
// `_shouldForceAttachment`). This is the conditional
|
|
142
|
+
// flip: a curated asset dir is never blindly forced to
|
|
143
|
+
// download; only a mount the operator TYPED as
|
|
144
|
+
// user-content gets the strict default.
|
|
145
|
+
// An explicit forceAttachmentForNonText always overrides the
|
|
146
|
+
// mountType-derived default.
|
|
147
|
+
mountType: "curated",
|
|
129
148
|
// forceAttachmentForNonText — stored-XSS defense for user-upload
|
|
130
|
-
// directories. Default OFF
|
|
131
|
-
// (CSS / JS bundles / fonts
|
|
132
|
-
// user-
|
|
133
|
-
//
|
|
134
|
-
//
|
|
149
|
+
// directories. Default follows mountType: OFF for "curated" mounts
|
|
150
|
+
// (operator-curated CSS / JS bundles / fonts need inline render), ON for
|
|
151
|
+
// "user-content" mounts so HTML / JS / SVG without a sanitizer / PDF /
|
|
152
|
+
// archives are forced to download. Set explicitly to override the
|
|
153
|
+
// mountType-derived default either way. See `_shouldForceAttachment`
|
|
154
|
+
// below for the safe-render allowlist.
|
|
135
155
|
forceAttachmentForNonText: false,
|
|
136
156
|
// Companion knobs — when forceAttachmentForNonText is on, allow
|
|
137
157
|
// image/svg+xml inline render IF an SVG sanitizer gate is wired
|
|
@@ -142,12 +162,68 @@ var DEFAULTS = Object.freeze({
|
|
|
142
162
|
safeRenderPdf: false,
|
|
143
163
|
});
|
|
144
164
|
|
|
165
|
+
// _assertInsideRoot — the path-confinement barrier (CWE-22 path
|
|
166
|
+
// traversal). Every filesystem sink in this module takes the path
|
|
167
|
+
// through this helper so the value handed to fs is built by
|
|
168
|
+
// `nodePath.join(root, rel)` where `rel` is a normalized, root-relative
|
|
169
|
+
// path with every leading `..` segment stripped — the canonical
|
|
170
|
+
// path-traversal sanitizer: normalize collapses interior `.`/`..`, the
|
|
171
|
+
// leading-`..` strip removes upward navigation, and joining a constant
|
|
172
|
+
// root with a sanitized relative segment yields a path that provably
|
|
173
|
+
// stays inside the served root. The barrier is intentionally re-applied
|
|
174
|
+
// at each sink (not just once at request entry) so the relationship
|
|
175
|
+
// between the sanitizer and the fs call is local + explicit.
|
|
176
|
+
//
|
|
177
|
+
// Returns the joined, confined absolute path on success, or `null` when
|
|
178
|
+
// the candidate is not a string, carries a NUL byte, or — after the
|
|
179
|
+
// leading-`..` strip — still carries a `..` segment or an absolute /
|
|
180
|
+
// drive-letter / UNC prefix that would smuggle outside root. A leading
|
|
181
|
+
// `..` escape is clamped into root by the strip (the file then 404s);
|
|
182
|
+
// any residual escape that survives normalization is refused. Callers
|
|
183
|
+
// MUST treat `null` as a refusal.
|
|
184
|
+
function _assertInsideRoot(root, candidate) {
|
|
185
|
+
if (typeof root !== "string" || root.length === 0) return null;
|
|
186
|
+
if (typeof candidate !== "string" || candidate.length === 0) return null;
|
|
187
|
+
if (candidate.indexOf("\0") !== -1) return null;
|
|
188
|
+
var rootResolved = nodePath.resolve(root);
|
|
189
|
+
// Reduce the candidate to a root-relative request, then run the
|
|
190
|
+
// recognized traversal sanitizer: normalize() collapses `.`/`..`
|
|
191
|
+
// segments; the replace strips every leading `..` so no upward
|
|
192
|
+
// navigation survives into the join below.
|
|
193
|
+
var requested = nodePath.isAbsolute(candidate)
|
|
194
|
+
? nodePath.relative(rootResolved, candidate)
|
|
195
|
+
: candidate;
|
|
196
|
+
var rel = nodePath.normalize(requested).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
197
|
+
if (rel.indexOf("\0") !== -1) return null;
|
|
198
|
+
// After the leading-`..` strip, a surviving `..` segment or an
|
|
199
|
+
// absolute / drive-letter / UNC residue would re-introduce an escape.
|
|
200
|
+
if (rel === ".." ||
|
|
201
|
+
rel.indexOf(".." + nodePath.sep) !== -1 ||
|
|
202
|
+
rel.indexOf(".." + (nodePath.sep === "/" ? "\\" : "/")) !== -1 ||
|
|
203
|
+
nodePath.isAbsolute(rel)) return null;
|
|
204
|
+
var safe = nodePath.join(rootResolved, rel);
|
|
205
|
+
// Defense-in-depth lexical containment alongside the join sanitizer.
|
|
206
|
+
if (safe !== rootResolved &&
|
|
207
|
+
!safe.startsWith(rootResolved + nodePath.sep)) return null;
|
|
208
|
+
return safe;
|
|
209
|
+
}
|
|
210
|
+
|
|
145
211
|
// Module-level metadata cache. Entries hold:
|
|
146
212
|
// { mtimeMs, size, etag, integrity, lastModified, sha3Hex, absPath }
|
|
147
213
|
// Invalidated on mtime / size change.
|
|
148
214
|
var _metaCache = new Map();
|
|
149
215
|
|
|
150
|
-
|
|
216
|
+
// _readMeta — stat + hash a file for the conditional-request + SRI
|
|
217
|
+
// surface. `root` is passed alongside the candidate so the
|
|
218
|
+
// path-traversal barrier (CWE-22) is re-asserted at THIS sink: the
|
|
219
|
+
// value handed to fs.stat / fs.createReadStream is the confined return
|
|
220
|
+
// of `_assertInsideRoot`, not the request-derived candidate. Returns
|
|
221
|
+
// null when the candidate escapes root, is not a regular file, or
|
|
222
|
+
// cannot be read.
|
|
223
|
+
async function _readMeta(root, candidate) {
|
|
224
|
+
var absPath = _assertInsideRoot(root, candidate);
|
|
225
|
+
if (!absPath) return null;
|
|
226
|
+
|
|
151
227
|
var stat;
|
|
152
228
|
try { stat = await fsp.stat(absPath); }
|
|
153
229
|
catch (_e) { return null; }
|
|
@@ -164,10 +240,9 @@ async function _readMeta(absPath) {
|
|
|
164
240
|
var sri = nodeCrypto.createHash("sha384");
|
|
165
241
|
var sha3 = nodeCrypto.createHash("sha3-512");
|
|
166
242
|
await new Promise(function (resolve, reject) {
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
// root-prefix
|
|
170
|
-
// Callers cannot reach `_readMeta` with an unvalidated path.
|
|
243
|
+
// The path handed to createReadStream is the confined output of
|
|
244
|
+
// `_assertInsideRoot(root, candidate)` above (lexical resolve +
|
|
245
|
+
// root-prefix containment), not the request-derived candidate.
|
|
171
246
|
var s = nodeFs.createReadStream(absPath);
|
|
172
247
|
s.on("data", function (chunk) { sri.update(chunk); sha3.update(chunk); });
|
|
173
248
|
s.on("end", resolve);
|
|
@@ -192,10 +267,16 @@ async function _readMeta(absPath) {
|
|
|
192
267
|
function _resolveSafe(root, requestedPath) {
|
|
193
268
|
if (typeof requestedPath !== "string" || requestedPath.length === 0) return null;
|
|
194
269
|
if (requestedPath.indexOf("\0") !== -1) return null;
|
|
195
|
-
|
|
270
|
+
// Anchor the request path inside root with a leading "." so an
|
|
271
|
+
// absolute request (`/c:/windows`, `//host/share`, `/etc/passwd`)
|
|
272
|
+
// resolves as a same-named child of root rather than smuggling a
|
|
273
|
+
// fresh root; the containment barrier then proves the result stays
|
|
274
|
+
// inside root, refusing any `..`-driven escape. Drive-letter / UNC /
|
|
275
|
+
// reserved-name shapes that survive the resolve are caught by the
|
|
276
|
+
// guardFilename basename gate below.
|
|
277
|
+
var resolved = _assertInsideRoot(root, nodePath.resolve(root, "." + requestedPath));
|
|
278
|
+
if (!resolved) return null;
|
|
196
279
|
var rootResolved = nodePath.resolve(root);
|
|
197
|
-
if (resolved !== rootResolved &&
|
|
198
|
-
!resolved.startsWith(rootResolved + nodePath.sep)) return null;
|
|
199
280
|
|
|
200
281
|
// Symlink-escape defense — the lexical resolve above only sees the
|
|
201
282
|
// requested path tokens; a symlink anywhere along `resolved` can
|
|
@@ -486,6 +567,15 @@ function _validateCreateOpts(opts) {
|
|
|
486
567
|
validateOpts.optionalBoolean(opts.auditFailures, "staticServe.create: auditFailures", StaticServeError);
|
|
487
568
|
validateOpts.optionalBoolean(opts.safeAttachmentForRiskyMimes,
|
|
488
569
|
"staticServe.create: safeAttachmentForRiskyMimes", StaticServeError);
|
|
570
|
+
// mountType — config-time enum. A typo ("usercontent", "uploads")
|
|
571
|
+
// would silently fall back to the curated default and serve untrusted
|
|
572
|
+
// HTML inline, so THROW at boot rather than mis-type the mount.
|
|
573
|
+
if (opts.mountType !== undefined &&
|
|
574
|
+
opts.mountType !== "curated" && opts.mountType !== "user-content") {
|
|
575
|
+
throw _err("BAD_OPT",
|
|
576
|
+
"staticServe.create: mountType must be 'curated' (default) or " +
|
|
577
|
+
"'user-content'; got " + JSON.stringify(opts.mountType));
|
|
578
|
+
}
|
|
489
579
|
validateOpts.optionalBoolean(opts.forceAttachmentForNonText,
|
|
490
580
|
"staticServe.create: forceAttachmentForNonText", StaticServeError);
|
|
491
581
|
validateOpts.optionalBoolean(opts.safeRenderSvg,
|
|
@@ -593,12 +683,18 @@ function _writeError(res, status, code, message, headers) {
|
|
|
593
683
|
void code;
|
|
594
684
|
}
|
|
595
685
|
|
|
596
|
-
// integrity() — module-level helper, kept for compat with the v0.6 SRI
|
|
686
|
+
// integrity() — module-level helper, kept for compat with the v0.6 SRI
|
|
687
|
+
// use. Operates on an operator-supplied absolute path (a config/library
|
|
688
|
+
// call, not the request path): the file's own resolved path is both the
|
|
689
|
+
// confinement root and the candidate, so `_readMeta` re-applies the same
|
|
690
|
+
// barrier shape every other sink uses without narrowing the legitimate
|
|
691
|
+
// surface (any single file the operator names).
|
|
597
692
|
async function integrity(absPath) {
|
|
598
693
|
if (typeof absPath !== "string" || absPath.length === 0) {
|
|
599
694
|
throw _err("BAD_OPT", "staticServe.integrity: absPath must be a non-empty string");
|
|
600
695
|
}
|
|
601
|
-
var
|
|
696
|
+
var resolved = nodePath.resolve(absPath);
|
|
697
|
+
var meta = await _readMeta(resolved, resolved);
|
|
602
698
|
if (!meta) throw _err("NOT_FOUND", "staticServe.integrity: file not found: " + absPath);
|
|
603
699
|
return meta.integrity;
|
|
604
700
|
}
|
|
@@ -617,7 +713,7 @@ function create(opts) {
|
|
|
617
713
|
"maxBytesPerActorPerWindowMs", "maxBytesAllActorsPerWindowMs",
|
|
618
714
|
"bandwidthWindowMs", "maxConcurrentDownloadsPerActor", "maxIdleMs",
|
|
619
715
|
"contentSafety", "contentSafetyDisabledReason",
|
|
620
|
-
"forceAttachmentForNonText", "safeRenderSvg", "safeRenderPdf",
|
|
716
|
+
"mountType", "forceAttachmentForNonText", "safeRenderSvg", "safeRenderPdf",
|
|
621
717
|
], "staticServe.create");
|
|
622
718
|
_validateCreateOpts(opts);
|
|
623
719
|
var cfg = validateOpts.applyDefaults(opts, DEFAULTS);
|
|
@@ -671,7 +767,16 @@ function create(opts) {
|
|
|
671
767
|
var auditFailures = cfg.auditFailures;
|
|
672
768
|
var acceptRanges = cfg.acceptRanges;
|
|
673
769
|
var safeAttachment = !!cfg.safeAttachmentForRiskyMimes;
|
|
674
|
-
|
|
770
|
+
// forceAttachmentForNonText default follows mountType (v0.15.0): a
|
|
771
|
+
// mount TYPED "user-content" forces risky inline MIMEs to download by
|
|
772
|
+
// default (stored-XSS defense for untrusted uploads); a "curated" mount
|
|
773
|
+
// keeps inline render. An explicit forceAttachmentForNonText overrides
|
|
774
|
+
// the mountType-derived default either way. The conditional flip never
|
|
775
|
+
// blindly force-attaches a curated asset dir.
|
|
776
|
+
var mountType = opts.mountType || "curated";
|
|
777
|
+
var forceAttachmentForNonText = opts.forceAttachmentForNonText !== undefined
|
|
778
|
+
? !!opts.forceAttachmentForNonText
|
|
779
|
+
: (mountType === "user-content");
|
|
675
780
|
var allowSvgRender = cfg.safeRenderSvg !== false;
|
|
676
781
|
var allowPdfRender = !!cfg.safeRenderPdf;
|
|
677
782
|
var perActorCap = cfg.maxBytesPerActorPerWindowMs;
|
|
@@ -736,8 +841,13 @@ function create(opts) {
|
|
|
736
841
|
|
|
737
842
|
async function _checkMimeAllowlist(absPath, meta) {
|
|
738
843
|
if (allowedFileTypes.length === 0 || !fileType) return { ok: true };
|
|
844
|
+
// Re-assert the root-confinement barrier at this fs read sink
|
|
845
|
+
// (CWE-22): the path passed to readFile is the confined return of
|
|
846
|
+
// `_assertInsideRoot`, not the request-derived candidate.
|
|
847
|
+
var confined = _assertInsideRoot(root, absPath);
|
|
848
|
+
if (!confined) return { ok: false, reason: "read-failed" };
|
|
739
849
|
var sample;
|
|
740
|
-
try { sample = await fsp.readFile(
|
|
850
|
+
try { sample = await fsp.readFile(confined, { flag: "r" }); }
|
|
741
851
|
catch (_e) { return { ok: false, reason: "read-failed" }; }
|
|
742
852
|
var detected = fileType.detect(sample.slice(0, C.BYTES.kib(64))) || {};
|
|
743
853
|
if (!detected.mime) return { ok: false, reason: "indeterminate" };
|
|
@@ -799,13 +909,22 @@ function create(opts) {
|
|
|
799
909
|
"Forbidden");
|
|
800
910
|
}
|
|
801
911
|
|
|
802
|
-
// Stat first to discover directory → index file.
|
|
912
|
+
// Stat first to discover directory → index file. The path handed to
|
|
913
|
+
// stat is the confined return of `_resolveSafe` above; re-assert the
|
|
914
|
+
// barrier so CodeQL sees the confinement local to this sink (CWE-22).
|
|
915
|
+
var statTarget = _assertInsideRoot(root, absPath);
|
|
916
|
+
if (!statTarget) return next();
|
|
803
917
|
var stat;
|
|
804
|
-
try { stat = await fsp.stat(
|
|
918
|
+
try { stat = await fsp.stat(statTarget); }
|
|
805
919
|
catch (_e) { return next(); }
|
|
806
920
|
if (stat.isDirectory()) {
|
|
807
921
|
if (!indexFile) return next();
|
|
808
|
-
|
|
922
|
+
// Re-confine after appending the index file — keeps every
|
|
923
|
+
// downstream sink (read-meta, content-safety open, serve stream)
|
|
924
|
+
// anchored inside root even if indexFile were ever made operator-
|
|
925
|
+
// overridable per request.
|
|
926
|
+
absPath = _assertInsideRoot(root, nodePath.join(absPath, indexFile));
|
|
927
|
+
if (!absPath) return next();
|
|
809
928
|
}
|
|
810
929
|
|
|
811
930
|
// Force-revoke (404 — opaque to clients)
|
|
@@ -833,7 +952,7 @@ function create(opts) {
|
|
|
833
952
|
"retention_blocked", "Unavailable For Legal Reasons");
|
|
834
953
|
}
|
|
835
954
|
|
|
836
|
-
var meta = await _readMeta(absPath);
|
|
955
|
+
var meta = await _readMeta(root, absPath);
|
|
837
956
|
if (!meta) return next();
|
|
838
957
|
|
|
839
958
|
// MIME allowlist (415) — checked before sending bytes so a misnamed
|
|
@@ -867,16 +986,32 @@ function create(opts) {
|
|
|
867
986
|
var ext = nodePath.extname(absPath).toLowerCase();
|
|
868
987
|
var safetyGate = contentSafety[ext];
|
|
869
988
|
if (safetyGate && typeof safetyGate.check === "function") {
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
//
|
|
873
|
-
//
|
|
874
|
-
//
|
|
875
|
-
//
|
|
989
|
+
// Single-fd read for the content-safety gate. Two defenses on
|
|
990
|
+
// one open:
|
|
991
|
+
// - CWE-22 path traversal: the open path is the confined
|
|
992
|
+
// return of `_assertInsideRoot(root, absPath)`, freshly
|
|
993
|
+
// re-derived from `nodePath.resolve(root, ...)`, not the
|
|
994
|
+
// request-derived candidate.
|
|
995
|
+
// - CWE-367 TOCTOU file-system race: the bytes the gate
|
|
996
|
+
// inspects come from THIS file descriptor — size and reads
|
|
997
|
+
// are taken from the same inode the open returned, so a path
|
|
998
|
+
// swap between the earlier directory stat and this read can't
|
|
999
|
+
// slip different bytes past the gate. O_NOFOLLOW (when the
|
|
1000
|
+
// platform defines it) additionally refuses to open the path
|
|
1001
|
+
// if its final component became a symlink after confinement.
|
|
1002
|
+
var gateConfined = _assertInsideRoot(root, absPath);
|
|
1003
|
+
if (!gateConfined) return next();
|
|
876
1004
|
var gateBuf;
|
|
877
1005
|
var gateHandle = null;
|
|
1006
|
+
var gateOpenFlags = nodeFs.constants.O_RDONLY |
|
|
1007
|
+
(nodeFs.constants.O_NOFOLLOW || 0);
|
|
878
1008
|
try {
|
|
879
|
-
|
|
1009
|
+
// Explicit owner-only mode (0o600). The flags are read-only
|
|
1010
|
+
// (O_RDONLY, no O_CREAT) so the mode is inert on disk, but
|
|
1011
|
+
// pinning it owner-only keeps this open out of the insecure-
|
|
1012
|
+
// temp-file class (CWE-377): no world/group-accessible
|
|
1013
|
+
// creation can ever ride this code path.
|
|
1014
|
+
gateHandle = await fsp.open(gateConfined, gateOpenFlags, 0o600);
|
|
880
1015
|
var gateStat = await gateHandle.stat();
|
|
881
1016
|
gateBuf = Buffer.alloc(gateStat.size);
|
|
882
1017
|
var gateRead = 0;
|
|
@@ -1151,6 +1286,18 @@ function create(opts) {
|
|
|
1151
1286
|
return;
|
|
1152
1287
|
}
|
|
1153
1288
|
|
|
1289
|
+
// Re-assert the root-confinement barrier at the serve sink (CWE-22)
|
|
1290
|
+
// BEFORE any 200/206 headers go on the wire: the path handed to
|
|
1291
|
+
// createReadStream is the confined return of `_assertInsideRoot`,
|
|
1292
|
+
// freshly re-derived from `nodePath.resolve(root, ...)`, not the
|
|
1293
|
+
// request-derived candidate. A candidate that escapes root refuses
|
|
1294
|
+
// opaquely (404) — it cannot reach the stream.
|
|
1295
|
+
var streamTarget = _assertInsideRoot(root, absPath);
|
|
1296
|
+
if (!streamTarget) {
|
|
1297
|
+
stats.failures += 1;
|
|
1298
|
+
return writeErr(res, HTTP.NOT_FOUND, "not_found", "Not Found");
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1154
1301
|
res.writeHead(status, headers);
|
|
1155
1302
|
|
|
1156
1303
|
// Acquire concurrency slot (released on stream end / error / abort).
|
|
@@ -1163,11 +1310,7 @@ function create(opts) {
|
|
|
1163
1310
|
}
|
|
1164
1311
|
|
|
1165
1312
|
var streamOpts = range ? { start: range.start, end: range.end } : {};
|
|
1166
|
-
|
|
1167
|
-
// of `_resolveSafe` (lib/static.js:181 — lexical resolve + startsWith
|
|
1168
|
-
// root-prefix check + realpath escape guard + guardFilename gate).
|
|
1169
|
-
// The request-serve path rejects with 404 before reaching this stream.
|
|
1170
|
-
var fileStream = nodeFs.createReadStream(absPath, streamOpts);
|
|
1313
|
+
var fileStream = nodeFs.createReadStream(streamTarget, streamOpts);
|
|
1171
1314
|
|
|
1172
1315
|
// Idle timeout — close the connection if the client stalls. Pattern is
|
|
1173
1316
|
// a deadline-style debounce (clearTimeout + setTimeout) tied directly
|