@blamejs/blamejs-shop 0.0.44
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 +87 -0
- package/LICENSE +17 -0
- package/README.md +117 -0
- package/SECURITY.md +139 -0
- package/lib/admin.js +952 -0
- package/lib/analytics.js +267 -0
- package/lib/cart.js +279 -0
- package/lib/catalog-import.js +344 -0
- package/lib/catalog.js +769 -0
- package/lib/checkout.js +320 -0
- package/lib/config.js +151 -0
- package/lib/customers.js +322 -0
- package/lib/email.js +242 -0
- package/lib/externaldb-d1.js +283 -0
- package/lib/index.js +57 -0
- package/lib/inventory-alerts.js +198 -0
- package/lib/newsletter.js +142 -0
- package/lib/order.js +380 -0
- package/lib/payment.js +318 -0
- package/lib/pricing.js +185 -0
- package/lib/r2-bridge.js +169 -0
- package/lib/shipping.js +185 -0
- package/lib/storefront.js +2160 -0
- package/lib/subscriptions.js +410 -0
- package/lib/tax.js +161 -0
- package/lib/theme.js +194 -0
- package/lib/vendor/MANIFEST.json +19 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +23 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +34 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/project.yaml +16 -0
- package/lib/vendor/blamejs/.dockerignore +45 -0
- package/lib/vendor/blamejs/.gitattributes +42 -0
- package/lib/vendor/blamejs/.github/CODEOWNERS +4 -0
- package/lib/vendor/blamejs/.github/FUNDING.yml +2 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/bug_report.md +58 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/feature_request.md +99 -0
- package/lib/vendor/blamejs/.github/PULL_REQUEST_TEMPLATE.md +77 -0
- package/lib/vendor/blamejs/.github/dependabot.yml +37 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +148 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +107 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +122 -0
- package/lib/vendor/blamejs/.github/workflows/ci.yml +511 -0
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +50 -0
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +655 -0
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +406 -0
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +101 -0
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +134 -0
- package/lib/vendor/blamejs/.gitignore +102 -0
- package/lib/vendor/blamejs/.gitleaks.toml +166 -0
- package/lib/vendor/blamejs/.hadolint.yaml +18 -0
- package/lib/vendor/blamejs/.npmrc +5 -0
- package/lib/vendor/blamejs/.pinact.yaml +17 -0
- package/lib/vendor/blamejs/ARCHITECTURE.md +158 -0
- package/lib/vendor/blamejs/CHANGELOG.md +1351 -0
- package/lib/vendor/blamejs/CODE_OF_CONDUCT.md +86 -0
- package/lib/vendor/blamejs/CONTRIBUTING.md +156 -0
- package/lib/vendor/blamejs/GOVERNANCE.md +201 -0
- package/lib/vendor/blamejs/LICENSE +201 -0
- package/lib/vendor/blamejs/LTS-CALENDAR.md +29 -0
- package/lib/vendor/blamejs/MIGRATING.md +29 -0
- package/lib/vendor/blamejs/NOTICE +81 -0
- package/lib/vendor/blamejs/README.md +304 -0
- package/lib/vendor/blamejs/SECURITY.md +432 -0
- package/lib/vendor/blamejs/api-snapshot.json +48709 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.png +0 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.svg +129 -0
- package/lib/vendor/blamejs/bench/README.md +77 -0
- package/lib/vendor/blamejs/bench/_helpers.js +70 -0
- package/lib/vendor/blamejs/bench/baseline.json +183 -0
- package/lib/vendor/blamejs/bench/crypto-hash.bench.js +19 -0
- package/lib/vendor/blamejs/bench/crypto-symmetric.bench.js +28 -0
- package/lib/vendor/blamejs/bench/run.js +140 -0
- package/lib/vendor/blamejs/bench/safe-json.bench.js +31 -0
- package/lib/vendor/blamejs/bin/blamejs.js +13 -0
- package/lib/vendor/blamejs/docker/caddy/Caddyfile +46 -0
- package/lib/vendor/blamejs/docker/coredns/Corefile +37 -0
- package/lib/vendor/blamejs/docker/haproxy/haproxy.cfg +52 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +118 -0
- package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +87 -0
- package/lib/vendor/blamejs/docker/mitmproxy/config.yaml +16 -0
- package/lib/vendor/blamejs/docker/mongo/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/mysql/my.cnf +12 -0
- package/lib/vendor/blamejs/docker/nats/nats.conf +33 -0
- package/lib/vendor/blamejs/docker/postgres/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/postgres/postgresql.conf +18 -0
- package/lib/vendor/blamejs/docker/rabbitmq/rabbitmq.conf +18 -0
- package/lib/vendor/blamejs/docker/redis/redis.conf +15 -0
- package/lib/vendor/blamejs/docker/squid/squid.conf +24 -0
- package/lib/vendor/blamejs/docker/syslog/syslog-ng.conf +34 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +545 -0
- package/lib/vendor/blamejs/docs/cis-postgres-crosswalk.md +102 -0
- package/lib/vendor/blamejs/docs/cis-sqlite-equivalent.md +92 -0
- package/lib/vendor/blamejs/eslint.config.mjs +204 -0
- package/lib/vendor/blamejs/examples/wiki/Caddyfile +40 -0
- package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +218 -0
- package/lib/vendor/blamejs/examples/wiki/Dockerfile +120 -0
- package/lib/vendor/blamejs/examples/wiki/README.md +157 -0
- package/lib/vendor/blamejs/examples/wiki/cli-snapshot.json +250 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +231 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +166 -0
- package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +217 -0
- package/lib/vendor/blamejs/examples/wiki/lib/auto-site-entries.js +139 -0
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +555 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-cli.js +507 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-env-vars.js +435 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-errors.js +282 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-vendored-deps.js +321 -0
- package/lib/vendor/blamejs/examples/wiki/lib/nav.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/lib/opts-resolver.js +75 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +508 -0
- package/lib/vendor/blamejs/examples/wiki/lib/section.js +276 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +587 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +318 -0
- package/lib/vendor/blamejs/examples/wiki/lib/symbol-index.js +122 -0
- package/lib/vendor/blamejs/examples/wiki/migrations/0001-pages-schema.js +74 -0
- package/lib/vendor/blamejs/examples/wiki/package.json +18 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.png +0 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.svg +129 -0
- package/lib/vendor/blamejs/examples/wiki/public/robots.txt +5 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/MANIFEST.json +30 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.css +1 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/public/wiki.css +1250 -0
- package/lib/vendor/blamejs/examples/wiki/routes/admin.js +366 -0
- package/lib/vendor/blamejs/examples/wiki/routes/integration.js +230 -0
- package/lib/vendor/blamejs/examples/wiki/routes/pages.js +266 -0
- package/lib/vendor/blamejs/examples/wiki/scripts/backfill-module-metadata.js +214 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/0001-default-pages.js +35 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/_index.js +34 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/api.js +76 -0
- package/lib/vendor/blamejs/examples/wiki/server.js +129 -0
- package/lib/vendor/blamejs/examples/wiki/site.config.js +197 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/README.md +38 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/auth/password-hash.example.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/src/editor.js +103 -0
- package/lib/vendor/blamejs/examples/wiki/src/wiki.js +349 -0
- package/lib/vendor/blamejs/examples/wiki/test/AUDIT.md +155 -0
- package/lib/vendor/blamejs/examples/wiki/test/codebase-patterns.test.js +594 -0
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +741 -0
- package/lib/vendor/blamejs/examples/wiki/test/find-missing-pages.js +254 -0
- package/lib/vendor/blamejs/examples/wiki/test/integration.js +391 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-cli-snapshot.js +379 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-env-snapshot.js +346 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-nav-coverage.js +212 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-site-coverage.js +252 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-source-comment-blocks.js +107 -0
- package/lib/vendor/blamejs/examples/wiki/views/_layout.html +115 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/api-keys.html +51 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/dashboard.html +22 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/edit.html +17 -0
- package/lib/vendor/blamejs/examples/wiki/views/home.html +85 -0
- package/lib/vendor/blamejs/examples/wiki/views/login.html +18 -0
- package/lib/vendor/blamejs/examples/wiki/views/page.html +5 -0
- package/lib/vendor/blamejs/examples/wiki/views/partials/nav.html +13 -0
- package/lib/vendor/blamejs/examples/wiki/views/search.html +19 -0
- package/lib/vendor/blamejs/examples/wiki/wiki.config.js +15 -0
- package/lib/vendor/blamejs/fuzz/README.md +137 -0
- package/lib/vendor/blamejs/fuzz/_expected.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-agent-registry.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-csv.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/01-basic.csv +3 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/02-formula.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/03-hyperlink.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-dsn.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-email.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-email_seed_corpus/01-basic.eml +5 -0
- package/lib/vendor/blamejs/fuzz/guard-envelope.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-payload.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-topic.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-html.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/01-basic.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/02-script.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/03-event.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/04-jsurl.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-idempotency-key.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-imap-command.fuzz.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-jmap.fuzz.js +41 -0
- package/lib/vendor/blamejs/fuzz/guard-json.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/01-basic.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/02-proto.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/03-dupkey.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/04-nan.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/05-bom.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-list-id.fuzz.js +21 -0
- package/lib/vendor/blamejs/fuzz/guard-list-unsubscribe.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-compose.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-move.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-query.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-reply.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-sieve.fuzz.js +36 -0
- package/lib/vendor/blamejs/fuzz/guard-managesieve-command.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/01-basic.md +2 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/02-jsurl.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/03-jsimg.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-message-id.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-pop3-command.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-posture-chain.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-saga-config.fuzz.js +32 -0
- package/lib/vendor/blamejs/fuzz/guard-smtp-command.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-snapshot-envelope.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-stream-args.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-svg.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/01-basic.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/02-script.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-tenant-id.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-trace-context.fuzz.js +30 -0
- package/lib/vendor/blamejs/fuzz/guard-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/02-xxe.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/01-basic.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/02-anchor.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/03-norway.yaml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/04-multidoc.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini_seed_corpus/01-basic.ini +2 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml_seed_corpus/01-basic.toml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml_seed_corpus/01-basic.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/safe-decompress.fuzz.js +49 -0
- package/lib/vendor/blamejs/fuzz/safe-dns.fuzz.js +29 -0
- package/lib/vendor/blamejs/fuzz/safe-ical.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-icap.fuzz.js +42 -0
- package/lib/vendor/blamejs/fuzz/safe-json.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/01-object.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/02-array.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/03-string.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/04-proto.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/05-deep.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/02-filter.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/03-deepscan.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/04-slice.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-mime.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/safe-mount-info.fuzz.js +33 -0
- package/lib/vendor/blamejs/fuzz/safe-sieve.fuzz.js +28 -0
- package/lib/vendor/blamejs/fuzz/safe-smtp.fuzz.js +64 -0
- package/lib/vendor/blamejs/fuzz/safe-url.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/02-userinfo.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/03-dangerous.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/04-data.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/05-ipv6.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/06-idn.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-vcard.fuzz.js +16 -0
- package/lib/vendor/blamejs/index.js +678 -0
- package/lib/vendor/blamejs/keys/release-pqc-pub.json +7 -0
- package/lib/vendor/blamejs/lib/_test/crypto-fixtures.js +67 -0
- package/lib/vendor/blamejs/lib/a2a-tasks.js +598 -0
- package/lib/vendor/blamejs/lib/a2a.js +407 -0
- package/lib/vendor/blamejs/lib/acme.js +1448 -0
- package/lib/vendor/blamejs/lib/agent-audit.js +45 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +382 -0
- package/lib/vendor/blamejs/lib/agent-idempotency.js +497 -0
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +717 -0
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +366 -0
- package/lib/vendor/blamejs/lib/agent-saga.js +321 -0
- package/lib/vendor/blamejs/lib/agent-snapshot.js +676 -0
- package/lib/vendor/blamejs/lib/agent-stream.js +269 -0
- package/lib/vendor/blamejs/lib/agent-tenant.js +632 -0
- package/lib/vendor/blamejs/lib/agent-trace.js +281 -0
- package/lib/vendor/blamejs/lib/ai-adverse-decision.js +184 -0
- package/lib/vendor/blamejs/lib/ai-content-detect.js +268 -0
- package/lib/vendor/blamejs/lib/ai-input.js +201 -0
- package/lib/vendor/blamejs/lib/ai-model-manifest.js +363 -0
- package/lib/vendor/blamejs/lib/ai-pref.js +340 -0
- package/lib/vendor/blamejs/lib/api-key.js +721 -0
- package/lib/vendor/blamejs/lib/api-snapshot.js +458 -0
- package/lib/vendor/blamejs/lib/app-shutdown.js +557 -0
- package/lib/vendor/blamejs/lib/app.js +365 -0
- package/lib/vendor/blamejs/lib/archive.js +547 -0
- package/lib/vendor/blamejs/lib/arg-parser.js +697 -0
- package/lib/vendor/blamejs/lib/argon2-builtin.js +173 -0
- package/lib/vendor/blamejs/lib/asn1-der.js +424 -0
- package/lib/vendor/blamejs/lib/asyncapi-bindings.js +160 -0
- package/lib/vendor/blamejs/lib/asyncapi-traits.js +143 -0
- package/lib/vendor/blamejs/lib/asyncapi.js +575 -0
- package/lib/vendor/blamejs/lib/atomic-file.js +1023 -0
- package/lib/vendor/blamejs/lib/audit-chain.js +266 -0
- package/lib/vendor/blamejs/lib/audit-daily-review.js +389 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +751 -0
- package/lib/vendor/blamejs/lib/audit-tools.js +1113 -0
- package/lib/vendor/blamejs/lib/audit.js +1671 -0
- package/lib/vendor/blamejs/lib/auth/aal.js +169 -0
- package/lib/vendor/blamejs/lib/auth/access-lock.js +220 -0
- package/lib/vendor/blamejs/lib/auth/acr-vocabulary.js +265 -0
- package/lib/vendor/blamejs/lib/auth/ato-kill-switch.js +112 -0
- package/lib/vendor/blamejs/lib/auth/auth-time-tracker.js +111 -0
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +573 -0
- package/lib/vendor/blamejs/lib/auth/ciba.js +637 -0
- package/lib/vendor/blamejs/lib/auth/dpop.js +516 -0
- package/lib/vendor/blamejs/lib/auth/elevation-grant.js +306 -0
- package/lib/vendor/blamejs/lib/auth/fal.js +229 -0
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +681 -0
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +519 -0
- package/lib/vendor/blamejs/lib/auth/jwt.js +430 -0
- package/lib/vendor/blamejs/lib/auth/lockout.js +449 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +2141 -0
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +657 -0
- package/lib/vendor/blamejs/lib/auth/oid4vp.js +531 -0
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +600 -0
- package/lib/vendor/blamejs/lib/auth/passkey.js +676 -0
- package/lib/vendor/blamejs/lib/auth/password.js +693 -0
- package/lib/vendor/blamejs/lib/auth/saml.js +2109 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-disclosure.js +95 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-holder.js +225 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-issuer.js +197 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +728 -0
- package/lib/vendor/blamejs/lib/auth/status-list.js +272 -0
- package/lib/vendor/blamejs/lib/auth/step-up-policy.js +335 -0
- package/lib/vendor/blamejs/lib/auth/step-up.js +454 -0
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +505 -0
- package/lib/vendor/blamejs/lib/auth-header.js +148 -0
- package/lib/vendor/blamejs/lib/backup/bundle.js +265 -0
- package/lib/vendor/blamejs/lib/backup/crypto.js +176 -0
- package/lib/vendor/blamejs/lib/backup/index.js +1001 -0
- package/lib/vendor/blamejs/lib/backup/manifest.js +443 -0
- package/lib/vendor/blamejs/lib/boot-gates.js +174 -0
- package/lib/vendor/blamejs/lib/breach-deadline.js +272 -0
- package/lib/vendor/blamejs/lib/break-glass.js +1753 -0
- package/lib/vendor/blamejs/lib/budr.js +205 -0
- package/lib/vendor/blamejs/lib/bundler.js +461 -0
- package/lib/vendor/blamejs/lib/cache-redis.js +256 -0
- package/lib/vendor/blamejs/lib/cache-status.js +288 -0
- package/lib/vendor/blamejs/lib/cache.js +1331 -0
- package/lib/vendor/blamejs/lib/calendar.js +1240 -0
- package/lib/vendor/blamejs/lib/canonical-json.js +143 -0
- package/lib/vendor/blamejs/lib/cdn-cache-control.js +473 -0
- package/lib/vendor/blamejs/lib/cert.js +763 -0
- package/lib/vendor/blamejs/lib/chain-writer.js +259 -0
- package/lib/vendor/blamejs/lib/circuit-breaker.js +101 -0
- package/lib/vendor/blamejs/lib/cli-helpers.js +237 -0
- package/lib/vendor/blamejs/lib/cli.js +2328 -0
- package/lib/vendor/blamejs/lib/client-hints.js +318 -0
- package/lib/vendor/blamejs/lib/cloud-events.js +277 -0
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +317 -0
- package/lib/vendor/blamejs/lib/cluster-storage.js +351 -0
- package/lib/vendor/blamejs/lib/cluster.js +1017 -0
- package/lib/vendor/blamejs/lib/cms-codec.js +826 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +262 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-logging.js +190 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +205 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-risk.js +189 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +200 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act.js +821 -0
- package/lib/vendor/blamejs/lib/compliance-eaa.js +204 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-aliases.js +167 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fetcher.js +206 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fuzzy.js +297 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions.js +569 -0
- package/lib/vendor/blamejs/lib/compliance.js +1558 -0
- package/lib/vendor/blamejs/lib/config-drift.js +426 -0
- package/lib/vendor/blamejs/lib/config.js +446 -0
- package/lib/vendor/blamejs/lib/consent.js +369 -0
- package/lib/vendor/blamejs/lib/constants.js +209 -0
- package/lib/vendor/blamejs/lib/content-credentials.js +704 -0
- package/lib/vendor/blamejs/lib/cookies.js +560 -0
- package/lib/vendor/blamejs/lib/cra-report.js +299 -0
- package/lib/vendor/blamejs/lib/credential-hash.js +394 -0
- package/lib/vendor/blamejs/lib/crypto-field.js +1017 -0
- package/lib/vendor/blamejs/lib/crypto-hpke-pq.js +187 -0
- package/lib/vendor/blamejs/lib/crypto-hpke.js +256 -0
- package/lib/vendor/blamejs/lib/crypto.js +1908 -0
- package/lib/vendor/blamejs/lib/csp.js +271 -0
- package/lib/vendor/blamejs/lib/csv.js +418 -0
- package/lib/vendor/blamejs/lib/daemon.js +481 -0
- package/lib/vendor/blamejs/lib/dark-patterns.js +488 -0
- package/lib/vendor/blamejs/lib/data-act.js +328 -0
- package/lib/vendor/blamejs/lib/db-collection.js +587 -0
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +267 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +420 -0
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +333 -0
- package/lib/vendor/blamejs/lib/db-query.js +802 -0
- package/lib/vendor/blamejs/lib/db-role-context.js +50 -0
- package/lib/vendor/blamejs/lib/db-schema.js +322 -0
- package/lib/vendor/blamejs/lib/db.js +3111 -0
- package/lib/vendor/blamejs/lib/dbsc.js +299 -0
- package/lib/vendor/blamejs/lib/ddl-change-control.js +523 -0
- package/lib/vendor/blamejs/lib/deprecate.js +377 -0
- package/lib/vendor/blamejs/lib/dev.js +405 -0
- package/lib/vendor/blamejs/lib/dora.js +402 -0
- package/lib/vendor/blamejs/lib/dr-runbook.js +368 -0
- package/lib/vendor/blamejs/lib/dsr.js +1188 -0
- package/lib/vendor/blamejs/lib/dual-control.js +526 -0
- package/lib/vendor/blamejs/lib/early-hints.js +212 -0
- package/lib/vendor/blamejs/lib/error-page.js +420 -0
- package/lib/vendor/blamejs/lib/events.js +214 -0
- package/lib/vendor/blamejs/lib/external-db-migrate.js +659 -0
- package/lib/vendor/blamejs/lib/external-db.js +1877 -0
- package/lib/vendor/blamejs/lib/fapi2.js +394 -0
- package/lib/vendor/blamejs/lib/fda-21cfr11.js +395 -0
- package/lib/vendor/blamejs/lib/fdx.js +370 -0
- package/lib/vendor/blamejs/lib/fedcm.js +264 -0
- package/lib/vendor/blamejs/lib/file-type.js +360 -0
- package/lib/vendor/blamejs/lib/file-upload.js +1256 -0
- package/lib/vendor/blamejs/lib/flag-cache.js +136 -0
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +135 -0
- package/lib/vendor/blamejs/lib/flag-providers.js +279 -0
- package/lib/vendor/blamejs/lib/flag-targeting.js +210 -0
- package/lib/vendor/blamejs/lib/flag.js +346 -0
- package/lib/vendor/blamejs/lib/forms.js +525 -0
- package/lib/vendor/blamejs/lib/framework-error.js +724 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +845 -0
- package/lib/vendor/blamejs/lib/framework-sha1-hibp.js +34 -0
- package/lib/vendor/blamejs/lib/fsm.js +469 -0
- package/lib/vendor/blamejs/lib/gate-contract.js +1661 -0
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +261 -0
- package/lib/vendor/blamejs/lib/graphql-federation.js +234 -0
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +179 -0
- package/lib/vendor/blamejs/lib/guard-all.js +555 -0
- package/lib/vendor/blamejs/lib/guard-archive.js +901 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +451 -0
- package/lib/vendor/blamejs/lib/guard-cidr.js +676 -0
- package/lib/vendor/blamejs/lib/guard-csv.js +1176 -0
- package/lib/vendor/blamejs/lib/guard-domain.js +814 -0
- package/lib/vendor/blamejs/lib/guard-dsn.js +382 -0
- package/lib/vendor/blamejs/lib/guard-email.js +951 -0
- package/lib/vendor/blamejs/lib/guard-envelope.js +294 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +217 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +150 -0
- package/lib/vendor/blamejs/lib/guard-filename.js +956 -0
- package/lib/vendor/blamejs/lib/guard-graphql.js +731 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-aria.js +164 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-forms.js +144 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tables.js +154 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tagwalk.js +44 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag.js +470 -0
- package/lib/vendor/blamejs/lib/guard-html.js +1209 -0
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +151 -0
- package/lib/vendor/blamejs/lib/guard-image.js +584 -0
- package/lib/vendor/blamejs/lib/guard-imap-command.js +337 -0
- package/lib/vendor/blamejs/lib/guard-jmap.js +321 -0
- package/lib/vendor/blamejs/lib/guard-json.js +935 -0
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +512 -0
- package/lib/vendor/blamejs/lib/guard-jwt.js +772 -0
- package/lib/vendor/blamejs/lib/guard-list-id.js +318 -0
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +412 -0
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +282 -0
- package/lib/vendor/blamejs/lib/guard-mail-move.js +202 -0
- package/lib/vendor/blamejs/lib/guard-mail-query.js +310 -0
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +172 -0
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +207 -0
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +566 -0
- package/lib/vendor/blamejs/lib/guard-markdown.js +768 -0
- package/lib/vendor/blamejs/lib/guard-message-id.js +267 -0
- package/lib/vendor/blamejs/lib/guard-mime.js +609 -0
- package/lib/vendor/blamejs/lib/guard-oauth.js +650 -0
- package/lib/vendor/blamejs/lib/guard-pdf.js +569 -0
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +317 -0
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +201 -0
- package/lib/vendor/blamejs/lib/guard-regex.js +632 -0
- package/lib/vendor/blamejs/lib/guard-saga-config.js +157 -0
- package/lib/vendor/blamejs/lib/guard-shell.js +522 -0
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +594 -0
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +168 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +166 -0
- package/lib/vendor/blamejs/lib/guard-svg.js +1163 -0
- package/lib/vendor/blamejs/lib/guard-template.js +490 -0
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +138 -0
- package/lib/vendor/blamejs/lib/guard-time.js +586 -0
- package/lib/vendor/blamejs/lib/guard-trace-context.js +172 -0
- package/lib/vendor/blamejs/lib/guard-uuid.js +548 -0
- package/lib/vendor/blamejs/lib/guard-xml.js +666 -0
- package/lib/vendor/blamejs/lib/guard-yaml.js +726 -0
- package/lib/vendor/blamejs/lib/hal.js +125 -0
- package/lib/vendor/blamejs/lib/handlers.js +350 -0
- package/lib/vendor/blamejs/lib/honeytoken.js +168 -0
- package/lib/vendor/blamejs/lib/html-balance.js +347 -0
- package/lib/vendor/blamejs/lib/http-client-cache.js +923 -0
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +519 -0
- package/lib/vendor/blamejs/lib/http-client.js +2152 -0
- package/lib/vendor/blamejs/lib/http-message-signature.js +589 -0
- package/lib/vendor/blamejs/lib/http2-teardown.js +34 -0
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +398 -0
- package/lib/vendor/blamejs/lib/i18n.js +931 -0
- package/lib/vendor/blamejs/lib/iab-mspa.js +257 -0
- package/lib/vendor/blamejs/lib/iab-tcf.js +461 -0
- package/lib/vendor/blamejs/lib/importmap-integrity.js +90 -0
- package/lib/vendor/blamejs/lib/inbox.js +435 -0
- package/lib/vendor/blamejs/lib/incident-report.js +314 -0
- package/lib/vendor/blamejs/lib/ip-utils.js +102 -0
- package/lib/vendor/blamejs/lib/jobs.js +185 -0
- package/lib/vendor/blamejs/lib/jose-jwe-experimental.js +228 -0
- package/lib/vendor/blamejs/lib/jsonapi.js +230 -0
- package/lib/vendor/blamejs/lib/keychain.js +865 -0
- package/lib/vendor/blamejs/lib/lazy-require.js +48 -0
- package/lib/vendor/blamejs/lib/legal-hold.js +374 -0
- package/lib/vendor/blamejs/lib/local-db-thin.js +321 -0
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +369 -0
- package/lib/vendor/blamejs/lib/log-stream-local.js +146 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +410 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +286 -0
- package/lib/vendor/blamejs/lib/log-stream-syslog.js +310 -0
- package/lib/vendor/blamejs/lib/log-stream-webhook.js +199 -0
- package/lib/vendor/blamejs/lib/log-stream.js +584 -0
- package/lib/vendor/blamejs/lib/log.js +625 -0
- package/lib/vendor/blamejs/lib/lro.js +200 -0
- package/lib/vendor/blamejs/lib/mail-agent.js +786 -0
- package/lib/vendor/blamejs/lib/mail-arc-sign.js +417 -0
- package/lib/vendor/blamejs/lib/mail-arf.js +343 -0
- package/lib/vendor/blamejs/lib/mail-auth.js +2144 -0
- package/lib/vendor/blamejs/lib/mail-bimi.js +1047 -0
- package/lib/vendor/blamejs/lib/mail-bounce.js +955 -0
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +1286 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +789 -0
- package/lib/vendor/blamejs/lib/mail-crypto.js +108 -0
- package/lib/vendor/blamejs/lib/mail-dav.js +1224 -0
- package/lib/vendor/blamejs/lib/mail-deploy.js +1119 -0
- package/lib/vendor/blamejs/lib/mail-dkim.js +1250 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +448 -0
- package/lib/vendor/blamejs/lib/mail-helo.js +473 -0
- package/lib/vendor/blamejs/lib/mail-journal.js +435 -0
- package/lib/vendor/blamejs/lib/mail-mdn.js +424 -0
- package/lib/vendor/blamejs/lib/mail-rbl.js +392 -0
- package/lib/vendor/blamejs/lib/mail-require-tls.js +198 -0
- package/lib/vendor/blamejs/lib/mail-scan.js +502 -0
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +629 -0
- package/lib/vendor/blamejs/lib/mail-server-imap.js +1858 -0
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +1565 -0
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +908 -0
- package/lib/vendor/blamejs/lib/mail-server-mx.js +969 -0
- package/lib/vendor/blamejs/lib/mail-server-pop3.js +915 -0
- package/lib/vendor/blamejs/lib/mail-server-rate-limit.js +315 -0
- package/lib/vendor/blamejs/lib/mail-server-registry.js +378 -0
- package/lib/vendor/blamejs/lib/mail-server-submission.js +1396 -0
- package/lib/vendor/blamejs/lib/mail-server-tls.js +445 -0
- package/lib/vendor/blamejs/lib/mail-sieve.js +557 -0
- package/lib/vendor/blamejs/lib/mail-spam-score.js +284 -0
- package/lib/vendor/blamejs/lib/mail-srs.js +248 -0
- package/lib/vendor/blamejs/lib/mail-store-fts.js +394 -0
- package/lib/vendor/blamejs/lib/mail-store.js +929 -0
- package/lib/vendor/blamejs/lib/mail-unsubscribe.js +400 -0
- package/lib/vendor/blamejs/lib/mail.js +1971 -0
- package/lib/vendor/blamejs/lib/mcp-tool-registry.js +473 -0
- package/lib/vendor/blamejs/lib/mcp.js +950 -0
- package/lib/vendor/blamejs/lib/metrics.js +1503 -0
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/ai-act-disclosure.js +203 -0
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +981 -0
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +137 -0
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +171 -0
- package/lib/vendor/blamejs/lib/middleware/attach-user.js +220 -0
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +293 -0
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +1519 -0
- package/lib/vendor/blamejs/lib/middleware/bot-disclose.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +217 -0
- package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +122 -0
- package/lib/vendor/blamejs/lib/middleware/compose-pipeline.js +355 -0
- package/lib/vendor/blamejs/lib/middleware/compression.js +489 -0
- package/lib/vendor/blamejs/lib/middleware/cookies.js +130 -0
- package/lib/vendor/blamejs/lib/middleware/cors.js +386 -0
- package/lib/vendor/blamejs/lib/middleware/csp-nonce.js +388 -0
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +499 -0
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +243 -0
- package/lib/vendor/blamejs/lib/middleware/db-role-for.js +304 -0
- package/lib/vendor/blamejs/lib/middleware/dpop.js +402 -0
- package/lib/vendor/blamejs/lib/middleware/error-handler.js +69 -0
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +168 -0
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +110 -0
- package/lib/vendor/blamejs/lib/middleware/gpc.js +153 -0
- package/lib/vendor/blamejs/lib/middleware/headers.js +242 -0
- package/lib/vendor/blamejs/lib/middleware/health.js +438 -0
- package/lib/vendor/blamejs/lib/middleware/host-allowlist.js +189 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +964 -0
- package/lib/vendor/blamejs/lib/middleware/index.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/nel.js +214 -0
- package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +237 -0
- package/lib/vendor/blamejs/lib/middleware/no-cache.js +106 -0
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/protected-resource-metadata.js +277 -0
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +556 -0
- package/lib/vendor/blamejs/lib/middleware/request-id.js +79 -0
- package/lib/vendor/blamejs/lib/middleware/request-log.js +205 -0
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +138 -0
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +144 -0
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +290 -0
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +113 -0
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +97 -0
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +212 -0
- package/lib/vendor/blamejs/lib/middleware/require-step-up.js +226 -0
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +375 -0
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +285 -0
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +170 -0
- package/lib/vendor/blamejs/lib/middleware/span-http-server.js +280 -0
- package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +323 -0
- package/lib/vendor/blamejs/lib/middleware/sse.js +200 -0
- package/lib/vendor/blamejs/lib/middleware/trace-log-correlation.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/trace-propagate.js +148 -0
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +749 -0
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +164 -0
- package/lib/vendor/blamejs/lib/migration-files.js +37 -0
- package/lib/vendor/blamejs/lib/migrations.js +385 -0
- package/lib/vendor/blamejs/lib/mime-parse.js +198 -0
- package/lib/vendor/blamejs/lib/money.js +699 -0
- package/lib/vendor/blamejs/lib/mtls-ca.js +572 -0
- package/lib/vendor/blamejs/lib/mtls-engine-default.js +501 -0
- package/lib/vendor/blamejs/lib/network-byte-quota.js +308 -0
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +533 -0
- package/lib/vendor/blamejs/lib/network-dns.js +1930 -0
- package/lib/vendor/blamejs/lib/network-heartbeat.js +425 -0
- package/lib/vendor/blamejs/lib/network-nts.js +574 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +265 -0
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +836 -0
- package/lib/vendor/blamejs/lib/network-tls.js +3126 -0
- package/lib/vendor/blamejs/lib/network.js +346 -0
- package/lib/vendor/blamejs/lib/nis2-report.js +181 -0
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +293 -0
- package/lib/vendor/blamejs/lib/nonce-store.js +177 -0
- package/lib/vendor/blamejs/lib/notify.js +683 -0
- package/lib/vendor/blamejs/lib/ntp-check.js +458 -0
- package/lib/vendor/blamejs/lib/numeric-bounds.js +111 -0
- package/lib/vendor/blamejs/lib/numeric-checks.js +40 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +349 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +488 -0
- package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +351 -0
- package/lib/vendor/blamejs/lib/object-store/gcs.js +515 -0
- package/lib/vendor/blamejs/lib/object-store/http-put.js +153 -0
- package/lib/vendor/blamejs/lib/object-store/http-request.js +38 -0
- package/lib/vendor/blamejs/lib/object-store/index.js +197 -0
- package/lib/vendor/blamejs/lib/object-store/local.js +163 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +1133 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +957 -0
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +420 -0
- package/lib/vendor/blamejs/lib/observability-tracer.js +395 -0
- package/lib/vendor/blamejs/lib/observability.js +720 -0
- package/lib/vendor/blamejs/lib/openapi-paths-builder.js +248 -0
- package/lib/vendor/blamejs/lib/openapi-schema-walk.js +192 -0
- package/lib/vendor/blamejs/lib/openapi-security.js +169 -0
- package/lib/vendor/blamejs/lib/openapi-yaml.js +154 -0
- package/lib/vendor/blamejs/lib/openapi.js +489 -0
- package/lib/vendor/blamejs/lib/otel-export.js +278 -0
- package/lib/vendor/blamejs/lib/outbox.js +547 -0
- package/lib/vendor/blamejs/lib/pagination.js +542 -0
- package/lib/vendor/blamejs/lib/parsers/index.js +91 -0
- package/lib/vendor/blamejs/lib/parsers/safe-env.js +642 -0
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +293 -0
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +784 -0
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +390 -0
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +1015 -0
- package/lib/vendor/blamejs/lib/permissions.js +793 -0
- package/lib/vendor/blamejs/lib/pick.js +105 -0
- package/lib/vendor/blamejs/lib/pqc-agent.js +351 -0
- package/lib/vendor/blamejs/lib/pqc-gate.js +279 -0
- package/lib/vendor/blamejs/lib/pqc-software.js +271 -0
- package/lib/vendor/blamejs/lib/problem-details.js +482 -0
- package/lib/vendor/blamejs/lib/process-spawn.js +196 -0
- package/lib/vendor/blamejs/lib/promise-pool.js +162 -0
- package/lib/vendor/blamejs/lib/protobuf-encoder.js +190 -0
- package/lib/vendor/blamejs/lib/protocol-dispatcher.js +161 -0
- package/lib/vendor/blamejs/lib/public-suffix.js +403 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +154 -0
- package/lib/vendor/blamejs/lib/pubsub-redis.js +167 -0
- package/lib/vendor/blamejs/lib/pubsub.js +463 -0
- package/lib/vendor/blamejs/lib/queue-local.js +476 -0
- package/lib/vendor/blamejs/lib/queue-redis.js +745 -0
- package/lib/vendor/blamejs/lib/queue-sqs.js +319 -0
- package/lib/vendor/blamejs/lib/queue.js +1016 -0
- package/lib/vendor/blamejs/lib/redact.js +1007 -0
- package/lib/vendor/blamejs/lib/redis-client.js +520 -0
- package/lib/vendor/blamejs/lib/render.js +285 -0
- package/lib/vendor/blamejs/lib/request-helpers.js +767 -0
- package/lib/vendor/blamejs/lib/resource-access-lock.js +116 -0
- package/lib/vendor/blamejs/lib/restore-bundle.js +340 -0
- package/lib/vendor/blamejs/lib/restore-rollback.js +365 -0
- package/lib/vendor/blamejs/lib/restore.js +409 -0
- package/lib/vendor/blamejs/lib/retention.js +640 -0
- package/lib/vendor/blamejs/lib/retry.js +523 -0
- package/lib/vendor/blamejs/lib/router.js +1289 -0
- package/lib/vendor/blamejs/lib/safe-async.js +1184 -0
- package/lib/vendor/blamejs/lib/safe-buffer.js +562 -0
- package/lib/vendor/blamejs/lib/safe-decompress.js +297 -0
- package/lib/vendor/blamejs/lib/safe-dns.js +665 -0
- package/lib/vendor/blamejs/lib/safe-ical.js +634 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +502 -0
- package/lib/vendor/blamejs/lib/safe-json.js +946 -0
- package/lib/vendor/blamejs/lib/safe-jsonpath.js +285 -0
- package/lib/vendor/blamejs/lib/safe-mime.js +831 -0
- package/lib/vendor/blamejs/lib/safe-mount-info.js +306 -0
- package/lib/vendor/blamejs/lib/safe-path.js +254 -0
- package/lib/vendor/blamejs/lib/safe-redirect.js +106 -0
- package/lib/vendor/blamejs/lib/safe-schema.js +1810 -0
- package/lib/vendor/blamejs/lib/safe-sieve.js +684 -0
- package/lib/vendor/blamejs/lib/safe-smtp.js +185 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +363 -0
- package/lib/vendor/blamejs/lib/safe-url.js +428 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +473 -0
- package/lib/vendor/blamejs/lib/sandbox-worker.js +135 -0
- package/lib/vendor/blamejs/lib/sandbox.js +358 -0
- package/lib/vendor/blamejs/lib/scheduler.js +827 -0
- package/lib/vendor/blamejs/lib/sd-notify.js +269 -0
- package/lib/vendor/blamejs/lib/sec-cyber.js +214 -0
- package/lib/vendor/blamejs/lib/security-assert.js +395 -0
- package/lib/vendor/blamejs/lib/seeders.js +620 -0
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +309 -0
- package/lib/vendor/blamejs/lib/self-update.js +804 -0
- package/lib/vendor/blamejs/lib/server-timing.js +174 -0
- package/lib/vendor/blamejs/lib/session-device-binding.js +431 -0
- package/lib/vendor/blamejs/lib/session-stores.js +138 -0
- package/lib/vendor/blamejs/lib/session.js +1162 -0
- package/lib/vendor/blamejs/lib/slug.js +381 -0
- package/lib/vendor/blamejs/lib/sse.js +349 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +792 -0
- package/lib/vendor/blamejs/lib/standard-webhooks.js +183 -0
- package/lib/vendor/blamejs/lib/static.js +1249 -0
- package/lib/vendor/blamejs/lib/storage.js +1272 -0
- package/lib/vendor/blamejs/lib/stream-throttle.js +235 -0
- package/lib/vendor/blamejs/lib/structured-fields.js +244 -0
- package/lib/vendor/blamejs/lib/subject.js +667 -0
- package/lib/vendor/blamejs/lib/tcpa-10dlc.js +175 -0
- package/lib/vendor/blamejs/lib/template.js +931 -0
- package/lib/vendor/blamejs/lib/tenant-quota.js +545 -0
- package/lib/vendor/blamejs/lib/test-harness.js +275 -0
- package/lib/vendor/blamejs/lib/testing.js +1185 -0
- package/lib/vendor/blamejs/lib/time.js +578 -0
- package/lib/vendor/blamejs/lib/tls-exporter.js +239 -0
- package/lib/vendor/blamejs/lib/totp.js +318 -0
- package/lib/vendor/blamejs/lib/tracing.js +546 -0
- package/lib/vendor/blamejs/lib/uuid.js +207 -0
- package/lib/vendor/blamejs/lib/validate-opts.js +381 -0
- package/lib/vendor/blamejs/lib/vault/index.js +638 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +311 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-source.js +198 -0
- package/lib/vendor/blamejs/lib/vault/rotate.js +803 -0
- package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +471 -0
- package/lib/vendor/blamejs/lib/vault/wrap.js +296 -0
- package/lib/vendor/blamejs/lib/vault-aad.js +259 -0
- package/lib/vendor/blamejs/lib/vendor/.vendor-data-pubkey +4 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +161 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.data.js +68 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.pem +33 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.data.js +1325 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.txt +10002 -0
- package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +9 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +18 -0
- package/lib/vendor/blamejs/lib/vendor/pki.cjs +181 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +16382 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5881 -0
- package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +328 -0
- package/lib/vendor/blamejs/lib/vendor/vendor-data-pubkey.js +16 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +520 -0
- package/lib/vendor/blamejs/lib/vex.js +630 -0
- package/lib/vendor/blamejs/lib/watcher.js +608 -0
- package/lib/vendor/blamejs/lib/web-push-vapid.js +322 -0
- package/lib/vendor/blamejs/lib/webhook.js +977 -0
- package/lib/vendor/blamejs/lib/websocket-channels.js +327 -0
- package/lib/vendor/blamejs/lib/websocket.js +1561 -0
- package/lib/vendor/blamejs/lib/wiki-concepts.js +338 -0
- package/lib/vendor/blamejs/lib/worker-pool.js +464 -0
- package/lib/vendor/blamejs/lib/ws-client.js +978 -0
- package/lib/vendor/blamejs/lib/xml-c14n.js +506 -0
- package/lib/vendor/blamejs/memory/specs/node-26-map-getorinsert-migration.md +164 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +19 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/README.md +88 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/build.sh +26 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/project.yaml +28 -0
- package/lib/vendor/blamejs/package.json +81 -0
- package/lib/vendor/blamejs/release-notes/v0.0.x.json +310 -0
- package/lib/vendor/blamejs/release-notes/v0.1.x.json +1798 -0
- package/lib/vendor/blamejs/release-notes/v0.10.x.json +1288 -0
- package/lib/vendor/blamejs/release-notes/v0.11.x.json +2551 -0
- package/lib/vendor/blamejs/release-notes/v0.12.0.json +64 -0
- package/lib/vendor/blamejs/release-notes/v0.12.1.json +32 -0
- package/lib/vendor/blamejs/release-notes/v0.12.2.json +45 -0
- package/lib/vendor/blamejs/release-notes/v0.2.x.json +706 -0
- package/lib/vendor/blamejs/release-notes/v0.3.x.json +786 -0
- package/lib/vendor/blamejs/release-notes/v0.4.x.json +588 -0
- package/lib/vendor/blamejs/release-notes/v0.5.x.json +390 -0
- package/lib/vendor/blamejs/release-notes/v0.6.x.json +1947 -0
- package/lib/vendor/blamejs/release-notes/v0.7.x.json +3811 -0
- package/lib/vendor/blamejs/release-notes/v0.8.x.json +3318 -0
- package/lib/vendor/blamejs/release-notes/v0.9.x.json +2257 -0
- package/lib/vendor/blamejs/scripts/build-vendored-sbom.js +325 -0
- package/lib/vendor/blamejs/scripts/check-api-snapshot.js +62 -0
- package/lib/vendor/blamejs/scripts/check-changelog-extract.js +108 -0
- package/lib/vendor/blamejs/scripts/check-pack-against-gitignore.js +83 -0
- package/lib/vendor/blamejs/scripts/check-services.js +483 -0
- package/lib/vendor/blamejs/scripts/check-vendor-currency.js +349 -0
- package/lib/vendor/blamejs/scripts/consolidate-release-notes.js +216 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +275 -0
- package/lib/vendor/blamejs/scripts/generate-changelog-entry.js +577 -0
- package/lib/vendor/blamejs/scripts/generate-release-signing-key.js +79 -0
- package/lib/vendor/blamejs/scripts/publish-dep-confusion-placeholder.sh +101 -0
- package/lib/vendor/blamejs/scripts/refresh-api-snapshot.js +31 -0
- package/lib/vendor/blamejs/scripts/refresh-vendor-manifest.js +132 -0
- package/lib/vendor/blamejs/scripts/release.js +652 -0
- package/lib/vendor/blamejs/scripts/sha3-digest.js +62 -0
- package/lib/vendor/blamejs/scripts/sign-release-artifact.js +92 -0
- package/lib/vendor/blamejs/scripts/test-integration.js +181 -0
- package/lib/vendor/blamejs/scripts/test-wiki-integration.js +126 -0
- package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +77 -0
- package/lib/vendor/blamejs/scripts/vendor-data-gen.js +186 -0
- package/lib/vendor/blamejs/scripts/vendor-data-keygen.js +101 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +278 -0
- package/lib/vendor/blamejs/test/00-primitives.js +19075 -0
- package/lib/vendor/blamejs/test/10-state.js +622 -0
- package/lib/vendor/blamejs/test/20-db.js +561 -0
- package/lib/vendor/blamejs/test/30-chain.js +2110 -0
- package/lib/vendor/blamejs/test/40-consumers.js +2453 -0
- package/lib/vendor/blamejs/test/50-integration.js +486 -0
- package/lib/vendor/blamejs/test/_helpers.js +10 -0
- package/lib/vendor/blamejs/test/_smoke-worker.js +69 -0
- package/lib/vendor/blamejs/test/fixtures/exploit-corpus/corpus.json +368 -0
- package/lib/vendor/blamejs/test/fixtures/http-client-stream-payload.txt +2 -0
- package/lib/vendor/blamejs/test/fixtures/worker-pool/echo.js +52 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle-worker.js +24 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle.js +203 -0
- package/lib/vendor/blamejs/test/helpers/_shape-match.js +513 -0
- package/lib/vendor/blamejs/test/helpers/check.js +36 -0
- package/lib/vendor/blamejs/test/helpers/cluster.js +70 -0
- package/lib/vendor/blamejs/test/helpers/db.js +143 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +207 -0
- package/lib/vendor/blamejs/test/helpers/fs-watch.js +101 -0
- package/lib/vendor/blamejs/test/helpers/http.js +14 -0
- package/lib/vendor/blamejs/test/helpers/index.js +93 -0
- package/lib/vendor/blamejs/test/helpers/json-round-trip.js +120 -0
- package/lib/vendor/blamejs/test/helpers/mocks.js +20 -0
- package/lib/vendor/blamejs/test/helpers/otel.js +13 -0
- package/lib/vendor/blamejs/test/helpers/services.js +380 -0
- package/lib/vendor/blamejs/test/helpers/wait.js +206 -0
- package/lib/vendor/blamejs/test/integration/cache.test.js +235 -0
- package/lib/vendor/blamejs/test/integration/cluster-provider-mysql.test.js +174 -0
- package/lib/vendor/blamejs/test/integration/federation-auth.test.js +611 -0
- package/lib/vendor/blamejs/test/integration/http-client.test.js +129 -0
- package/lib/vendor/blamejs/test/integration/log-stream.test.js +219 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +181 -0
- package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +152 -0
- package/lib/vendor/blamejs/test/integration/mail-smtp.test.js +161 -0
- package/lib/vendor/blamejs/test/integration/mtls-ca.test.js +289 -0
- package/lib/vendor/blamejs/test/integration/network-dns.test.js +123 -0
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/ntp-check.test.js +89 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +403 -0
- package/lib/vendor/blamejs/test/integration/pqc-pkcs8-forward-compat.test.js +271 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +137 -0
- package/lib/vendor/blamejs/test/integration/queue-redis.test.js +352 -0
- package/lib/vendor/blamejs/test/integration/redis-client-tls.test.js +96 -0
- package/lib/vendor/blamejs/test/integration/ssrf-guard.test.js +98 -0
- package/lib/vendor/blamejs/test/integration/websocket-permessage-deflate.test.js +261 -0
- package/lib/vendor/blamejs/test/integration/ws-client-roundtrip.test.js +230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +211 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/access-lock.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +219 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +262 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-orchestrator.test.js +390 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-posture-chain.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-stream.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-tenant.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-trace.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-adverse-decision.test.js +44 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-content-detect.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-input.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-model-manifest.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-pref.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +1080 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +311 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-zip-stream.test.js +291 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +108 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asyncapi.test.js +929 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-conflict-path.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-cve-defensive.test.js +176 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-export-cadf.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-framework-namespaces.test.js +141 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-segregation.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-ml-dsa-65.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-use-store.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge-verifier.test.js +485 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +331 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +352 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +572 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-password-audit.test.js +61 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-bucket-ops.test.js +258 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-worker.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bearer-auth.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-chunked-malformed.test.js +131 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-smuggling.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/boot-gates.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/breach-deadline.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +861 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/budr.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bundler-engine.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache-status.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +871 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +891 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/canonical-json-jcs.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cdn-cache-control.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +550 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-api-key.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-audit-verify-chain.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-backup.test.js +135 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-config-drift.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-file-type.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-helpers.test.js +145 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-mtls.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-password.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-restore.test.js +160 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-retention.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-security.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-vault.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/client-hints.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +237 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +9600 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-cascade.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-eaa.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +712 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config-drift.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config.test.js +424 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cra-report.test.js +31 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-base64url.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-envelope.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke-pq.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-mlkem768-x25519.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-namespace-hash.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-random-int.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-builder.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-nonce.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daily-byte-quota.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection-extensions.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-init-extensions.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-extensions.test.js +191 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-role-for.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-vacuum.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +184 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-row-policy.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-view.test.js +303 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-dnssec-algorithm.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-null-mx.test.js +39 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dr-runbook.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr-state-rules.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +786 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/early-hints.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/events.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/exploit-replay.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-routing.test.js +531 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fapi2.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +156 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fdx.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +434 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +432 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/file-type.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +887 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/forensic-snapshot.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fsm.test.js +375 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gcs-bucket-ops.test.js +321 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +41 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-agent-registry.test.js +87 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-all.test.js +328 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +694 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-dsn.test.js +296 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-email.test.js +234 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-envelope.test.js +192 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-payload.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-topic.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +386 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html-wcag.test.js +859 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +317 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-id.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-unsubscribe.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-compose.test.js +111 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-move.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-query.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-reply.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-sieve.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +301 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +265 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-message-id.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +161 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-posture-chain.test.js +100 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-saga-config.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-smtp-command.test.js +269 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-snapshot-envelope.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-stream-args.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +288 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-tenant-id.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-trace-context.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-xml.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-yaml.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/hal.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/honeytoken.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +692 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +225 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +991 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-mspa.test.js +63 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-tcf.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +612 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +166 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jose-jwe-experimental.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-api.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-round-trip-helper.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/local-db-thin.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-cloudwatch.test.js +489 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp-grpc.test.js +207 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/lro.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +417 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-arf.test.js +208 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +910 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +502 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +680 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-canspam.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +297 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +514 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy-tlsrpt.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +627 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-feedback-id.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-greylist.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-mdn.test.js +334 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-rbl.test.js +271 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-require-tls.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +215 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +336 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +732 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +840 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +285 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +394 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-tls.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-spam-score.test.js +204 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-srs.test.js +152 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-unsubscribe.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +439 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-tool-registry.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-shadow-registry.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +224 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-compose-pipeline.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/money.test.js +376 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-paths.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nel.test.js +200 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-byte-quota.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +372 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +635 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-heartbeat-passive.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-build-options.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-ct-inclusion.test.js +179 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +447 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nis2-report.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nist-crosswalk.test.js +42 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/no-cache.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/notify.test.js +707 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/numeric-bounds.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/openapi.test.js +877 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pagination.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +324 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +546 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-software.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/problem-details.test.js +195 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/process-spawn.test.js +62 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/promise-pool.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protected-resource-metadata.test.js +68 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protobuf-encoder.test.js +138 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protocol-dispatcher.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/public-suffix.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +232 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-dlq-extend-lease.test.js +178 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-priority-rate-progress.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-sqs.test.js +300 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-cluster.test.js +338 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-registry.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +335 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-log.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-auth-cache-control.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-mtls.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/resource-access-lock.test.js +52 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retry.test.js +535 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-cross-origin-redirect.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-loops.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-parallel.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +248 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +451 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +289 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-icap.test.js +206 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-jsonpath.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mount-info.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-sieve.test.js +123 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-smtp.test.js +95 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-idn-homograph.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-slo.test.js +249 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-exactly-once.test.js +238 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +700 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-notify.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sec-cyber.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +175 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/seeders.test.js +816 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update-standalone-verifier.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/server-timing.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +247 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +295 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/shape-match.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +952 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-multipart-sse.test.js +441 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/slug.test.js +330 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/smtp-policy.test.js +233 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/source-comment-blocks.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/speculation-rules.test.js +319 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse.test.js +148 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/standard-webhooks.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +487 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-chunk-scratch.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +773 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/stream-throttle.test.js +173 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/structured-fields.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tcpa-10dlc.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +571 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-harness.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing-request.test.js +119 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-exporter.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-ct.test.js +275 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-verify.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-pinset-drift.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-preferred-groups.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tracing.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/uuid.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-aad.test.js +277 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-seal-pem-file.test.js +252 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-data.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-manifest.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vex.test.js +661 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +308 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/web-push-vapid.test.js +144 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +674 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-channels.test.js +360 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +349 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +717 -0
- package/lib/vendor/blamejs/test/layer-5-integration/bundler-output.test.js +444 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-5-integration/security-chaos.test.js +308 -0
- package/lib/vendor/blamejs/test/smoke.js +431 -0
- package/lib/webhooks.js +305 -0
- package/package.json +43 -0
|
@@ -0,0 +1,2453 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer 4 — consumer modules.
|
|
4
|
+
*
|
|
5
|
+
* Layer 4 — consumer modules sit on top of audit + db + chain
|
|
6
|
+
* primitives.
|
|
7
|
+
*
|
|
8
|
+
* session — session lifecycle
|
|
9
|
+
* data-residency — db + storage residency rules
|
|
10
|
+
* storage — local + multi-backend object stores +
|
|
11
|
+
* classification routing + retry/breaker +
|
|
12
|
+
* sigv4 / GCS / Azure adapters
|
|
13
|
+
* queue — local queue lifecycle (consume / retry / lease)
|
|
14
|
+
* log-stream — log fan-out (local, webhook, bidirectional)
|
|
15
|
+
* external-db — dispatcher (basic / pool / transaction /
|
|
16
|
+
* residency / classification)
|
|
17
|
+
* middleware — request-id / security headers / error handler /
|
|
18
|
+
* bot-guard / cors / rate-limit
|
|
19
|
+
* env-load — full lifecycle (consumes env-parse + atomicFile)
|
|
20
|
+
*
|
|
21
|
+
* Layers 0–3 must run first.
|
|
22
|
+
*
|
|
23
|
+
* Usage from smoke.js:
|
|
24
|
+
* var consumersLayer = require("./40-consumers");
|
|
25
|
+
* await consumersLayer.run();
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
var helpers = require("./_helpers");
|
|
29
|
+
var b = helpers.b;
|
|
30
|
+
var fs = helpers.fs;
|
|
31
|
+
var os = helpers.os;
|
|
32
|
+
var path = helpers.path;
|
|
33
|
+
var check = helpers.check;
|
|
34
|
+
var setupTestDb = helpers.setupTestDb;
|
|
35
|
+
var teardownTestDb = helpers.teardownTestDb;
|
|
36
|
+
var setupTestDbForMW = helpers.setupTestDbForMW;
|
|
37
|
+
var teardownMW = helpers.teardownMW;
|
|
38
|
+
var listenOnRandomPort = helpers.listenOnRandomPort;
|
|
39
|
+
var _makeFakeDriver = helpers._makeFakeDriver;
|
|
40
|
+
var _makeSqliteDriver = helpers._makeSqliteDriver;
|
|
41
|
+
var _makeFakeServiceAccount = helpers._makeFakeServiceAccount;
|
|
42
|
+
var _mockReq = helpers._mockReq;
|
|
43
|
+
var _mockRes = helpers._mockRes;
|
|
44
|
+
|
|
45
|
+
async function testSession() {
|
|
46
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-session-"));
|
|
47
|
+
try {
|
|
48
|
+
await setupTestDb(tmpDir);
|
|
49
|
+
|
|
50
|
+
// Create + verify
|
|
51
|
+
var s1 = await b.session.create({ userId: "u-1", data: { csrfToken: "abc" } });
|
|
52
|
+
check("create returns sealed token", typeof s1.token === "string" && s1.token.indexOf("vault:") === 0);
|
|
53
|
+
check("create returns expiresAt > now", s1.expiresAt > Date.now());
|
|
54
|
+
|
|
55
|
+
var v1 = await b.session.verify(s1.token);
|
|
56
|
+
check("verify returns the session", v1 && v1.userId === "u-1");
|
|
57
|
+
check("verify decrypts data field", v1 && v1.data && v1.data.csrfToken === "abc");
|
|
58
|
+
|
|
59
|
+
// The plaintext sid should NEVER be in the DB — only its hash
|
|
60
|
+
var rawRows = b.db.prepare("SELECT sidHash FROM _blamejs_sessions").all();
|
|
61
|
+
check("only sidHash stored, never plaintext sid",
|
|
62
|
+
rawRows.every(r => r.sidHash !== s1.token && r.sidHash.length === 128));
|
|
63
|
+
|
|
64
|
+
// verify on garbage token returns null
|
|
65
|
+
check("verify on garbage token returns null", (await b.session.verify("not-a-real-token")) === null);
|
|
66
|
+
check("verify on empty token returns null", (await b.session.verify("")) === null);
|
|
67
|
+
|
|
68
|
+
// touch
|
|
69
|
+
var beforeTouch = await b.session.verify(s1.token);
|
|
70
|
+
var t0 = beforeTouch.lastActivity;
|
|
71
|
+
// Sleep briefly to ensure lastActivity changes
|
|
72
|
+
await new Promise(function (r) { setTimeout(r, 10); });
|
|
73
|
+
var ok = await b.session.touch(s1.token);
|
|
74
|
+
check("touch returns true", ok === true);
|
|
75
|
+
var afterTouch = await b.session.verify(s1.token);
|
|
76
|
+
check("touch updates lastActivity", afterTouch.lastActivity > t0);
|
|
77
|
+
|
|
78
|
+
// touch's extendBy is bounded by the same MAX_TTL_MS as create / rotate
|
|
79
|
+
// (10 years). Repeated touches with arbitrary extendBy values
|
|
80
|
+
// can't push expiresAt past that bound.
|
|
81
|
+
var extendThrew = null;
|
|
82
|
+
try { await b.session.touch(s1.token, { extendBy: 1000 * 60 * 60 * 24 * 365 * 100 }); }
|
|
83
|
+
catch (e) { extendThrew = e; }
|
|
84
|
+
check("touch rejects extendBy beyond MAX_TTL_MS",
|
|
85
|
+
extendThrew && /exceeds maximum/.test(extendThrew.message || ""));
|
|
86
|
+
var negThrew = null;
|
|
87
|
+
try { await b.session.touch(s1.token, { extendBy: -1 }); }
|
|
88
|
+
catch (e) { negThrew = e; }
|
|
89
|
+
check("touch rejects negative extendBy", negThrew !== null);
|
|
90
|
+
var nanThrew = null;
|
|
91
|
+
try { await b.session.touch(s1.token, { extendBy: NaN }); }
|
|
92
|
+
catch (e) { nanThrew = e; }
|
|
93
|
+
check("touch rejects NaN extendBy", nanThrew !== null);
|
|
94
|
+
|
|
95
|
+
// destroyAllForUser
|
|
96
|
+
var s2 = await b.session.create({ userId: "u-1" });
|
|
97
|
+
var s3 = await b.session.create({ userId: "u-2" });
|
|
98
|
+
check("count includes all active sessions", (await b.session.count()) === 3);
|
|
99
|
+
var nDel = await b.session.destroyAllForUser("u-1");
|
|
100
|
+
check("destroyAllForUser returns count", nDel === 2);
|
|
101
|
+
check("u-1's sessions all gone",
|
|
102
|
+
(await b.session.verify(s1.token)) === null && (await b.session.verify(s2.token)) === null);
|
|
103
|
+
check("u-2's session survives", (await b.session.verify(s3.token)) !== null);
|
|
104
|
+
|
|
105
|
+
// destroy single
|
|
106
|
+
check("destroy returns true on success", (await b.session.destroy(s3.token)) === true);
|
|
107
|
+
check("destroy returns false on missing", (await b.session.destroy(s3.token)) === false);
|
|
108
|
+
|
|
109
|
+
// Expired session auto-cleans on verify
|
|
110
|
+
var sExp = await b.session.create({ userId: "u-3", ttlMs: 50 });
|
|
111
|
+
await new Promise(function (r) { setTimeout(r, 100); });
|
|
112
|
+
check("verify on expired session returns null", (await b.session.verify(sExp.token)) === null);
|
|
113
|
+
|
|
114
|
+
// purgeExpired
|
|
115
|
+
var sExp2 = await b.session.create({ userId: "u-4", ttlMs: 50 });
|
|
116
|
+
void sExp2;
|
|
117
|
+
await new Promise(function (r) { setTimeout(r, 100); });
|
|
118
|
+
var purged = await b.session.purgeExpired();
|
|
119
|
+
check("purgeExpired returns count", purged >= 1);
|
|
120
|
+
|
|
121
|
+
// Invalid input — session.create rejects synchronously before
|
|
122
|
+
// returning a Promise, so the throw is observable via try/catch
|
|
123
|
+
// around the awaited call (the rejected Promise raises in await).
|
|
124
|
+
var rejected = false;
|
|
125
|
+
try { await b.session.create({}); } catch (_) { rejected = true; }
|
|
126
|
+
check("session.create requires userId", rejected);
|
|
127
|
+
} finally {
|
|
128
|
+
await teardownTestDb(tmpDir);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function testDataResidency() {
|
|
133
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-dr-"));
|
|
134
|
+
try {
|
|
135
|
+
b.vault._resetForTest();
|
|
136
|
+
b.db._resetForTest();
|
|
137
|
+
await b.vault.init({ dataDir: tmpDir, mode: "plaintext" });
|
|
138
|
+
await b.db.init({
|
|
139
|
+
dataDir: tmpDir,
|
|
140
|
+
atRest: "plain",
|
|
141
|
+
auditSigning: { mode: "plaintext" },
|
|
142
|
+
schema: [{ name: "x", columns: { _id: "TEXT PRIMARY KEY" } }],
|
|
143
|
+
dataResidency: { region: "EU", allowedStorageRegions: ["eu-west-1"] },
|
|
144
|
+
});
|
|
145
|
+
var dr = b.db.getDataResidency();
|
|
146
|
+
check("getDataResidency returns config", dr && dr.region === "EU");
|
|
147
|
+
check("dataResidency includes allowedRegions", dr.allowedStorageRegions[0] === "eu-west-1");
|
|
148
|
+
} finally {
|
|
149
|
+
await teardownTestDb(tmpDir);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function testStorage() {
|
|
154
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-storage-"));
|
|
155
|
+
try {
|
|
156
|
+
await setupTestDb(tmpDir);
|
|
157
|
+
b.storage.init({ backend: "local", uploadDir: path.join(tmpDir, "uploads") });
|
|
158
|
+
|
|
159
|
+
var content = Buffer.from("hello blamejs storage " + Date.now(), "utf8");
|
|
160
|
+
var saved = await b.storage.saveFile(content, "user-1/welcome.txt");
|
|
161
|
+
check("saveFile returns storedPath", saved.storedPath === "user-1/welcome.txt");
|
|
162
|
+
check("saveFile returns sealed encryptionKey",
|
|
163
|
+
typeof saved.encryptionKey === "string" && saved.encryptionKey.startsWith("vault:"));
|
|
164
|
+
|
|
165
|
+
// The on-disk file must NOT contain the plaintext content
|
|
166
|
+
var onDisk = fs.readFileSync(path.join(tmpDir, "uploads", "user-1/welcome.txt"));
|
|
167
|
+
check("on-disk file is encrypted (not plaintext)", onDisk.indexOf(content) === -1);
|
|
168
|
+
check("on-disk file starts with format byte 0x02", onDisk[0] === b.constants.FORMAT.XCHACHA20_POLY1305);
|
|
169
|
+
|
|
170
|
+
// Round-trip
|
|
171
|
+
var decrypted = await b.storage.getFileBuffer("user-1/welcome.txt", saved.encryptionKey);
|
|
172
|
+
check("getFileBuffer round-trip preserves content", decrypted.equals(content));
|
|
173
|
+
|
|
174
|
+
// Stream form
|
|
175
|
+
var stream = await b.storage.getFileStream("user-1/welcome.txt", saved.encryptionKey);
|
|
176
|
+
var chunks = [];
|
|
177
|
+
for await (var chunk of stream) chunks.push(chunk);
|
|
178
|
+
var streamed = Buffer.concat(chunks);
|
|
179
|
+
check("getFileStream round-trip preserves content", streamed.equals(content));
|
|
180
|
+
|
|
181
|
+
// Wrong key fails
|
|
182
|
+
var wrongRejected = false;
|
|
183
|
+
try { await b.storage.getFileBuffer("user-1/welcome.txt", b.vault.seal("not-the-real-key")); }
|
|
184
|
+
catch (_) { wrongRejected = true; }
|
|
185
|
+
check("getFileBuffer with wrong key throws", wrongRejected);
|
|
186
|
+
|
|
187
|
+
// No key required throws
|
|
188
|
+
var noKeyRejected = false;
|
|
189
|
+
try { await b.storage.getFileBuffer("user-1/welcome.txt", null); }
|
|
190
|
+
catch (_) { noKeyRejected = true; }
|
|
191
|
+
check("getFileBuffer without key throws", noKeyRejected);
|
|
192
|
+
|
|
193
|
+
// exists
|
|
194
|
+
check("exists returns true on present file", (await b.storage.exists("user-1/welcome.txt")) === true);
|
|
195
|
+
check("exists returns false on missing", (await b.storage.exists("does/not/exist.txt")) === false);
|
|
196
|
+
|
|
197
|
+
// saveRaw / getRawBuffer (no encryption)
|
|
198
|
+
var rawContent = Buffer.from("already-encrypted-or-not-sensitive", "utf8");
|
|
199
|
+
await b.storage.saveRaw(rawContent, "raw/blob.bin");
|
|
200
|
+
var rawBack = await b.storage.getRawBuffer("raw/blob.bin");
|
|
201
|
+
check("saveRaw / getRawBuffer round-trip", rawBack.equals(rawContent));
|
|
202
|
+
|
|
203
|
+
// deleteFile
|
|
204
|
+
check("deleteFile returns true on existing", (await b.storage.deleteFile("user-1/welcome.txt")) === true);
|
|
205
|
+
check("deleteFile returns false on missing", (await b.storage.deleteFile("user-1/welcome.txt")) === false);
|
|
206
|
+
check("file no longer exists after delete", (await b.storage.exists("user-1/welcome.txt")) === false);
|
|
207
|
+
|
|
208
|
+
// Path traversal rejected
|
|
209
|
+
var traversalRejected = false;
|
|
210
|
+
try { await b.storage.saveFile(content, "../escape.txt"); }
|
|
211
|
+
catch (_) { traversalRejected = true; }
|
|
212
|
+
check("path traversal via .. rejected", traversalRejected);
|
|
213
|
+
|
|
214
|
+
var absRejected = false;
|
|
215
|
+
try { await b.storage.saveFile(content, "/etc/passwd"); }
|
|
216
|
+
catch (_) { absRejected = true; }
|
|
217
|
+
check("absolute path rejected", absRejected);
|
|
218
|
+
|
|
219
|
+
var nullByteRejected = false;
|
|
220
|
+
try { await b.storage.saveFile(content, "okinjected"); }
|
|
221
|
+
catch (_) { nullByteRejected = true; }
|
|
222
|
+
check("null-byte in path rejected", nullByteRejected);
|
|
223
|
+
|
|
224
|
+
// S3 backend not yet available
|
|
225
|
+
b.storage._resetForTest();
|
|
226
|
+
var s3Rejected = false;
|
|
227
|
+
try { b.storage.init({ backend: "s3", bucket: "x" }); }
|
|
228
|
+
catch (e) { s3Rejected = /sigv4|deferred|not yet/i.test(e.message); }
|
|
229
|
+
check("storage backend 's3' deferred with clear message", s3Rejected);
|
|
230
|
+
} finally {
|
|
231
|
+
await teardownTestDb(tmpDir);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function testMultiBackend() {
|
|
236
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-multi-"));
|
|
237
|
+
try {
|
|
238
|
+
await setupTestDb(tmpDir);
|
|
239
|
+
b.storage.init({
|
|
240
|
+
backends: {
|
|
241
|
+
"primary": {
|
|
242
|
+
protocol: "local",
|
|
243
|
+
rootDir: path.join(tmpDir, "primary"),
|
|
244
|
+
classifications: ["personal"],
|
|
245
|
+
residencyTag: "unrestricted",
|
|
246
|
+
},
|
|
247
|
+
"ops": {
|
|
248
|
+
protocol: "local",
|
|
249
|
+
rootDir: path.join(tmpDir, "ops"),
|
|
250
|
+
classifications: ["operational", "public"],
|
|
251
|
+
residencyTag: "unrestricted",
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
defaultClassification: "personal",
|
|
255
|
+
refuseUnclassified: true,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
var listed = b.storage.listBackends();
|
|
259
|
+
check("listBackends returns 2 entries", listed.length === 2);
|
|
260
|
+
check("backend names enumerated", listed.some(b => b.name === "primary") && listed.some(b => b.name === "ops"));
|
|
261
|
+
|
|
262
|
+
// Save personal data → routes to 'primary'
|
|
263
|
+
var content1 = Buffer.from("private medical record", "utf8");
|
|
264
|
+
var saved1 = await b.storage.saveFile(content1, "patient/123.json", { classification: "personal" });
|
|
265
|
+
check("personal data routes to primary", saved1.backend === "primary");
|
|
266
|
+
|
|
267
|
+
// Save operational data → routes to 'ops'
|
|
268
|
+
var content2 = Buffer.from("nginx access log line", "utf8");
|
|
269
|
+
var saved2 = await b.storage.saveFile(content2, "logs/2026-04-25.log", { classification: "operational" });
|
|
270
|
+
check("operational data routes to ops", saved2.backend === "ops");
|
|
271
|
+
|
|
272
|
+
// File lands in the right physical directory
|
|
273
|
+
check("personal file in primary tree", fs.existsSync(path.join(tmpDir, "primary", "patient/123.json")));
|
|
274
|
+
check("operational file in ops tree", fs.existsSync(path.join(tmpDir, "ops", "logs/2026-04-25.log")));
|
|
275
|
+
check("personal NOT in ops tree", !fs.existsSync(path.join(tmpDir, "ops", "patient/123.json")));
|
|
276
|
+
|
|
277
|
+
// Round-trip with explicit backend opt
|
|
278
|
+
var back = await b.storage.getFileBuffer("patient/123.json", saved1.encryptionKey, { backend: "primary" });
|
|
279
|
+
check("explicit-backend round-trip works", back.equals(content1));
|
|
280
|
+
|
|
281
|
+
// Unknown classification → fails
|
|
282
|
+
var unknownClsRejected = false;
|
|
283
|
+
try { await b.storage.saveFile(content1, "test", { classification: "unknown" }); }
|
|
284
|
+
catch (e) { unknownClsRejected = e.code === "NO_BACKEND_FOR_CLASSIFICATION"; }
|
|
285
|
+
check("unknown classification rejected", unknownClsRejected);
|
|
286
|
+
|
|
287
|
+
// refuseUnclassified: missing classification rejected
|
|
288
|
+
var noClsRejected = false;
|
|
289
|
+
try { await b.storage.saveFile(content1, "test"); }
|
|
290
|
+
catch (e) { noClsRejected = e.code === "UNCLASSIFIED"; }
|
|
291
|
+
check("refuseUnclassified rejects missing classification", noClsRejected);
|
|
292
|
+
|
|
293
|
+
// Wrong-backend-for-classification rejected
|
|
294
|
+
var wrongBackendRejected = false;
|
|
295
|
+
try { await b.storage.saveFile(content1, "test", { backend: "ops", classification: "personal" }); }
|
|
296
|
+
catch (e) { wrongBackendRejected = e.code === "CLASSIFICATION_MISMATCH"; }
|
|
297
|
+
check("backend that doesn't serve classification rejected", wrongBackendRejected);
|
|
298
|
+
} finally {
|
|
299
|
+
await teardownTestDb(tmpDir);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function testClassificationRouting() {
|
|
304
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-cls-"));
|
|
305
|
+
try {
|
|
306
|
+
await setupTestDb(tmpDir);
|
|
307
|
+
// Wildcard backend serves any classification
|
|
308
|
+
b.storage.init({
|
|
309
|
+
backends: {
|
|
310
|
+
"any": {
|
|
311
|
+
protocol: "local",
|
|
312
|
+
rootDir: path.join(tmpDir, "any"),
|
|
313
|
+
classifications: ["*"],
|
|
314
|
+
residencyTag: "unrestricted",
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
var c1 = Buffer.from("a", "utf8");
|
|
320
|
+
var s1 = await b.storage.saveFile(c1, "x", { classification: "personal" });
|
|
321
|
+
check("wildcard backend accepts personal", s1.backend === "any");
|
|
322
|
+
var s2 = await b.storage.saveFile(c1, "y", { classification: "audit-archive" });
|
|
323
|
+
check("wildcard backend accepts custom class", s2.backend === "any");
|
|
324
|
+
var s3 = await b.storage.saveFile(c1, "z");
|
|
325
|
+
check("wildcard backend accepts no-classification", s3.backend === "any");
|
|
326
|
+
} finally {
|
|
327
|
+
await teardownTestDb(tmpDir);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function testResidencyEnforcement() {
|
|
332
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-residency-"));
|
|
333
|
+
try {
|
|
334
|
+
process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
|
|
335
|
+
process.env.BLAMEJS_AUDIT_SIGNING_MODE = "plaintext";
|
|
336
|
+
b.vault._resetForTest();
|
|
337
|
+
b.db._resetForTest();
|
|
338
|
+
await b.vault.init({ dataDir: tmpDir, mode: "plaintext" });
|
|
339
|
+
await b.db.init({
|
|
340
|
+
dataDir: tmpDir,
|
|
341
|
+
atRest: "plain",
|
|
342
|
+
auditSigning: { mode: "plaintext" },
|
|
343
|
+
schema: [],
|
|
344
|
+
dataResidency: { region: "EU", allowedStorageRegions: ["EU"] },
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Configuring a personal-data backend tagged US should refuse to init
|
|
348
|
+
var residencyViolation = false;
|
|
349
|
+
try {
|
|
350
|
+
b.storage.init({
|
|
351
|
+
backends: {
|
|
352
|
+
"us-bad": {
|
|
353
|
+
protocol: "local",
|
|
354
|
+
rootDir: path.join(tmpDir, "us"),
|
|
355
|
+
classifications: ["personal"],
|
|
356
|
+
residencyTag: "US", // ← violation
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
} catch (e) {
|
|
361
|
+
residencyViolation = e.code === "RESIDENCY_VIOLATION";
|
|
362
|
+
}
|
|
363
|
+
check("personal-data backend outside region refused", residencyViolation);
|
|
364
|
+
|
|
365
|
+
// EU-tagged backend is fine
|
|
366
|
+
b.storage._resetForTest();
|
|
367
|
+
b.storage.init({
|
|
368
|
+
backends: {
|
|
369
|
+
"eu-ok": {
|
|
370
|
+
protocol: "local",
|
|
371
|
+
rootDir: path.join(tmpDir, "eu"),
|
|
372
|
+
classifications: ["personal"],
|
|
373
|
+
residencyTag: "EU",
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
var listed = b.storage.listBackends();
|
|
378
|
+
check("EU-tagged backend accepted", listed.length === 1 && listed[0].residencyTag === "EU");
|
|
379
|
+
} finally {
|
|
380
|
+
await teardownTestDb(tmpDir);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function testRetryAndBreaker() {
|
|
385
|
+
// Retry policy unit tests — exercise withRetry directly without backend setup
|
|
386
|
+
var attempts = 0;
|
|
387
|
+
var transientErr = function () {
|
|
388
|
+
attempts += 1;
|
|
389
|
+
var e = new Error("transient");
|
|
390
|
+
e.statusCode = 503;
|
|
391
|
+
e.isObjectStoreError = true;
|
|
392
|
+
e.permanent = false;
|
|
393
|
+
throw e;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Retries 5xx
|
|
397
|
+
var caught = false;
|
|
398
|
+
attempts = 0;
|
|
399
|
+
try {
|
|
400
|
+
await b.retry.withRetry(transientErr, { maxAttempts: 3, baseDelayMs: 1, maxDelayMs: 5 });
|
|
401
|
+
} catch (_) { caught = true; }
|
|
402
|
+
check("retry exhausts maxAttempts on transient", caught && attempts === 3);
|
|
403
|
+
|
|
404
|
+
// Does NOT retry permanent (4xx)
|
|
405
|
+
attempts = 0;
|
|
406
|
+
var permErr = function () {
|
|
407
|
+
attempts += 1;
|
|
408
|
+
var e = new Error("forbidden");
|
|
409
|
+
e.statusCode = 403;
|
|
410
|
+
e.isObjectStoreError = true;
|
|
411
|
+
e.permanent = true;
|
|
412
|
+
throw e;
|
|
413
|
+
};
|
|
414
|
+
var permCaught = false;
|
|
415
|
+
try { await b.retry.withRetry(permErr, { maxAttempts: 5 }); }
|
|
416
|
+
catch (_) { permCaught = true; }
|
|
417
|
+
check("retry does NOT retry permanent errors", permCaught && attempts === 1);
|
|
418
|
+
|
|
419
|
+
// Retryable classification
|
|
420
|
+
check("isRetryable: 503 → true", b.retry.isRetryable({ statusCode: 503 }));
|
|
421
|
+
check("isRetryable: 403 → false", !b.retry.isRetryable({ statusCode: 403 }));
|
|
422
|
+
check("isRetryable: ECONNRESET → true", b.retry.isRetryable({ code: "ECONNRESET" }));
|
|
423
|
+
check("isRetryable: ENOENT → false (not in retry set)", !b.retry.isRetryable({ code: "ENOENT" }));
|
|
424
|
+
|
|
425
|
+
// Circuit breaker
|
|
426
|
+
var breaker = new b.retry.CircuitBreaker("test", { failureThreshold: 3, cooldownMs: 50, successThreshold: 1 });
|
|
427
|
+
check("breaker starts closed", breaker.getState() === "closed");
|
|
428
|
+
// Trip it
|
|
429
|
+
for (var i = 0; i < 3; i++) {
|
|
430
|
+
try { await breaker.wrap(function () { throw Object.assign(new Error("fail"), { code: "ECONNRESET" }); }); }
|
|
431
|
+
catch (_) {}
|
|
432
|
+
}
|
|
433
|
+
check("breaker opens after threshold", breaker.getState() === "open");
|
|
434
|
+
|
|
435
|
+
// Open breaker fails fast (CIRCUIT_OPEN code)
|
|
436
|
+
var fastFail = false;
|
|
437
|
+
try { await breaker.wrap(function () { return Promise.resolve("never-runs"); }); }
|
|
438
|
+
catch (e) { fastFail = e.code === "CIRCUIT_OPEN"; }
|
|
439
|
+
check("open breaker fails fast", fastFail);
|
|
440
|
+
|
|
441
|
+
// Wait for cooldown then half-open + success closes
|
|
442
|
+
await new Promise(function (r) { setTimeout(r, 60); });
|
|
443
|
+
await breaker.wrap(function () { return Promise.resolve("ok"); });
|
|
444
|
+
check("breaker closes after successful probe", breaker.getState() === "closed");
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function testSigv4Primitives() {
|
|
448
|
+
var sigv4 = require("../lib/object-store/sigv4");
|
|
449
|
+
|
|
450
|
+
// AWS-published test vector for signing-key derivation
|
|
451
|
+
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-signing.html)
|
|
452
|
+
var key = sigv4.deriveSigningKey(
|
|
453
|
+
"wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
|
454
|
+
"20150830",
|
|
455
|
+
"us-east-1",
|
|
456
|
+
"iam"
|
|
457
|
+
);
|
|
458
|
+
var hex = key.toString("hex");
|
|
459
|
+
check("sigv4 deriveSigningKey matches AWS test vector",
|
|
460
|
+
hex === "c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9");
|
|
461
|
+
|
|
462
|
+
// awsUriEncode
|
|
463
|
+
check("awsUriEncode preserves alphanumerics and unreserved",
|
|
464
|
+
sigv4.awsUriEncode("hello-world.txt", true) === "hello-world.txt");
|
|
465
|
+
check("awsUriEncode encodes spaces",
|
|
466
|
+
sigv4.awsUriEncode("a b", true) === "a%20b");
|
|
467
|
+
check("awsUriEncode preserves slashes when encodeSlash=false",
|
|
468
|
+
sigv4.awsUriEncode("foo/bar", false) === "foo/bar");
|
|
469
|
+
check("awsUriEncode encodes slashes when encodeSlash=true",
|
|
470
|
+
sigv4.awsUriEncode("foo/bar", true) === "foo%2Fbar");
|
|
471
|
+
|
|
472
|
+
// canonicalQueryString — sorted, encoded
|
|
473
|
+
var u = new (require("url").URL)("https://x/?b=2&a=1&c=3");
|
|
474
|
+
check("canonicalQueryString sorts keys",
|
|
475
|
+
sigv4.canonicalQueryString(u.searchParams) === "a=1&b=2&c=3");
|
|
476
|
+
|
|
477
|
+
// canonicalHeaders — lowercase keys, sorted, signed list
|
|
478
|
+
var ch = sigv4.canonicalHeaders({ "X-Foo": "bar", host: "example.com", "Z-Last": " trim " });
|
|
479
|
+
check("canonicalHeaders has trailing newline per pair",
|
|
480
|
+
/host:example\.com\n/.test(ch.canonical));
|
|
481
|
+
check("canonicalHeaders lowercases + sorts",
|
|
482
|
+
ch.signed === "host;x-foo;z-last");
|
|
483
|
+
check("canonicalHeaders trims + collapses whitespace",
|
|
484
|
+
/z-last:trim\n/.test(ch.canonical));
|
|
485
|
+
|
|
486
|
+
// signRequest produces an Authorization header with the right shape
|
|
487
|
+
var signed = sigv4.signRequest({
|
|
488
|
+
method: "GET",
|
|
489
|
+
url: "https://test-bucket.s3.us-east-1.amazonaws.com/key1",
|
|
490
|
+
headers: {},
|
|
491
|
+
payloadHash: sigv4.sha256Hex(Buffer.alloc(0)),
|
|
492
|
+
region: "us-east-1",
|
|
493
|
+
accessKeyId: "AKIAIOSFODNN7EXAMPLE",
|
|
494
|
+
secretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
|
495
|
+
date: new Date(Date.UTC(2026, 3, 25, 12, 34, 56)), // 2026-04-25T12:34:56Z
|
|
496
|
+
});
|
|
497
|
+
check("signRequest produces AWS4-HMAC-SHA256 Authorization",
|
|
498
|
+
/^AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE\/20260425\/us-east-1\/s3\/aws4_request, SignedHeaders=[a-z0-9;-]+, Signature=[0-9a-f]{64}$/.test(signed.headers["Authorization"]));
|
|
499
|
+
check("signRequest sets host header", signed.headers["host"] === "test-bucket.s3.us-east-1.amazonaws.com");
|
|
500
|
+
check("signRequest sets x-amz-date", signed.headers["x-amz-date"] === "20260425T123456Z");
|
|
501
|
+
check("signRequest sets x-amz-content-sha256",
|
|
502
|
+
signed.headers["x-amz-content-sha256"] === sigv4.sha256Hex(Buffer.alloc(0)));
|
|
503
|
+
check("signRequest signature is deterministic for same inputs",
|
|
504
|
+
signed.signature.length === 64);
|
|
505
|
+
|
|
506
|
+
// Same inputs → same signature
|
|
507
|
+
var signed2 = sigv4.signRequest({
|
|
508
|
+
method: "GET",
|
|
509
|
+
url: "https://test-bucket.s3.us-east-1.amazonaws.com/key1",
|
|
510
|
+
headers: {},
|
|
511
|
+
payloadHash: sigv4.sha256Hex(Buffer.alloc(0)),
|
|
512
|
+
region: "us-east-1",
|
|
513
|
+
accessKeyId: "AKIAIOSFODNN7EXAMPLE",
|
|
514
|
+
secretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
|
515
|
+
date: new Date(Date.UTC(2026, 3, 25, 12, 34, 56)),
|
|
516
|
+
});
|
|
517
|
+
check("signRequest deterministic across calls", signed.signature === signed2.signature);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async function testSigv4MockServer() {
|
|
521
|
+
var http = require("http");
|
|
522
|
+
var sigv4 = require("../lib/object-store/sigv4");
|
|
523
|
+
|
|
524
|
+
// In-process mock S3 server. Validates request shape (Authorization +
|
|
525
|
+
// x-amz-date + x-amz-content-sha256) and stores PUT bodies in memory
|
|
526
|
+
// so subsequent GET/HEAD/LIST/DELETE can return them.
|
|
527
|
+
var stored = {};
|
|
528
|
+
var server = http.createServer(function (req, res) {
|
|
529
|
+
var auth = req.headers["authorization"] || "";
|
|
530
|
+
if (!/^AWS4-HMAC-SHA256 /.test(auth)) {
|
|
531
|
+
res.writeHead(401); res.end("missing AWS4-HMAC-SHA256"); return;
|
|
532
|
+
}
|
|
533
|
+
if (!req.headers["x-amz-date"]) {
|
|
534
|
+
res.writeHead(400); res.end("missing x-amz-date"); return;
|
|
535
|
+
}
|
|
536
|
+
if (!req.headers["x-amz-content-sha256"]) {
|
|
537
|
+
res.writeHead(400); res.end("missing x-amz-content-sha256"); return;
|
|
538
|
+
}
|
|
539
|
+
// Strip query for routing; URL parse needs Host header
|
|
540
|
+
var pathname = req.url.split("?")[0];
|
|
541
|
+
// Path-style: /bucket/key → key extraction
|
|
542
|
+
var m = pathname.match(/^\/[^/]+\/(.+)$/);
|
|
543
|
+
var key = m ? m[1] : null;
|
|
544
|
+
|
|
545
|
+
if (req.method === "PUT" && key) {
|
|
546
|
+
var bufs = [];
|
|
547
|
+
req.on("data", function (c) { bufs.push(c); });
|
|
548
|
+
req.on("end", function () {
|
|
549
|
+
stored[key] = Buffer.concat(bufs); // lgtm[js/remote-property-injection] test fixture; not a runtime path
|
|
550
|
+
res.writeHead(200, { ETag: '"' + sigv4.sha256Hex(stored[key]).slice(0, 32) + '"' });
|
|
551
|
+
res.end();
|
|
552
|
+
});
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (req.method === "GET" && key && stored[key]) {
|
|
556
|
+
res.writeHead(200, { "Content-Length": stored[key].length });
|
|
557
|
+
res.end(stored[key]);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (req.method === "GET" && pathname === "/test-bucket/" || (req.url || "").indexOf("list-type=2") !== -1) {
|
|
561
|
+
// List request
|
|
562
|
+
var xml = "<?xml version=\"1.0\"?><ListBucketResult>";
|
|
563
|
+
Object.keys(stored).forEach(function (k) {
|
|
564
|
+
xml += "<Contents><Key>" + k + "</Key><Size>" + stored[k].length + "</Size>" +
|
|
565
|
+
"<LastModified>2026-04-25T00:00:00.000Z</LastModified></Contents>";
|
|
566
|
+
});
|
|
567
|
+
xml += "<IsTruncated>false</IsTruncated></ListBucketResult>";
|
|
568
|
+
res.writeHead(200, { "Content-Type": "application/xml" });
|
|
569
|
+
res.end(xml);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (req.method === "HEAD" && key && stored[key]) {
|
|
573
|
+
res.writeHead(200, { "Content-Length": stored[key].length });
|
|
574
|
+
res.end();
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (req.method === "DELETE" && key) {
|
|
578
|
+
if (stored[key]) {
|
|
579
|
+
delete stored[key]; // lgtm[js/remote-property-injection] test fixture; not a runtime path
|
|
580
|
+
res.writeHead(204); res.end();
|
|
581
|
+
} else {
|
|
582
|
+
res.writeHead(404); res.end();
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
res.writeHead(404); res.end();
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
var port = await listenOnRandomPort(server);
|
|
590
|
+
try {
|
|
591
|
+
var client = sigv4.create({
|
|
592
|
+
endpoint: "http://127.0.0.1:" + port,
|
|
593
|
+
region: "us-east-1",
|
|
594
|
+
bucket: "test-bucket",
|
|
595
|
+
accessKeyId: "AKIAIOSFODNN7EXAMPLE",
|
|
596
|
+
secretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
|
597
|
+
pathStyle: true, // 127.0.0.1 doesn't support virtual-hosted
|
|
598
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, // local mock — opt in to cleartext
|
|
599
|
+
allowInternal: true,
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// PUT
|
|
603
|
+
var content = Buffer.from("sigv4 test payload " + Date.now(), "utf8");
|
|
604
|
+
var putResult = await client.put("dir/object.bin", content);
|
|
605
|
+
check("sigv4 put returns size + etag", putResult.size === content.length && typeof putResult.etag === "string");
|
|
606
|
+
|
|
607
|
+
// GET round-trip
|
|
608
|
+
var got = await client.get("dir/object.bin");
|
|
609
|
+
check("sigv4 get round-trips bytes", got.equals(content));
|
|
610
|
+
|
|
611
|
+
// HEAD
|
|
612
|
+
var meta = await client.head("dir/object.bin");
|
|
613
|
+
check("sigv4 head returns size", meta.size === content.length);
|
|
614
|
+
|
|
615
|
+
// LIST
|
|
616
|
+
var listed = await client.list("");
|
|
617
|
+
check("sigv4 list returns 1 item", listed.items.length === 1);
|
|
618
|
+
check("sigv4 list returns the key", listed.items[0].key === "dir/object.bin");
|
|
619
|
+
|
|
620
|
+
// DELETE
|
|
621
|
+
var del = await client.delete("dir/object.bin");
|
|
622
|
+
check("sigv4 delete returns true", del === true);
|
|
623
|
+
var del2 = await client.delete("dir/object.bin");
|
|
624
|
+
check("sigv4 delete on missing returns false", del2 === false);
|
|
625
|
+
} finally {
|
|
626
|
+
server.close();
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function testGcsPrimitives() {
|
|
631
|
+
var gcs = require("../lib/object-store/gcs");
|
|
632
|
+
|
|
633
|
+
// base64url encoding (no padding, '+'→'-', '/'→'_')
|
|
634
|
+
var b1 = gcs._base64UrlEncode(Buffer.from("hello"));
|
|
635
|
+
check("gcs base64url encodes basic input", b1 === "aGVsbG8");
|
|
636
|
+
var b2 = gcs._base64UrlEncode(Buffer.from([0xff, 0xff, 0xff]));
|
|
637
|
+
check("gcs base64url has no padding", !/=/.test(b2));
|
|
638
|
+
check("gcs base64url uses '-' and '_'", !/\+|\//.test(b2));
|
|
639
|
+
|
|
640
|
+
// JWT signing with a real keypair
|
|
641
|
+
var sa = _makeFakeServiceAccount();
|
|
642
|
+
var jwt = gcs._signJwt(sa, "test-scope", "https://oauth2.googleapis.com/token");
|
|
643
|
+
var parts = jwt.split(".");
|
|
644
|
+
check("JWT has 3 parts", parts.length === 3);
|
|
645
|
+
// Header is base64url(JSON({ alg, typ }))
|
|
646
|
+
var headerJson = JSON.parse(Buffer.from(parts[0].replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8"));
|
|
647
|
+
check("JWT header alg is RS256", headerJson.alg === "RS256");
|
|
648
|
+
check("JWT header typ is JWT", headerJson.typ === "JWT");
|
|
649
|
+
var claimJson = JSON.parse(Buffer.from(parts[1].replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8"));
|
|
650
|
+
check("JWT iss is service-account email", claimJson.iss === sa.client_email);
|
|
651
|
+
check("JWT scope is honored", claimJson.scope === "test-scope");
|
|
652
|
+
check("JWT aud is token endpoint", claimJson.aud === "https://oauth2.googleapis.com/token");
|
|
653
|
+
check("JWT exp - iat = 3600", claimJson.exp - claimJson.iat === 3600);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function testGcsMockServer() {
|
|
657
|
+
var http = require("http");
|
|
658
|
+
var url = require("url");
|
|
659
|
+
var gcs = require("../lib/object-store/gcs");
|
|
660
|
+
var sa = _makeFakeServiceAccount();
|
|
661
|
+
|
|
662
|
+
var stored = {};
|
|
663
|
+
var tokenIssued = 0;
|
|
664
|
+
|
|
665
|
+
// Mock OAuth2 token endpoint + storage JSON API on the same server,
|
|
666
|
+
// routed by pathname.
|
|
667
|
+
var server = http.createServer(function (req, res) {
|
|
668
|
+
var u = new url.URL(req.url, "http://x");
|
|
669
|
+
var path = u.pathname;
|
|
670
|
+
|
|
671
|
+
// Token exchange
|
|
672
|
+
if (req.method === "POST" && path === "/token") {
|
|
673
|
+
var bufs = [];
|
|
674
|
+
req.on("data", function (c) { bufs.push(c); });
|
|
675
|
+
req.on("end", function () {
|
|
676
|
+
var body = Buffer.concat(bufs).toString("utf8");
|
|
677
|
+
if (!/grant_type=urn/.test(body) || !/assertion=/.test(body)) {
|
|
678
|
+
res.writeHead(400); res.end("bad request"); return;
|
|
679
|
+
}
|
|
680
|
+
tokenIssued += 1;
|
|
681
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
682
|
+
res.end(JSON.stringify({ access_token: "mock-access-token-" + tokenIssued, expires_in: 3600, token_type: "Bearer" }));
|
|
683
|
+
});
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// All storage operations require Bearer auth
|
|
688
|
+
if (!/^Bearer mock-access-token-/.test(req.headers["authorization"] || "")) {
|
|
689
|
+
res.writeHead(401); res.end("missing bearer"); return;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// PUT object: POST /upload/storage/v1/b/<bucket>/o?uploadType=media&name=<key>
|
|
693
|
+
if (req.method === "POST" && /^\/upload\/storage\/v1\/b\/[^/]+\/o$/.test(path)) {
|
|
694
|
+
var name = u.searchParams.get("name");
|
|
695
|
+
var bufs2 = [];
|
|
696
|
+
req.on("data", function (c) { bufs2.push(c); });
|
|
697
|
+
req.on("end", function () {
|
|
698
|
+
stored[name] = Buffer.concat(bufs2);
|
|
699
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
700
|
+
res.end(JSON.stringify({ name: name, size: String(stored[name].length), etag: "\"" + name + "\"", updated: "2026-04-25T00:00:00.000Z" }));
|
|
701
|
+
});
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// GET / HEAD: /storage/v1/b/<bucket>/o/<encoded-key>
|
|
706
|
+
var objectMatch = path.match(/^\/storage\/v1\/b\/[^/]+\/o\/(.+)$/);
|
|
707
|
+
if (objectMatch && req.method === "GET") {
|
|
708
|
+
var key = decodeURIComponent(objectMatch[1]);
|
|
709
|
+
if (u.searchParams.get("alt") === "media") {
|
|
710
|
+
if (stored[key]) {
|
|
711
|
+
res.writeHead(200); res.end(stored[key]);
|
|
712
|
+
} else {
|
|
713
|
+
res.writeHead(404); res.end();
|
|
714
|
+
}
|
|
715
|
+
} else {
|
|
716
|
+
// metadata
|
|
717
|
+
if (stored[key]) {
|
|
718
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
719
|
+
res.end(JSON.stringify({ name: key, size: String(stored[key].length), etag: "\"" + key + "\"", updated: "2026-04-25T00:00:00.000Z" }));
|
|
720
|
+
} else {
|
|
721
|
+
res.writeHead(404); res.end();
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (objectMatch && req.method === "DELETE") {
|
|
727
|
+
var dkey = decodeURIComponent(objectMatch[1]);
|
|
728
|
+
if (stored[dkey]) { delete stored[dkey]; res.writeHead(204); res.end(); }
|
|
729
|
+
else { res.writeHead(404); res.end(); }
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// LIST: /storage/v1/b/<bucket>/o (no /<key>)
|
|
734
|
+
if (req.method === "GET" && /^\/storage\/v1\/b\/[^/]+\/o$/.test(path)) {
|
|
735
|
+
var prefix = u.searchParams.get("prefix") || "";
|
|
736
|
+
var items = Object.keys(stored).filter(function (k) { return k.indexOf(prefix) === 0; }).map(function (k) {
|
|
737
|
+
return { name: k, size: String(stored[k].length), updated: "2026-04-25T00:00:00.000Z" };
|
|
738
|
+
});
|
|
739
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
740
|
+
res.end(JSON.stringify({ items: items }));
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
res.writeHead(404); res.end();
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
var port = await listenOnRandomPort(server);
|
|
748
|
+
try {
|
|
749
|
+
var client = gcs.create({
|
|
750
|
+
bucket: "test-bucket",
|
|
751
|
+
serviceAccount: sa,
|
|
752
|
+
endpoint: "http://127.0.0.1:" + port,
|
|
753
|
+
tokenEndpoint: "http://127.0.0.1:" + port + "/token",
|
|
754
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
755
|
+
allowInternal: true,
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
var content = Buffer.from("gcs test payload " + Date.now(), "utf8");
|
|
759
|
+
var putResult = await client.put("dir/object.bin", content);
|
|
760
|
+
check("gcs put returns size", putResult.size === content.length);
|
|
761
|
+
|
|
762
|
+
var got = await client.get("dir/object.bin");
|
|
763
|
+
check("gcs get round-trips bytes", got.equals(content));
|
|
764
|
+
|
|
765
|
+
var meta = await client.head("dir/object.bin");
|
|
766
|
+
check("gcs head returns size", meta.size === content.length);
|
|
767
|
+
|
|
768
|
+
var listed = await client.list("");
|
|
769
|
+
check("gcs list returns 1 item", listed.items.length === 1);
|
|
770
|
+
check("gcs list returns the key", listed.items[0].key === "dir/object.bin");
|
|
771
|
+
|
|
772
|
+
var del = await client.delete("dir/object.bin");
|
|
773
|
+
check("gcs delete returns true", del === true);
|
|
774
|
+
|
|
775
|
+
// Token caching: should have only issued ONE token across all calls
|
|
776
|
+
check("gcs token issued once and cached across calls", tokenIssued === 1);
|
|
777
|
+
} finally {
|
|
778
|
+
server.close();
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function testAzureBlobPrimitives() {
|
|
783
|
+
var az = require("../lib/object-store/azure-blob");
|
|
784
|
+
|
|
785
|
+
// signRequest produces SharedKey-format Authorization
|
|
786
|
+
var s = az.signRequest({
|
|
787
|
+
method: "PUT",
|
|
788
|
+
url: "https://test.blob.core.windows.net/c/key1",
|
|
789
|
+
headers: { "Content-Type": "application/octet-stream", "Content-Length": "5", "x-ms-blob-type": "BlockBlob" },
|
|
790
|
+
accountName: "test",
|
|
791
|
+
accountKey: Buffer.from("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "utf8").toString("base64"),
|
|
792
|
+
});
|
|
793
|
+
check("azure signRequest produces SharedKey auth", /^SharedKey test:/.test(s.headers["Authorization"]));
|
|
794
|
+
check("azure signRequest sets x-ms-version", !!s.headers["x-ms-version"]);
|
|
795
|
+
check("azure signRequest sets x-ms-date", !!s.headers["x-ms-date"]);
|
|
796
|
+
check("azure signature is base64", /^[A-Za-z0-9+/=]+$/.test(s.signature));
|
|
797
|
+
|
|
798
|
+
// Same inputs at same time produce same signature
|
|
799
|
+
var date = new Date(Date.UTC(2026, 3, 25, 12, 34, 56));
|
|
800
|
+
var dateStr = date.toUTCString();
|
|
801
|
+
var s1 = az.signRequest({
|
|
802
|
+
method: "GET",
|
|
803
|
+
url: "https://test.blob.core.windows.net/c/key2",
|
|
804
|
+
headers: { "x-ms-date": dateStr },
|
|
805
|
+
accountName: "test",
|
|
806
|
+
accountKey: Buffer.from("ZZZZZZZZ", "base64").toString("base64"),
|
|
807
|
+
});
|
|
808
|
+
var s2 = az.signRequest({
|
|
809
|
+
method: "GET",
|
|
810
|
+
url: "https://test.blob.core.windows.net/c/key2",
|
|
811
|
+
headers: { "x-ms-date": dateStr },
|
|
812
|
+
accountName: "test",
|
|
813
|
+
accountKey: Buffer.from("ZZZZZZZZ", "base64").toString("base64"),
|
|
814
|
+
});
|
|
815
|
+
check("azure signature deterministic for same inputs", s1.signature === s2.signature);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
async function testAzureBlobMockServer() {
|
|
819
|
+
var http = require("http");
|
|
820
|
+
var az = require("../lib/object-store/azure-blob");
|
|
821
|
+
|
|
822
|
+
var stored = {};
|
|
823
|
+
var server = http.createServer(function (req, res) {
|
|
824
|
+
if (!/^SharedKey /.test(req.headers["authorization"] || "")) {
|
|
825
|
+
res.writeHead(401); res.end("missing SharedKey"); return;
|
|
826
|
+
}
|
|
827
|
+
if (!req.headers["x-ms-version"]) { res.writeHead(400); res.end("missing x-ms-version"); return; }
|
|
828
|
+
if (!req.headers["x-ms-date"]) { res.writeHead(400); res.end("missing x-ms-date"); return; }
|
|
829
|
+
|
|
830
|
+
var path = req.url.split("?")[0];
|
|
831
|
+
var keyMatch = path.match(/^\/[^/]+\/(.+)$/);
|
|
832
|
+
var key = keyMatch ? keyMatch[1] : null;
|
|
833
|
+
|
|
834
|
+
if (req.method === "PUT" && key) {
|
|
835
|
+
if (req.headers["x-ms-blob-type"] !== "BlockBlob") { res.writeHead(400); res.end("bad blob type"); return; }
|
|
836
|
+
var bufs = [];
|
|
837
|
+
req.on("data", function (c) { bufs.push(c); });
|
|
838
|
+
req.on("end", function () {
|
|
839
|
+
stored[key] = Buffer.concat(bufs); // lgtm[js/remote-property-injection] test fixture; not a runtime path
|
|
840
|
+
res.writeHead(201, { ETag: "\"" + key + "\"" });
|
|
841
|
+
res.end();
|
|
842
|
+
});
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (req.method === "GET" && key && stored[key]) {
|
|
846
|
+
res.writeHead(200, { "Content-Length": stored[key].length });
|
|
847
|
+
res.end(stored[key]);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (req.method === "HEAD" && key && stored[key]) {
|
|
851
|
+
res.writeHead(200, { "Content-Length": stored[key].length });
|
|
852
|
+
res.end();
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (req.method === "DELETE" && key) {
|
|
856
|
+
if (stored[key]) { delete stored[key]; res.writeHead(202); res.end(); } // lgtm[js/remote-property-injection] test fixture; not a runtime path
|
|
857
|
+
else { res.writeHead(404); res.end(); }
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (req.method === "GET" && (req.url || "").indexOf("comp=list") !== -1) {
|
|
861
|
+
var xml = "<?xml version=\"1.0\"?><EnumerationResults><Blobs>";
|
|
862
|
+
Object.keys(stored).forEach(function (k) {
|
|
863
|
+
xml += "<Blob><Name>" + k + "</Name><Properties><Content-Length>" + stored[k].length + "</Content-Length><Last-Modified>Sat, 25 Apr 2026 00:00:00 GMT</Last-Modified></Properties></Blob>";
|
|
864
|
+
});
|
|
865
|
+
xml += "</Blobs><NextMarker/></EnumerationResults>";
|
|
866
|
+
res.writeHead(200, { "Content-Type": "application/xml" });
|
|
867
|
+
res.end(xml);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
res.writeHead(404); res.end();
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
var port = await listenOnRandomPort(server);
|
|
874
|
+
try {
|
|
875
|
+
var client = az.create({
|
|
876
|
+
accountName: "test",
|
|
877
|
+
accountKey: Buffer.from("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "utf8").toString("base64"),
|
|
878
|
+
container: "test-container",
|
|
879
|
+
endpoint: "http://127.0.0.1:" + port,
|
|
880
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
881
|
+
allowInternal: true,
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
var content = Buffer.from("azure test payload " + Date.now(), "utf8");
|
|
885
|
+
var putResult = await client.put("dir/blob.bin", content);
|
|
886
|
+
check("azure put returns size", putResult.size === content.length);
|
|
887
|
+
|
|
888
|
+
var got = await client.get("dir/blob.bin");
|
|
889
|
+
check("azure get round-trips bytes", got.equals(content));
|
|
890
|
+
|
|
891
|
+
var meta = await client.head("dir/blob.bin");
|
|
892
|
+
check("azure head returns size", meta.size === content.length);
|
|
893
|
+
|
|
894
|
+
var listed = await client.list("");
|
|
895
|
+
check("azure list returns 1 item", listed.items.length === 1);
|
|
896
|
+
check("azure list returns the key", listed.items[0].key === "dir/blob.bin");
|
|
897
|
+
|
|
898
|
+
var del = await client.delete("dir/blob.bin");
|
|
899
|
+
check("azure delete returns true", del === true);
|
|
900
|
+
} finally {
|
|
901
|
+
server.close();
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
async function testQueueLocal() {
|
|
906
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-queue-"));
|
|
907
|
+
try {
|
|
908
|
+
await setupTestDb(tmpDir);
|
|
909
|
+
b.queue.init({ backends: { primary: { protocol: "local" } } });
|
|
910
|
+
|
|
911
|
+
check("queue namespace present", typeof b.queue === "object");
|
|
912
|
+
check("queue.listBackends has 1 entry", b.queue.listBackends().length === 1);
|
|
913
|
+
|
|
914
|
+
// Enqueue
|
|
915
|
+
var result = await b.queue.enqueue("send-welcome", { userId: "u-1", email: "a@b.com" }, {
|
|
916
|
+
classification: "personal",
|
|
917
|
+
traceId: "trace-123",
|
|
918
|
+
});
|
|
919
|
+
check("enqueue returns jobId", typeof result.jobId === "string");
|
|
920
|
+
check("enqueue returns queueName", result.queueName === "send-welcome");
|
|
921
|
+
check("enqueue returns classification", result.classification === "personal");
|
|
922
|
+
|
|
923
|
+
// size reflects pending
|
|
924
|
+
check("size returns 1 after one enqueue", (await b.queue.size("send-welcome")) === 1);
|
|
925
|
+
|
|
926
|
+
// payload sealed on disk
|
|
927
|
+
var rawRow = b.db.prepare("SELECT payload FROM _blamejs_jobs WHERE _id = ?").get(result.jobId);
|
|
928
|
+
check("queue payload sealed in DB", rawRow.payload.startsWith("vault:"));
|
|
929
|
+
|
|
930
|
+
// unrelated queue is independent
|
|
931
|
+
check("size returns 0 for empty queue", (await b.queue.size("other-queue")) === 0);
|
|
932
|
+
|
|
933
|
+
// purge clears
|
|
934
|
+
var purged = await b.queue.purge("send-welcome");
|
|
935
|
+
check("purge returns count of deleted", purged === 1);
|
|
936
|
+
check("size returns 0 after purge", (await b.queue.size("send-welcome")) === 0);
|
|
937
|
+
|
|
938
|
+
// Reserved table name protection still works
|
|
939
|
+
check("_blamejs_jobs is reserved", b.db.RESERVED_TABLE_NAMES.has("_blamejs_jobs"));
|
|
940
|
+
} finally {
|
|
941
|
+
try { await b.queue.shutdown({ timeoutMs: 1000 }); } catch (_e) {}
|
|
942
|
+
await teardownTestDb(tmpDir);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
async function testQueueConsume() {
|
|
947
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-qcons-"));
|
|
948
|
+
try {
|
|
949
|
+
await setupTestDb(tmpDir);
|
|
950
|
+
b.queue.init({ backends: { primary: { protocol: "local" } } });
|
|
951
|
+
|
|
952
|
+
var processed = [];
|
|
953
|
+
var consumer = b.queue.consume("test-job", function (job) {
|
|
954
|
+
processed.push(job.payload);
|
|
955
|
+
return Promise.resolve();
|
|
956
|
+
}, { concurrency: 2, pollIntervalMs: 50, fastPollMs: 20, leaseDurationMs: 5000 });
|
|
957
|
+
|
|
958
|
+
await b.queue.enqueue("test-job", { msg: "hello-1" });
|
|
959
|
+
await b.queue.enqueue("test-job", { msg: "hello-2" });
|
|
960
|
+
await b.queue.enqueue("test-job", { msg: "hello-3" });
|
|
961
|
+
|
|
962
|
+
// Wait for processing (poll up to 3s)
|
|
963
|
+
var deadline = Date.now() + 3000;
|
|
964
|
+
while (processed.length < 3 && Date.now() < deadline) {
|
|
965
|
+
await new Promise(function (r) { setTimeout(r, 50); });
|
|
966
|
+
}
|
|
967
|
+
check("consume processed all 3 jobs", processed.length === 3);
|
|
968
|
+
check("payloads decoded correctly", processed.some(p => p.msg === "hello-1"));
|
|
969
|
+
check("queue size 0 after consume", (await b.queue.size("test-job")) === 0);
|
|
970
|
+
|
|
971
|
+
// All jobs should be in 'done' status
|
|
972
|
+
var doneCount = b.db.prepare("SELECT COUNT(*) AS n FROM _blamejs_jobs WHERE queueName = ? AND status = ?").get("test-job", "done");
|
|
973
|
+
check("all jobs marked done", doneCount.n === 3);
|
|
974
|
+
|
|
975
|
+
// Drain buffered audit emissions before reading audit_log.
|
|
976
|
+
await b.audit.flush();
|
|
977
|
+
// Audit chain has system.queue.enqueue + .consume.start + .consume.success
|
|
978
|
+
var enqRows = await b.audit.query({ action: "system.queue.enqueue" });
|
|
979
|
+
check("audit recorded enqueue events", enqRows.length === 3);
|
|
980
|
+
var sucRows = await b.audit.query({ action: "system.queue.consume.success" });
|
|
981
|
+
check("audit recorded consume.success events", sucRows.length === 3);
|
|
982
|
+
|
|
983
|
+
consumer.cancel();
|
|
984
|
+
} finally {
|
|
985
|
+
try { await b.queue.shutdown({ timeoutMs: 1000 }); } catch (_e) {}
|
|
986
|
+
await teardownTestDb(tmpDir);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
async function testQueueRetryAndFail() {
|
|
991
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-qfail-"));
|
|
992
|
+
try {
|
|
993
|
+
await setupTestDb(tmpDir);
|
|
994
|
+
b.queue.init({ backends: { primary: { protocol: "local" } } });
|
|
995
|
+
|
|
996
|
+
var attempts = 0;
|
|
997
|
+
var consumer = b.queue.consume("fail-job", function (_job) {
|
|
998
|
+
attempts += 1;
|
|
999
|
+
throw new Error("simulated failure attempt " + attempts);
|
|
1000
|
+
}, { concurrency: 1, pollIntervalMs: 50, fastPollMs: 20, leaseDurationMs: 5000 });
|
|
1001
|
+
|
|
1002
|
+
await b.queue.enqueue("fail-job", { x: 1 }, { maxAttempts: 3 });
|
|
1003
|
+
|
|
1004
|
+
// Wait until job is finally failed (3 attempts × ~exponential backoff = up to ~7s)
|
|
1005
|
+
var deadline = Date.now() + 12000;
|
|
1006
|
+
var lastStatus;
|
|
1007
|
+
while (Date.now() < deadline) {
|
|
1008
|
+
var row = b.db.prepare("SELECT status FROM _blamejs_jobs WHERE queueName = ?").get("fail-job");
|
|
1009
|
+
lastStatus = row && row.status;
|
|
1010
|
+
if (lastStatus === "failed") break;
|
|
1011
|
+
await new Promise(function (r) { setTimeout(r, 100); });
|
|
1012
|
+
}
|
|
1013
|
+
check("job ends up in 'failed' status after maxAttempts", lastStatus === "failed");
|
|
1014
|
+
check("handler invoked maxAttempts times", attempts === 3);
|
|
1015
|
+
|
|
1016
|
+
// Drain buffered audit emissions before reading audit_log.
|
|
1017
|
+
await b.audit.flush();
|
|
1018
|
+
// Audit chain has consume.failure events
|
|
1019
|
+
var failRows = await b.audit.query({ action: "system.queue.consume.failure" });
|
|
1020
|
+
check("audit recorded consume.failure events", failRows.length === 3);
|
|
1021
|
+
|
|
1022
|
+
consumer.cancel();
|
|
1023
|
+
} finally {
|
|
1024
|
+
try { await b.queue.shutdown({ timeoutMs: 1000 }); } catch (_e) {}
|
|
1025
|
+
await teardownTestDb(tmpDir);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
async function testQueueLeaseExpiry() {
|
|
1030
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-qlease-"));
|
|
1031
|
+
try {
|
|
1032
|
+
await setupTestDb(tmpDir);
|
|
1033
|
+
b.queue.init({ backends: { primary: { protocol: "local" } } });
|
|
1034
|
+
|
|
1035
|
+
// Manually call lease via the backend to simulate a crashed handler
|
|
1036
|
+
// (lease the job, never complete or fail it).
|
|
1037
|
+
var localBackend = require("../lib/queue-local").create({});
|
|
1038
|
+
await b.queue.enqueue("orphan-job", { x: 1 });
|
|
1039
|
+
var leased = await localBackend.lease("orphan-job", 100, 1); // 100ms lease
|
|
1040
|
+
check("lease returned the job", leased.length === 1);
|
|
1041
|
+
check("after lease, job status is inflight",
|
|
1042
|
+
b.db.prepare("SELECT status FROM _blamejs_jobs WHERE queueName = ?").get("orphan-job").status === "inflight");
|
|
1043
|
+
|
|
1044
|
+
// Wait for lease to expire, then sweep
|
|
1045
|
+
await new Promise(function (r) { setTimeout(r, 200); });
|
|
1046
|
+
var swept = await localBackend.sweepExpired();
|
|
1047
|
+
check("sweepExpired returned 1 unstuck job", swept === 1);
|
|
1048
|
+
check("unstuck job is back to pending",
|
|
1049
|
+
b.db.prepare("SELECT status FROM _blamejs_jobs WHERE queueName = ?").get("orphan-job").status === "pending");
|
|
1050
|
+
} finally {
|
|
1051
|
+
try { await b.queue.shutdown({ timeoutMs: 1000 }); } catch (_e) {}
|
|
1052
|
+
await teardownTestDb(tmpDir);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
async function testQueueShutdown() {
|
|
1057
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-qsh-"));
|
|
1058
|
+
try {
|
|
1059
|
+
await setupTestDb(tmpDir);
|
|
1060
|
+
b.queue.init({ backends: { primary: { protocol: "local" } } });
|
|
1061
|
+
|
|
1062
|
+
var processed = 0;
|
|
1063
|
+
var consumer = b.queue.consume("shutdown-job", async function (_job) {
|
|
1064
|
+
// Long-running handler
|
|
1065
|
+
await new Promise(function (r) { setTimeout(r, 200); });
|
|
1066
|
+
processed += 1;
|
|
1067
|
+
}, { concurrency: 2, pollIntervalMs: 30, fastPollMs: 10, leaseDurationMs: 5000 });
|
|
1068
|
+
|
|
1069
|
+
for (var i = 0; i < 3; i++) await b.queue.enqueue("shutdown-job", { i: i });
|
|
1070
|
+
await new Promise(function (r) { setTimeout(r, 100) }); // let some lease
|
|
1071
|
+
|
|
1072
|
+
var t0 = Date.now();
|
|
1073
|
+
await b.queue.shutdown({ timeoutMs: 5000 });
|
|
1074
|
+
var elapsed = Date.now() - t0;
|
|
1075
|
+
|
|
1076
|
+
check("shutdown waits for in-flight handlers", processed >= 1);
|
|
1077
|
+
check("shutdown completes under timeout", elapsed < 5000);
|
|
1078
|
+
void consumer;
|
|
1079
|
+
} finally {
|
|
1080
|
+
await teardownTestDb(tmpDir);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
async function testJobsDefineAndEnqueue() {
|
|
1085
|
+
// Surface tests + a single-job round-trip. Uses the framework's
|
|
1086
|
+
// built-in 'local' queue protocol (SQLite via the framework DB).
|
|
1087
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-jobs-"));
|
|
1088
|
+
try {
|
|
1089
|
+
await setupTestDb(tmpDir);
|
|
1090
|
+
b.queue.init({ backends: { primary: { protocol: "local" } } });
|
|
1091
|
+
var jobs = b.jobs.create();
|
|
1092
|
+
var processed = [];
|
|
1093
|
+
jobs.define("welcome", async function (job) {
|
|
1094
|
+
processed.push(job.payload.userId);
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
// stats() pre-start
|
|
1098
|
+
var s0 = jobs.stats();
|
|
1099
|
+
check("jobs.stats: defined names listed", s0.defined.length === 1 && s0.defined[0] === "welcome");
|
|
1100
|
+
check("jobs.stats: started=false before start", s0.started === false);
|
|
1101
|
+
|
|
1102
|
+
// enqueue before start is fine — queue persists rows
|
|
1103
|
+
var enq = await jobs.enqueue("welcome", { userId: "u-1" });
|
|
1104
|
+
check("jobs.enqueue returns jobId", typeof enq.jobId === "string");
|
|
1105
|
+
|
|
1106
|
+
await jobs.start();
|
|
1107
|
+
check("jobs.stats: started=true after start", jobs.stats().started === true);
|
|
1108
|
+
|
|
1109
|
+
// Wait for the consumer to drain
|
|
1110
|
+
var t0 = Date.now();
|
|
1111
|
+
while (processed.length === 0 && Date.now() - t0 < 5000) {
|
|
1112
|
+
await new Promise(function (r) { setTimeout(r, 50); });
|
|
1113
|
+
}
|
|
1114
|
+
check("jobs: handler ran for enqueued job", processed.length === 1 && processed[0] === "u-1");
|
|
1115
|
+
|
|
1116
|
+
await jobs.shutdown({ timeoutMs: 2000 });
|
|
1117
|
+
check("jobs.shutdown: started=false after", jobs.stats().started === false);
|
|
1118
|
+
} finally {
|
|
1119
|
+
await teardownTestDb(tmpDir);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
async function testJobsValidation() {
|
|
1124
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-jobs-"));
|
|
1125
|
+
try {
|
|
1126
|
+
await setupTestDb(tmpDir);
|
|
1127
|
+
b.queue.init({ backends: { primary: { protocol: "local" } } });
|
|
1128
|
+
var jobs = b.jobs.create();
|
|
1129
|
+
|
|
1130
|
+
// Invalid name / handler
|
|
1131
|
+
var threw = null;
|
|
1132
|
+
try { jobs.define("", async function () {}); } catch (e) { threw = e; }
|
|
1133
|
+
check("jobs.define: empty name rejected", threw && threw.code === "INVALID_NAME");
|
|
1134
|
+
|
|
1135
|
+
threw = null;
|
|
1136
|
+
try { jobs.define("x", "not-a-fn"); } catch (e) { threw = e; }
|
|
1137
|
+
check("jobs.define: non-function handler rejected", threw && threw.code === "INVALID_HANDLER");
|
|
1138
|
+
|
|
1139
|
+
// Duplicate
|
|
1140
|
+
jobs.define("dup", async function () {});
|
|
1141
|
+
threw = null;
|
|
1142
|
+
try { jobs.define("dup", async function () {}); } catch (e) { threw = e; }
|
|
1143
|
+
check("jobs.define: duplicate name rejected", threw && threw.code === "DUPLICATE_NAME");
|
|
1144
|
+
|
|
1145
|
+
// enqueue without define
|
|
1146
|
+
threw = null;
|
|
1147
|
+
try { await jobs.enqueue("never-defined", {}); } catch (e) { threw = e; }
|
|
1148
|
+
check("jobs.enqueue: undefined name rejected", threw && threw.code === "UNDEFINED_NAME");
|
|
1149
|
+
|
|
1150
|
+
// allowUnregistered: true bypasses
|
|
1151
|
+
var permissive = b.jobs.create({ allowUnregisteredEnqueue: true });
|
|
1152
|
+
var enqOk = await permissive.enqueue("late-binding", { x: 1 });
|
|
1153
|
+
check("jobs.enqueue: allowUnregistered passes", typeof enqOk.jobId === "string");
|
|
1154
|
+
|
|
1155
|
+
// define after start rejected
|
|
1156
|
+
await jobs.start();
|
|
1157
|
+
threw = null;
|
|
1158
|
+
try { jobs.define("post-start", async function () {}); } catch (e) { threw = e; }
|
|
1159
|
+
check("jobs.define after start rejected", threw && threw.code === "ALREADY_STARTED");
|
|
1160
|
+
|
|
1161
|
+
await jobs.shutdown({ timeoutMs: 2000 });
|
|
1162
|
+
await permissive.shutdown({ timeoutMs: 2000 });
|
|
1163
|
+
} finally {
|
|
1164
|
+
await teardownTestDb(tmpDir);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
async function testJobsMultipleHandlers() {
|
|
1169
|
+
// Two handlers, each consuming its own queue, dispatched correctly.
|
|
1170
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-jobs-"));
|
|
1171
|
+
try {
|
|
1172
|
+
await setupTestDb(tmpDir);
|
|
1173
|
+
b.queue.init({ backends: { primary: { protocol: "local" } } });
|
|
1174
|
+
var jobs = b.jobs.create({
|
|
1175
|
+
consumerDefaults: { pollIntervalMs: 30, fastPollMs: 10 },
|
|
1176
|
+
});
|
|
1177
|
+
var emails = [];
|
|
1178
|
+
var rebuilds = [];
|
|
1179
|
+
jobs.define("send-email", async function (job) { emails.push(job.payload.to); });
|
|
1180
|
+
jobs.define("rebuild-index", async function (job) { rebuilds.push(job.payload.what); });
|
|
1181
|
+
|
|
1182
|
+
await jobs.start();
|
|
1183
|
+
await jobs.enqueue("send-email", { to: "alice@example.com" });
|
|
1184
|
+
await jobs.enqueue("send-email", { to: "bob@example.com" });
|
|
1185
|
+
await jobs.enqueue("rebuild-index", { what: "users" });
|
|
1186
|
+
|
|
1187
|
+
var t0 = Date.now();
|
|
1188
|
+
while ((emails.length < 2 || rebuilds.length < 1) && Date.now() - t0 < 5000) {
|
|
1189
|
+
await new Promise(function (r) { setTimeout(r, 50); });
|
|
1190
|
+
}
|
|
1191
|
+
check("jobs: both email handlers ran", emails.length === 2);
|
|
1192
|
+
check("jobs: rebuild handler ran", rebuilds.length === 1);
|
|
1193
|
+
// Cross-handler isolation
|
|
1194
|
+
check("jobs: email handler didn't see rebuild payload",
|
|
1195
|
+
emails.indexOf("users") === -1);
|
|
1196
|
+
|
|
1197
|
+
await jobs.shutdown({ timeoutMs: 2000 });
|
|
1198
|
+
} finally {
|
|
1199
|
+
await teardownTestDb(tmpDir);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function testJobsSurface() {
|
|
1204
|
+
check("b.jobs namespace present", typeof b.jobs === "object");
|
|
1205
|
+
check("b.jobs.create is a function", typeof b.jobs.create === "function");
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
async function testLogStreamLocal() {
|
|
1209
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-log-"));
|
|
1210
|
+
try {
|
|
1211
|
+
await setupTestDb(tmpDir);
|
|
1212
|
+
b.logStream.init({
|
|
1213
|
+
sinks: { primary: { protocol: "local", dir: path.join(tmpDir, "logs"), maxFileBytes: 1024 } },
|
|
1214
|
+
minLevel: "debug",
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
check("logStream namespace present", typeof b.logStream === "object");
|
|
1218
|
+
check("logStream.LEVELS includes debug/info/warn/error",
|
|
1219
|
+
b.logStream.LEVELS.length === 4);
|
|
1220
|
+
|
|
1221
|
+
b.logStream.info("hello world", { user: "alice" });
|
|
1222
|
+
b.logStream.warn("watch out", { password: "should-be-redacted" });
|
|
1223
|
+
b.logStream.error("kaboom", { jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.aaaa.bbbb" });
|
|
1224
|
+
|
|
1225
|
+
// Allow async writes to complete
|
|
1226
|
+
await new Promise(function (r) { setTimeout(r, 50); });
|
|
1227
|
+
await b.logStream.shutdown();
|
|
1228
|
+
|
|
1229
|
+
var logPath = path.join(tmpDir, "logs", "blamejs.log");
|
|
1230
|
+
check("log file exists", fs.existsSync(logPath));
|
|
1231
|
+
var content = fs.readFileSync(logPath, "utf8");
|
|
1232
|
+
var lines = content.trim().split("\n").filter(Boolean);
|
|
1233
|
+
check("3 events emitted as JSON lines", lines.length === 3);
|
|
1234
|
+
|
|
1235
|
+
var infoRecord = JSON.parse(lines[0]);
|
|
1236
|
+
check("first record has level=info", infoRecord.level === "info");
|
|
1237
|
+
check("first record has message", infoRecord.message === "hello world");
|
|
1238
|
+
check("first record has meta.user", infoRecord.meta.user === "alice");
|
|
1239
|
+
|
|
1240
|
+
var warnRecord = JSON.parse(lines[1]);
|
|
1241
|
+
check("warn record password is redacted", warnRecord.meta.password === "[REDACTED]");
|
|
1242
|
+
|
|
1243
|
+
var errRecord = JSON.parse(lines[2]);
|
|
1244
|
+
check("error record JWT-shaped value redacted", errRecord.meta.jwt === "[REDACTED-JWT]");
|
|
1245
|
+
} finally {
|
|
1246
|
+
await teardownTestDb(tmpDir);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
async function testLogStreamWebhook() {
|
|
1251
|
+
var http = require("http");
|
|
1252
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-loghook-"));
|
|
1253
|
+
try {
|
|
1254
|
+
await setupTestDb(tmpDir);
|
|
1255
|
+
|
|
1256
|
+
var received = [];
|
|
1257
|
+
var server = http.createServer(function (req, res) {
|
|
1258
|
+
if (req.headers["authorization"] !== "Bearer test-token") {
|
|
1259
|
+
res.writeHead(401); res.end("missing auth"); return;
|
|
1260
|
+
}
|
|
1261
|
+
var bufs = [];
|
|
1262
|
+
req.on("data", function (c) { bufs.push(c); });
|
|
1263
|
+
req.on("end", function () {
|
|
1264
|
+
try {
|
|
1265
|
+
var batch = JSON.parse(Buffer.concat(bufs).toString("utf8"));
|
|
1266
|
+
batch.forEach(function (ev) { received.push(ev); });
|
|
1267
|
+
res.writeHead(200); res.end("ok");
|
|
1268
|
+
} catch (e) { res.writeHead(400); res.end(e.message); }
|
|
1269
|
+
});
|
|
1270
|
+
});
|
|
1271
|
+
var port = await listenOnRandomPort(server);
|
|
1272
|
+
|
|
1273
|
+
try {
|
|
1274
|
+
b.logStream.init({
|
|
1275
|
+
sinks: {
|
|
1276
|
+
siem: {
|
|
1277
|
+
protocol: "webhook",
|
|
1278
|
+
url: "http://127.0.0.1:" + port + "/ingest",
|
|
1279
|
+
auth: "bearer",
|
|
1280
|
+
token: "test-token",
|
|
1281
|
+
batchSize: 2,
|
|
1282
|
+
maxBatchAgeMs: 100,
|
|
1283
|
+
bodyShape: "array",
|
|
1284
|
+
retry: { maxAttempts: 1 },
|
|
1285
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
1286
|
+
allowInternal: true,
|
|
1287
|
+
},
|
|
1288
|
+
},
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
b.logStream.info("first", { x: 1 });
|
|
1292
|
+
b.logStream.info("second", { x: 2 });
|
|
1293
|
+
// batchSize=2 should trigger immediate flush
|
|
1294
|
+
await new Promise(function (r) { setTimeout(r, 200); });
|
|
1295
|
+
|
|
1296
|
+
check("webhook received 2 events", received.length === 2);
|
|
1297
|
+
check("first event message", received[0].message === "first");
|
|
1298
|
+
check("second event message", received[1].message === "second");
|
|
1299
|
+
|
|
1300
|
+
// Auth failure path: send another event after server stops accepting
|
|
1301
|
+
b.logStream.info("third", { x: 3 });
|
|
1302
|
+
await new Promise(function (r) { setTimeout(r, 200); });
|
|
1303
|
+
check("third event delivered (under batch size, flushed by age)", received.length >= 3);
|
|
1304
|
+
|
|
1305
|
+
await b.logStream.shutdown();
|
|
1306
|
+
} finally {
|
|
1307
|
+
server.close();
|
|
1308
|
+
}
|
|
1309
|
+
} finally {
|
|
1310
|
+
await teardownTestDb(tmpDir);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
async function testLogStreamBidirectional() {
|
|
1315
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-logbidi-"));
|
|
1316
|
+
try {
|
|
1317
|
+
await setupTestDb(tmpDir);
|
|
1318
|
+
b.logStream.init({
|
|
1319
|
+
sinks: { primary: { protocol: "local", dir: path.join(tmpDir, "logs") } },
|
|
1320
|
+
});
|
|
1321
|
+
|
|
1322
|
+
var received = [];
|
|
1323
|
+
var unregister = b.logStream.onIncoming(async function (payload, opts) {
|
|
1324
|
+
received.push({ payload: payload, opts: opts });
|
|
1325
|
+
return "ack-" + (received.length);
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
var results = await b.logStream.deliverIncoming({ command: "block-user", userId: "u-123" }, { source: "siem-test" });
|
|
1329
|
+
check("deliverIncoming routes to handler", received.length === 1);
|
|
1330
|
+
check("payload preserved", received[0].payload.command === "block-user");
|
|
1331
|
+
check("opts.source preserved", received[0].opts.source === "siem-test");
|
|
1332
|
+
check("handler return value captured in results", results[0].ok === true && results[0].value === "ack-1");
|
|
1333
|
+
|
|
1334
|
+
// Drain buffered audit emissions before reading audit_log.
|
|
1335
|
+
await b.audit.flush();
|
|
1336
|
+
// Audit: incoming command logged
|
|
1337
|
+
var incRows = await b.audit.query({ action: "system.log.incoming" });
|
|
1338
|
+
check("audit recorded system.log.incoming", incRows.length === 1);
|
|
1339
|
+
|
|
1340
|
+
// Unregister and verify no further dispatch
|
|
1341
|
+
unregister();
|
|
1342
|
+
await b.logStream.deliverIncoming({ command: "second" });
|
|
1343
|
+
check("after unregister, handler no longer called", received.length === 1);
|
|
1344
|
+
|
|
1345
|
+
await b.logStream.shutdown();
|
|
1346
|
+
} finally {
|
|
1347
|
+
await teardownTestDb(tmpDir);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
async function testExternalDbBasic() {
|
|
1352
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-extdb-"));
|
|
1353
|
+
try {
|
|
1354
|
+
await setupTestDb(tmpDir);
|
|
1355
|
+
var driver = _makeFakeDriver();
|
|
1356
|
+
b.externalDb.init({
|
|
1357
|
+
backends: {
|
|
1358
|
+
"primary": {
|
|
1359
|
+
connect: driver.connect,
|
|
1360
|
+
query: driver.query,
|
|
1361
|
+
close: driver.close,
|
|
1362
|
+
ping: driver.ping,
|
|
1363
|
+
},
|
|
1364
|
+
},
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
check("externalDb namespace present", typeof b.externalDb === "object");
|
|
1368
|
+
|
|
1369
|
+
var listed = b.externalDb.listBackends();
|
|
1370
|
+
check("listBackends returns 1 entry", listed.length === 1);
|
|
1371
|
+
|
|
1372
|
+
var insertResult = await b.externalDb.query(
|
|
1373
|
+
"INSERT INTO kv (id, value) VALUES ($1, $2)", ["k1", "v1"]
|
|
1374
|
+
);
|
|
1375
|
+
check("insert returns rowCount = 1", insertResult.rowCount === 1);
|
|
1376
|
+
|
|
1377
|
+
var selectResult = await b.externalDb.query(
|
|
1378
|
+
"SELECT id, value FROM kv WHERE id = $1", ["k1"]
|
|
1379
|
+
);
|
|
1380
|
+
check("select returns the inserted row", selectResult.rows[0].value === "v1");
|
|
1381
|
+
|
|
1382
|
+
var miss = await b.externalDb.query(
|
|
1383
|
+
"SELECT id, value FROM kv WHERE id = $1", ["missing"]
|
|
1384
|
+
);
|
|
1385
|
+
check("miss returns 0 rows", miss.rowCount === 0);
|
|
1386
|
+
|
|
1387
|
+
// Health check
|
|
1388
|
+
var hc = await b.externalDb.healthCheck();
|
|
1389
|
+
check("healthCheck returns ok for primary", hc.primary && hc.primary.ok === true);
|
|
1390
|
+
check("healthCheck returns breakerState", hc.primary.breakerState === "closed");
|
|
1391
|
+
|
|
1392
|
+
// Drain buffered audit emissions before reading audit_log.
|
|
1393
|
+
await b.audit.flush();
|
|
1394
|
+
// Audit recorded
|
|
1395
|
+
var qRows = await b.audit.query({ action: "system.externaldb.query" });
|
|
1396
|
+
check("audit recorded externaldb.query events", qRows.length >= 3);
|
|
1397
|
+
} finally {
|
|
1398
|
+
try { await b.externalDb.shutdown(); } catch (_e) {}
|
|
1399
|
+
await teardownTestDb(tmpDir);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
async function testExternalDbPool() {
|
|
1404
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-extdbpool-"));
|
|
1405
|
+
try {
|
|
1406
|
+
await setupTestDb(tmpDir);
|
|
1407
|
+
var driver = _makeFakeDriver();
|
|
1408
|
+
b.externalDb.init({
|
|
1409
|
+
backends: {
|
|
1410
|
+
"primary": {
|
|
1411
|
+
connect: driver.connect, query: driver.query, close: driver.close,
|
|
1412
|
+
pool: { min: 0, max: 3, idleTimeoutMs: 60000 },
|
|
1413
|
+
},
|
|
1414
|
+
},
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
// Sequential queries reuse the same connection
|
|
1418
|
+
await b.externalDb.query("SELECT 1");
|
|
1419
|
+
await b.externalDb.query("SELECT 1");
|
|
1420
|
+
await b.externalDb.query("SELECT 1");
|
|
1421
|
+
var s = driver.getStats();
|
|
1422
|
+
check("pool reuses idle connection", s.connectCount === 1);
|
|
1423
|
+
|
|
1424
|
+
// Concurrent queries open up to max
|
|
1425
|
+
var promises = [];
|
|
1426
|
+
for (var i = 0; i < 5; i++) promises.push(b.externalDb.query("SELECT 1"));
|
|
1427
|
+
await Promise.all(promises);
|
|
1428
|
+
var s2 = driver.getStats();
|
|
1429
|
+
check("concurrent queries open up to pool.max", s2.connectCount <= 3);
|
|
1430
|
+
|
|
1431
|
+
// listBackends shows pool stats
|
|
1432
|
+
var listed = b.externalDb.listBackends();
|
|
1433
|
+
check("listBackends includes pool stats", typeof listed[0].pool === "object");
|
|
1434
|
+
} finally {
|
|
1435
|
+
try { await b.externalDb.shutdown(); } catch (_e) {}
|
|
1436
|
+
await teardownTestDb(tmpDir);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
async function testExternalDbTransaction() {
|
|
1441
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-extdbtx-"));
|
|
1442
|
+
try {
|
|
1443
|
+
await setupTestDb(tmpDir);
|
|
1444
|
+
var driver = _makeFakeDriver();
|
|
1445
|
+
b.externalDb.init({
|
|
1446
|
+
backends: {
|
|
1447
|
+
"primary": {
|
|
1448
|
+
connect: driver.connect, query: driver.query, close: driver.close,
|
|
1449
|
+
},
|
|
1450
|
+
},
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
// Successful transaction commits
|
|
1454
|
+
var commitResult = await b.externalDb.transaction(async function (tx) {
|
|
1455
|
+
await tx.query("INSERT INTO kv (id, value) VALUES ($1, $2)", ["tx1", "a"]);
|
|
1456
|
+
await tx.query("INSERT INTO kv (id, value) VALUES ($1, $2)", ["tx2", "b"]);
|
|
1457
|
+
return "all-good";
|
|
1458
|
+
});
|
|
1459
|
+
check("transaction returns fn's return value", commitResult === "all-good");
|
|
1460
|
+
var got1 = await b.externalDb.query("SELECT id, value FROM kv WHERE id = $1", ["tx1"]);
|
|
1461
|
+
check("committed rows visible", got1.rows[0].value === "a");
|
|
1462
|
+
|
|
1463
|
+
// Failed transaction (handler throws) — rollback
|
|
1464
|
+
var caught = false;
|
|
1465
|
+
try {
|
|
1466
|
+
await b.externalDb.transaction(async function (tx) {
|
|
1467
|
+
await tx.query("INSERT INTO kv (id, value) VALUES ($1, $2)", ["tx3", "c"]);
|
|
1468
|
+
throw new Error("simulated");
|
|
1469
|
+
});
|
|
1470
|
+
} catch (e) { caught = e.message === "simulated"; }
|
|
1471
|
+
check("transaction error propagates", caught);
|
|
1472
|
+
|
|
1473
|
+
// External-db's audit emissions buffer in the handler; flush
|
|
1474
|
+
// explicitly to make them durable before querying.
|
|
1475
|
+
await b.audit.flush();
|
|
1476
|
+
var txRows = await b.audit.query({ action: "system.externaldb.transaction" });
|
|
1477
|
+
check("transaction events audit-logged", txRows.length >= 2);
|
|
1478
|
+
var failRows = txRows.filter(function (r) { return r.outcome === "failure"; });
|
|
1479
|
+
check("rollback event recorded as failure", failRows.length === 1);
|
|
1480
|
+
} finally {
|
|
1481
|
+
try { await b.externalDb.shutdown(); } catch (_e) {}
|
|
1482
|
+
await teardownTestDb(tmpDir);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
async function testExternalDbResidency() {
|
|
1487
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-extdbres-"));
|
|
1488
|
+
try {
|
|
1489
|
+
process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
|
|
1490
|
+
process.env.BLAMEJS_AUDIT_SIGNING_MODE = "plaintext";
|
|
1491
|
+
b.vault._resetForTest();
|
|
1492
|
+
b.db._resetForTest();
|
|
1493
|
+
await b.vault.init({ dataDir: tmpDir, mode: "plaintext" });
|
|
1494
|
+
await b.db.init({
|
|
1495
|
+
dataDir: tmpDir,
|
|
1496
|
+
atRest: "plain",
|
|
1497
|
+
auditSigning: { mode: "plaintext" },
|
|
1498
|
+
schema: [],
|
|
1499
|
+
dataResidency: { region: "EU", allowedStorageRegions: ["EU"] },
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
var driver = _makeFakeDriver();
|
|
1503
|
+
var residencyViolation = false;
|
|
1504
|
+
try {
|
|
1505
|
+
b.externalDb.init({
|
|
1506
|
+
backends: {
|
|
1507
|
+
"us-bad": {
|
|
1508
|
+
connect: driver.connect, query: driver.query, close: driver.close,
|
|
1509
|
+
classifications: ["personal"],
|
|
1510
|
+
residencyTag: "US", // ← violation
|
|
1511
|
+
},
|
|
1512
|
+
},
|
|
1513
|
+
});
|
|
1514
|
+
} catch (e) { residencyViolation = e.code === "RESIDENCY_VIOLATION"; }
|
|
1515
|
+
check("external DB residency violation refused", residencyViolation);
|
|
1516
|
+
|
|
1517
|
+
// EU-tagged backend OK
|
|
1518
|
+
b.externalDb._resetForTest();
|
|
1519
|
+
b.externalDb.init({
|
|
1520
|
+
backends: {
|
|
1521
|
+
"eu-ok": {
|
|
1522
|
+
connect: driver.connect, query: driver.query, close: driver.close,
|
|
1523
|
+
classifications: ["personal"],
|
|
1524
|
+
residencyTag: "EU",
|
|
1525
|
+
},
|
|
1526
|
+
},
|
|
1527
|
+
});
|
|
1528
|
+
var listed = b.externalDb.listBackends();
|
|
1529
|
+
check("EU backend accepted", listed.length === 1 && listed[0].residencyTag === "EU");
|
|
1530
|
+
} finally {
|
|
1531
|
+
try { await b.externalDb.shutdown(); } catch (_e) {}
|
|
1532
|
+
await teardownTestDb(tmpDir);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
async function testExternalDbClassification() {
|
|
1537
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-extdbcls-"));
|
|
1538
|
+
try {
|
|
1539
|
+
await setupTestDb(tmpDir);
|
|
1540
|
+
var personalDriver = _makeFakeDriver();
|
|
1541
|
+
var operationalDriver = _makeFakeDriver();
|
|
1542
|
+
b.externalDb.init({
|
|
1543
|
+
backends: {
|
|
1544
|
+
"personal-db": {
|
|
1545
|
+
connect: personalDriver.connect, query: personalDriver.query, close: personalDriver.close,
|
|
1546
|
+
classifications: ["personal"],
|
|
1547
|
+
},
|
|
1548
|
+
"ops-db": {
|
|
1549
|
+
connect: operationalDriver.connect, query: operationalDriver.query, close: operationalDriver.close,
|
|
1550
|
+
classifications: ["operational"],
|
|
1551
|
+
},
|
|
1552
|
+
},
|
|
1553
|
+
});
|
|
1554
|
+
|
|
1555
|
+
await b.externalDb.query("INSERT INTO kv (id, value) VALUES ($1, $2)", ["x", "y"], { classification: "personal" });
|
|
1556
|
+
await b.externalDb.query("INSERT INTO kv (id, value) VALUES ($1, $2)", ["a", "b"], { classification: "operational" });
|
|
1557
|
+
|
|
1558
|
+
check("personal query routed to personal-db", personalDriver.getStats().queryCount === 1);
|
|
1559
|
+
check("operational query routed to ops-db", operationalDriver.getStats().queryCount === 1);
|
|
1560
|
+
|
|
1561
|
+
// Wrong-classification rejection
|
|
1562
|
+
var rejected = false;
|
|
1563
|
+
try {
|
|
1564
|
+
await b.externalDb.query("SELECT 1", [], { backend: "ops-db", classification: "personal" });
|
|
1565
|
+
} catch (e) { rejected = e.code === "CLASSIFICATION_MISMATCH"; }
|
|
1566
|
+
check("backend that doesn't serve classification rejected", rejected);
|
|
1567
|
+
|
|
1568
|
+
// No backend serves a missing classification
|
|
1569
|
+
var noBackendRejected = false;
|
|
1570
|
+
try { await b.externalDb.query("SELECT 1", [], { classification: "nonexistent" }); }
|
|
1571
|
+
catch (e) { noBackendRejected = e.code === "NO_BACKEND_FOR_CLASSIFICATION"; }
|
|
1572
|
+
check("missing classification rejected", noBackendRejected);
|
|
1573
|
+
} finally {
|
|
1574
|
+
try { await b.externalDb.shutdown(); } catch (_e) {}
|
|
1575
|
+
await teardownTestDb(tmpDir);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
async function testMiddlewareRequestId() {
|
|
1580
|
+
await setupTestDbForMW();
|
|
1581
|
+
try {
|
|
1582
|
+
var mw = b.middleware.requestId();
|
|
1583
|
+
var nextCalled = false;
|
|
1584
|
+
|
|
1585
|
+
// Generates fresh ID
|
|
1586
|
+
var req1 = _mockReq();
|
|
1587
|
+
var res1 = _mockRes();
|
|
1588
|
+
mw(req1, res1, function () { nextCalled = true; });
|
|
1589
|
+
check("requestId calls next()", nextCalled);
|
|
1590
|
+
check("requestId sets req.requestId (32 hex chars)", typeof req1.requestId === "string" && req1.requestId.length === 32);
|
|
1591
|
+
check("requestId sets X-Request-Id response header", res1._captured().headers["x-request-id"] === req1.requestId);
|
|
1592
|
+
|
|
1593
|
+
// Propagates upstream value when format matches
|
|
1594
|
+
var req2 = _mockReq({ headers: { "x-request-id": "trace-abc-123_xyz" } });
|
|
1595
|
+
var res2 = _mockRes();
|
|
1596
|
+
mw(req2, res2, function () {});
|
|
1597
|
+
check("requestId propagates valid upstream id", req2.requestId === "trace-abc-123_xyz");
|
|
1598
|
+
|
|
1599
|
+
// Rejects malformed and generates fresh
|
|
1600
|
+
var req3 = _mockReq({ headers: { "x-request-id": "bad id with spaces!@#" } });
|
|
1601
|
+
var res3 = _mockRes();
|
|
1602
|
+
mw(req3, res3, function () {});
|
|
1603
|
+
check("requestId rejects malformed upstream id", req3.requestId !== "bad id with spaces!@#");
|
|
1604
|
+
} finally { teardownMW(); }
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
async function testMiddlewareSecurityHeaders() {
|
|
1608
|
+
await setupTestDbForMW();
|
|
1609
|
+
try {
|
|
1610
|
+
var mw = b.middleware.securityHeaders();
|
|
1611
|
+
// Mark the request socket as TLS so the v0.5.3 HSTS-only-on-HTTPS
|
|
1612
|
+
// gate engages — operators on plain HTTP won't get HSTS, which
|
|
1613
|
+
// matches RFC 6797 §7.2.
|
|
1614
|
+
var req = _mockReq();
|
|
1615
|
+
req.socket = { encrypted: true };
|
|
1616
|
+
var res = _mockRes();
|
|
1617
|
+
mw(req, res, function () {});
|
|
1618
|
+
var h = res._captured().headers;
|
|
1619
|
+
check("security: HSTS set", /max-age=63072000.+includeSubDomains/.test(h["strict-transport-security"]));
|
|
1620
|
+
check("security: X-Content-Type-Options nosniff", h["x-content-type-options"] === "nosniff");
|
|
1621
|
+
check("security: X-Frame-Options DENY", h["x-frame-options"] === "DENY");
|
|
1622
|
+
check("security: Referrer-Policy no-referrer", h["referrer-policy"] === "no-referrer");
|
|
1623
|
+
check("security: Permissions-Policy disables camera", /camera=\(\)/.test(h["permissions-policy"]));
|
|
1624
|
+
check("security: COOP same-origin", h["cross-origin-opener-policy"] === "same-origin");
|
|
1625
|
+
check("security: CORP same-origin", h["cross-origin-resource-policy"] === "same-origin");
|
|
1626
|
+
check("security: Origin-Agent-Cluster ?1", h["origin-agent-cluster"] === "?1");
|
|
1627
|
+
check("security: X-DNS-Prefetch-Control off", h["x-dns-prefetch-control"] === "off");
|
|
1628
|
+
check("security: CSP includes default-src 'self'", /default-src 'self'/.test(h["content-security-policy"]));
|
|
1629
|
+
check("security: CSP no longer ships 'unsafe-inline'",
|
|
1630
|
+
h["content-security-policy"].indexOf("'unsafe-inline'") === -1);
|
|
1631
|
+
check("security: CSP keeps style-src 'self'", /style-src 'self'(?!\s+'unsafe-inline')/.test(h["content-security-policy"]));
|
|
1632
|
+
|
|
1633
|
+
// Override + disable
|
|
1634
|
+
var mw2 = b.middleware.securityHeaders({
|
|
1635
|
+
frameOptions: "SAMEORIGIN",
|
|
1636
|
+
originAgentCluster: false,
|
|
1637
|
+
dnsPrefetchControl: "on",
|
|
1638
|
+
csp: false,
|
|
1639
|
+
});
|
|
1640
|
+
var req2 = _mockReq();
|
|
1641
|
+
var res2 = _mockRes();
|
|
1642
|
+
mw2(req2, res2, function () {});
|
|
1643
|
+
var h2 = res2._captured().headers;
|
|
1644
|
+
check("security: frameOptions override applied", h2["x-frame-options"] === "SAMEORIGIN");
|
|
1645
|
+
check("security: csp disabled when false", h2["content-security-policy"] === undefined);
|
|
1646
|
+
check("security: Origin-Agent-Cluster disabled when false",
|
|
1647
|
+
h2["origin-agent-cluster"] === undefined);
|
|
1648
|
+
check("security: DNS-Prefetch-Control on override", h2["x-dns-prefetch-control"] === "on");
|
|
1649
|
+
|
|
1650
|
+
var threwUnknownOpt = false;
|
|
1651
|
+
try { b.middleware.securityHeaders({ NOT_A_REAL_OPT: true }); }
|
|
1652
|
+
catch (_e) { threwUnknownOpt = true; }
|
|
1653
|
+
check("security: rejects unknown opt", threwUnknownOpt);
|
|
1654
|
+
} finally { teardownMW(); }
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
async function testMiddlewareErrorHandler() {
|
|
1658
|
+
await setupTestDbForMW();
|
|
1659
|
+
try {
|
|
1660
|
+
var mw = b.middleware.errorHandler({ exposeStackInDev: false });
|
|
1661
|
+
|
|
1662
|
+
// Simple error → 500
|
|
1663
|
+
var req = _mockReq({ url: "/x" });
|
|
1664
|
+
var res = _mockRes();
|
|
1665
|
+
mw(new Error("boom"), req, res, function () {});
|
|
1666
|
+
var captured = res._captured();
|
|
1667
|
+
check("errorHandler: default → 500", captured.status === 500);
|
|
1668
|
+
var body = JSON.parse(captured.body);
|
|
1669
|
+
check("errorHandler: generic message on 500", body.error.message === "Internal Server Error");
|
|
1670
|
+
check("errorHandler: error code present", !!body.error.code);
|
|
1671
|
+
|
|
1672
|
+
// statusCode-bearing error → that status
|
|
1673
|
+
var customErr = new Error("not found");
|
|
1674
|
+
customErr.statusCode = 404;
|
|
1675
|
+
customErr.code = "not_found";
|
|
1676
|
+
var req2 = _mockReq();
|
|
1677
|
+
var res2 = _mockRes();
|
|
1678
|
+
mw(customErr, req2, res2, function () {});
|
|
1679
|
+
var c2 = res2._captured();
|
|
1680
|
+
check("errorHandler: respects statusCode on error", c2.status === 404);
|
|
1681
|
+
var b2 = JSON.parse(c2.body);
|
|
1682
|
+
check("errorHandler: 4xx exposes message", b2.error.message === "not found");
|
|
1683
|
+
|
|
1684
|
+
// SafeJsonError → 400 + path
|
|
1685
|
+
var jse = new b.safeJson.SafeJsonError("validation failed", "json/validation", "$.email");
|
|
1686
|
+
var req3 = _mockReq();
|
|
1687
|
+
var res3 = _mockRes();
|
|
1688
|
+
mw(jse, req3, res3, function () {});
|
|
1689
|
+
var c3 = res3._captured();
|
|
1690
|
+
check("errorHandler: SafeJsonError → 400", c3.status === 400);
|
|
1691
|
+
var b3 = JSON.parse(c3.body);
|
|
1692
|
+
check("errorHandler: 400 body includes path", b3.error.path === "$.email");
|
|
1693
|
+
|
|
1694
|
+
// Drain buffered audit emissions before reading audit_log.
|
|
1695
|
+
await b.audit.flush();
|
|
1696
|
+
// Audit recorded
|
|
1697
|
+
var errRows = await b.audit.query({ action: "system.http.error" });
|
|
1698
|
+
check("errorHandler: audit-recorded errors", errRows.length === 3);
|
|
1699
|
+
} finally { teardownMW(); }
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
async function testMiddlewareBotGuard() {
|
|
1703
|
+
await setupTestDbForMW();
|
|
1704
|
+
try {
|
|
1705
|
+
var mw = b.middleware.botGuard();
|
|
1706
|
+
|
|
1707
|
+
// curl UA in 'block' mode → 403
|
|
1708
|
+
var req = _mockReq({ headers: { "user-agent": "curl/8.0.0", "accept-language": "en", "sec-fetch-mode": "navigate" } });
|
|
1709
|
+
var res = _mockRes();
|
|
1710
|
+
var nextCalled = false;
|
|
1711
|
+
mw(req, res, function () { nextCalled = true; });
|
|
1712
|
+
check("botGuard: curl UA blocked", res._captured().status === 403 && !nextCalled);
|
|
1713
|
+
|
|
1714
|
+
// Real-browser-shaped request → pass
|
|
1715
|
+
var req2 = _mockReq({ headers: { "user-agent": "Mozilla/5.0", "accept-language": "en-US", "sec-fetch-mode": "navigate" } });
|
|
1716
|
+
var res2 = _mockRes();
|
|
1717
|
+
var next2 = false;
|
|
1718
|
+
mw(req2, res2, function () { next2 = true; });
|
|
1719
|
+
check("botGuard: browser request passes", next2);
|
|
1720
|
+
|
|
1721
|
+
// Tag mode marks req but doesn't block
|
|
1722
|
+
var mwTag = b.middleware.botGuard({ mode: "tag" });
|
|
1723
|
+
var req3 = _mockReq({ headers: { "user-agent": "curl/8.0.0", "accept-language": "en" } });
|
|
1724
|
+
var res3 = _mockRes();
|
|
1725
|
+
var next3 = false;
|
|
1726
|
+
mwTag(req3, res3, function () { next3 = true; });
|
|
1727
|
+
check("botGuard tag mode: passes through", next3);
|
|
1728
|
+
check("botGuard tag mode: req.suspectedBot set", req3.suspectedBot === "blocked-agent");
|
|
1729
|
+
|
|
1730
|
+
// Skip path
|
|
1731
|
+
var mwSkip = b.middleware.botGuard({ skipPaths: ["/healthz"] });
|
|
1732
|
+
var req4 = _mockReq({ url: "/healthz", pathname: "/healthz", headers: { "user-agent": "curl/8.0.0" } });
|
|
1733
|
+
var res4 = _mockRes();
|
|
1734
|
+
var next4 = false;
|
|
1735
|
+
mwSkip(req4, res4, function () { next4 = true; });
|
|
1736
|
+
check("botGuard: skipPaths bypassed", next4);
|
|
1737
|
+
|
|
1738
|
+
// API path is exempt from missing-Accept-Language by default (onlyForHtml)
|
|
1739
|
+
var req5 = _mockReq({ url: "/api/x", pathname: "/api/x", headers: { "user-agent": "Mozilla" } });
|
|
1740
|
+
var res5 = _mockRes();
|
|
1741
|
+
var next5 = false;
|
|
1742
|
+
mw(req5, res5, function () { next5 = true; });
|
|
1743
|
+
check("botGuard: onlyForHtml exempts /api/*", next5);
|
|
1744
|
+
} finally { teardownMW(); }
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
async function testMiddlewareCors() {
|
|
1748
|
+
await setupTestDbForMW();
|
|
1749
|
+
try {
|
|
1750
|
+
var mw = b.middleware.cors({
|
|
1751
|
+
origins: ["https://app.example.com", /^https:\/\/.+\.staging\.example\.com$/],
|
|
1752
|
+
credentials: true,
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
// Allowed origin → CORS headers set
|
|
1756
|
+
var req = _mockReq({ headers: { origin: "https://app.example.com" } });
|
|
1757
|
+
var res = _mockRes();
|
|
1758
|
+
var nextCalled = false;
|
|
1759
|
+
mw(req, res, function () { nextCalled = true; });
|
|
1760
|
+
check("cors: allowed origin → next called", nextCalled);
|
|
1761
|
+
check("cors: ACAO set", res._captured().headers["access-control-allow-origin"] === "https://app.example.com");
|
|
1762
|
+
check("cors: ACAC set when credentials:true", res._captured().headers["access-control-allow-credentials"] === "true");
|
|
1763
|
+
|
|
1764
|
+
// Regex origin → match
|
|
1765
|
+
var req2 = _mockReq({ headers: { origin: "https://feature-1.staging.example.com" } });
|
|
1766
|
+
var res2 = _mockRes();
|
|
1767
|
+
mw(req2, res2, function () {});
|
|
1768
|
+
check("cors: regex origin matched", res2._captured().headers["access-control-allow-origin"] === "https://feature-1.staging.example.com");
|
|
1769
|
+
|
|
1770
|
+
// Disallowed origin → 403 (refuseUnknown default)
|
|
1771
|
+
var req3 = _mockReq({ headers: { origin: "https://evil.example.com" } });
|
|
1772
|
+
var res3 = _mockRes();
|
|
1773
|
+
var n3 = false;
|
|
1774
|
+
mw(req3, res3, function () { n3 = true; });
|
|
1775
|
+
check("cors: unknown origin blocked", res3._captured().status === 403 && !n3);
|
|
1776
|
+
|
|
1777
|
+
// No Origin header → pass through
|
|
1778
|
+
var req4 = _mockReq();
|
|
1779
|
+
var res4 = _mockRes();
|
|
1780
|
+
var n4 = false;
|
|
1781
|
+
mw(req4, res4, function () { n4 = true; });
|
|
1782
|
+
check("cors: no Origin header → passes through", n4 && !res4._captured().headers["access-control-allow-origin"]);
|
|
1783
|
+
|
|
1784
|
+
// Preflight (OPTIONS + Access-Control-Request-Method) → 204 with allow-headers
|
|
1785
|
+
var req5 = _mockReq({ method: "OPTIONS", headers: { origin: "https://app.example.com", "access-control-request-method": "PUT" } });
|
|
1786
|
+
var res5 = _mockRes();
|
|
1787
|
+
mw(req5, res5, function () {});
|
|
1788
|
+
check("cors preflight: 204", res5._captured().status === 204);
|
|
1789
|
+
check("cors preflight: ACAM set", /PUT/.test(res5._captured().headers["access-control-allow-methods"]));
|
|
1790
|
+
} finally { teardownMW(); }
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
async function testMiddlewareRateLimit() {
|
|
1794
|
+
await setupTestDbForMW();
|
|
1795
|
+
try {
|
|
1796
|
+
var mw = b.middleware.rateLimit({ burst: 3, refillPerSecond: 1 });
|
|
1797
|
+
|
|
1798
|
+
function fire() {
|
|
1799
|
+
var req = _mockReq();
|
|
1800
|
+
var res = _mockRes();
|
|
1801
|
+
var nextCalled = false;
|
|
1802
|
+
mw(req, res, function () { nextCalled = true; });
|
|
1803
|
+
return { passed: nextCalled, status: res._captured().status };
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// First 3 pass (burst=3)
|
|
1807
|
+
check("rateLimit: 1st request passes", fire().passed);
|
|
1808
|
+
check("rateLimit: 2nd request passes", fire().passed);
|
|
1809
|
+
check("rateLimit: 3rd request passes", fire().passed);
|
|
1810
|
+
var blocked = fire();
|
|
1811
|
+
check("rateLimit: 4th request blocked with 429", !blocked.passed && blocked.status === 429);
|
|
1812
|
+
|
|
1813
|
+
// Different key → independent bucket
|
|
1814
|
+
var mw2 = b.middleware.rateLimit({ burst: 2, refillPerSecond: 0.5, keyFn: function (req) { return req.headers["x-key"] || "default"; } });
|
|
1815
|
+
function fireKey(k) {
|
|
1816
|
+
var req = _mockReq({ headers: { "x-key": k } });
|
|
1817
|
+
var res = _mockRes();
|
|
1818
|
+
var ok = false;
|
|
1819
|
+
mw2(req, res, function () { ok = true; });
|
|
1820
|
+
return ok;
|
|
1821
|
+
}
|
|
1822
|
+
check("rateLimit: keyA 1st passes", fireKey("a"));
|
|
1823
|
+
check("rateLimit: keyA 2nd passes", fireKey("a"));
|
|
1824
|
+
check("rateLimit: keyA 3rd blocked", !fireKey("a"));
|
|
1825
|
+
check("rateLimit: keyB independent — 1st passes", fireKey("b"));
|
|
1826
|
+
|
|
1827
|
+
// Skip path
|
|
1828
|
+
var mwSkip = b.middleware.rateLimit({ burst: 1, refillPerSecond: 0.1, skipPaths: ["/healthz"] });
|
|
1829
|
+
function fireWithPath(p) {
|
|
1830
|
+
var req = _mockReq({ url: p, pathname: p });
|
|
1831
|
+
var res = _mockRes();
|
|
1832
|
+
var ok = false;
|
|
1833
|
+
mwSkip(req, res, function () { ok = true; });
|
|
1834
|
+
return ok;
|
|
1835
|
+
}
|
|
1836
|
+
check("rateLimit: 1st /healthz passes", fireWithPath("/healthz"));
|
|
1837
|
+
check("rateLimit: 2nd /healthz passes (skipped)", fireWithPath("/healthz"));
|
|
1838
|
+
check("rateLimit: 1st /api passes", fireWithPath("/api"));
|
|
1839
|
+
check("rateLimit: 2nd /api blocked", !fireWithPath("/api"));
|
|
1840
|
+
} finally { teardownMW(); }
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
async function testMiddlewareCsrfProtect() {
|
|
1844
|
+
// End-to-end via a real http server. Token is stored in a fake
|
|
1845
|
+
// session under req.expectedCsrfToken so the middleware's tokenLookup
|
|
1846
|
+
// can return it without dragging the real session module into this
|
|
1847
|
+
// test fixture (which is about CSRF gating, not session lifecycle).
|
|
1848
|
+
await setupTestDbForMW();
|
|
1849
|
+
try {
|
|
1850
|
+
var http = require("http");
|
|
1851
|
+
|
|
1852
|
+
var EXPECTED = b.forms.generateCsrfToken();
|
|
1853
|
+
|
|
1854
|
+
function _captureBody(req) {
|
|
1855
|
+
return new Promise(function (resolve) {
|
|
1856
|
+
var chunks = [];
|
|
1857
|
+
req.on("data", function (c) { chunks.push(c); });
|
|
1858
|
+
req.on("end", function () { resolve(Buffer.concat(chunks).toString("utf8")); });
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
var bodyParser = b.middleware.bodyParser();
|
|
1863
|
+
var protect = b.middleware.csrfProtect({
|
|
1864
|
+
tokenLookup: function (req) { return EXPECTED; },
|
|
1865
|
+
});
|
|
1866
|
+
var server = http.createServer(async function (req, res) {
|
|
1867
|
+
// bodyParser runs first so urlencoded form posts populate req.body
|
|
1868
|
+
// for csrf-protect to read. Header path still works without it.
|
|
1869
|
+
await new Promise(function (resolve) {
|
|
1870
|
+
bodyParser(req, res, function () {
|
|
1871
|
+
protect(req, res, function () {
|
|
1872
|
+
res.writeHead(200, { "Content-Type": "text/plain", "Content-Length": 2 });
|
|
1873
|
+
res.end("ok");
|
|
1874
|
+
resolve();
|
|
1875
|
+
});
|
|
1876
|
+
});
|
|
1877
|
+
});
|
|
1878
|
+
});
|
|
1879
|
+
var port = await listenOnRandomPort(server);
|
|
1880
|
+
try {
|
|
1881
|
+
// 1. Safe method (GET) → middleware passes through, even with no token
|
|
1882
|
+
var safe = await b.httpClient.request({
|
|
1883
|
+
url: "http://127.0.0.1:" + port + "/protected",
|
|
1884
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
1885
|
+
allowInternal: true,
|
|
1886
|
+
});
|
|
1887
|
+
check("csrfProtect: GET passes through", safe.statusCode === 200);
|
|
1888
|
+
|
|
1889
|
+
// 2. POST without token → 403
|
|
1890
|
+
var noTok = await b.httpClient.request({
|
|
1891
|
+
method: "POST",
|
|
1892
|
+
url: "http://127.0.0.1:" + port + "/protected",
|
|
1893
|
+
body: Buffer.from(""),
|
|
1894
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
1895
|
+
allowInternal: true,
|
|
1896
|
+
errorClass: b.frameworkError.ObjectStoreError,
|
|
1897
|
+
}).catch(function (e) { return e; });
|
|
1898
|
+
check("csrfProtect: POST without token → 403", noTok.statusCode === 403);
|
|
1899
|
+
|
|
1900
|
+
// 3. POST with token in X-CSRF-Token header → 200
|
|
1901
|
+
var hdrOk = await b.httpClient.request({
|
|
1902
|
+
method: "POST",
|
|
1903
|
+
url: "http://127.0.0.1:" + port + "/protected",
|
|
1904
|
+
headers: { "x-csrf-token": EXPECTED, "Content-Type": "application/json" },
|
|
1905
|
+
body: Buffer.from("{}"),
|
|
1906
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
1907
|
+
allowInternal: true,
|
|
1908
|
+
});
|
|
1909
|
+
check("csrfProtect: POST with header token → 200", hdrOk.statusCode === 200);
|
|
1910
|
+
|
|
1911
|
+
// 4. POST with token in urlencoded body → 200
|
|
1912
|
+
var bodyOk = await b.httpClient.request({
|
|
1913
|
+
method: "POST",
|
|
1914
|
+
url: "http://127.0.0.1:" + port + "/protected",
|
|
1915
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1916
|
+
body: Buffer.from("_csrf=" + encodeURIComponent(EXPECTED) + "&name=Alice"),
|
|
1917
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
1918
|
+
allowInternal: true,
|
|
1919
|
+
});
|
|
1920
|
+
check("csrfProtect: POST with urlencoded body token → 200", bodyOk.statusCode === 200);
|
|
1921
|
+
|
|
1922
|
+
// 5. POST with WRONG token → 403
|
|
1923
|
+
var wrong = await b.httpClient.request({
|
|
1924
|
+
method: "POST",
|
|
1925
|
+
url: "http://127.0.0.1:" + port + "/protected",
|
|
1926
|
+
headers: { "x-csrf-token": "wrong-token" },
|
|
1927
|
+
body: Buffer.from(""),
|
|
1928
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
1929
|
+
allowInternal: true,
|
|
1930
|
+
errorClass: b.frameworkError.ObjectStoreError,
|
|
1931
|
+
}).catch(function (e) { return e; });
|
|
1932
|
+
check("csrfProtect: POST with wrong token → 403", wrong.statusCode === 403);
|
|
1933
|
+
} finally { server.close(); }
|
|
1934
|
+
|
|
1935
|
+
// 6. Custom methods + custom headerName
|
|
1936
|
+
var protectCustom = b.middleware.csrfProtect({
|
|
1937
|
+
tokenLookup: function () { return EXPECTED; },
|
|
1938
|
+
methods: ["DELETE"],
|
|
1939
|
+
headerName: "X-My-CSRF",
|
|
1940
|
+
});
|
|
1941
|
+
var server2 = http.createServer(async function (req, res) {
|
|
1942
|
+
await new Promise(function (resolve) {
|
|
1943
|
+
protectCustom(req, res, function () {
|
|
1944
|
+
res.writeHead(200, { "Content-Type": "text/plain", "Content-Length": 2 });
|
|
1945
|
+
res.end("ok");
|
|
1946
|
+
resolve();
|
|
1947
|
+
});
|
|
1948
|
+
});
|
|
1949
|
+
});
|
|
1950
|
+
var port2 = await listenOnRandomPort(server2);
|
|
1951
|
+
try {
|
|
1952
|
+
// POST is now NOT in the protected methods → passes through
|
|
1953
|
+
var postPass = await b.httpClient.request({
|
|
1954
|
+
method: "POST",
|
|
1955
|
+
url: "http://127.0.0.1:" + port2 + "/x",
|
|
1956
|
+
body: Buffer.from(""),
|
|
1957
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
1958
|
+
allowInternal: true,
|
|
1959
|
+
});
|
|
1960
|
+
check("csrfProtect: custom methods exclude POST", postPass.statusCode === 200);
|
|
1961
|
+
|
|
1962
|
+
// DELETE without token → 403
|
|
1963
|
+
var del403 = await b.httpClient.request({
|
|
1964
|
+
method: "DELETE",
|
|
1965
|
+
url: "http://127.0.0.1:" + port2 + "/x",
|
|
1966
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
1967
|
+
allowInternal: true,
|
|
1968
|
+
errorClass: b.frameworkError.ObjectStoreError,
|
|
1969
|
+
}).catch(function (e) { return e; });
|
|
1970
|
+
check("csrfProtect: DELETE in custom methods gated", del403.statusCode === 403);
|
|
1971
|
+
|
|
1972
|
+
// DELETE with token in custom header → 200
|
|
1973
|
+
var del200 = await b.httpClient.request({
|
|
1974
|
+
method: "DELETE",
|
|
1975
|
+
url: "http://127.0.0.1:" + port2 + "/x",
|
|
1976
|
+
headers: { "x-my-csrf": EXPECTED },
|
|
1977
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
1978
|
+
allowInternal: true,
|
|
1979
|
+
});
|
|
1980
|
+
check("csrfProtect: custom headerName honored", del200.statusCode === 200);
|
|
1981
|
+
} finally { server2.close(); }
|
|
1982
|
+
|
|
1983
|
+
// 7. tokenLookup returns null → 403 (no expected token to compare)
|
|
1984
|
+
var protectNullLookup = b.middleware.csrfProtect({
|
|
1985
|
+
tokenLookup: function () { return null; },
|
|
1986
|
+
});
|
|
1987
|
+
var server3 = http.createServer(async function (req, res) {
|
|
1988
|
+
await new Promise(function (resolve) {
|
|
1989
|
+
protectNullLookup(req, res, function () {
|
|
1990
|
+
res.writeHead(200); res.end("should not reach"); resolve();
|
|
1991
|
+
});
|
|
1992
|
+
});
|
|
1993
|
+
});
|
|
1994
|
+
var port3 = await listenOnRandomPort(server3);
|
|
1995
|
+
try {
|
|
1996
|
+
var nullLookup = await b.httpClient.request({
|
|
1997
|
+
method: "POST",
|
|
1998
|
+
url: "http://127.0.0.1:" + port3 + "/x",
|
|
1999
|
+
headers: { "x-csrf-token": EXPECTED },
|
|
2000
|
+
body: Buffer.from(""),
|
|
2001
|
+
allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
|
|
2002
|
+
allowInternal: true,
|
|
2003
|
+
errorClass: b.frameworkError.ObjectStoreError,
|
|
2004
|
+
}).catch(function (e) { return e; });
|
|
2005
|
+
check("csrfProtect: tokenLookup null → 403", nullLookup.statusCode === 403);
|
|
2006
|
+
} finally { server3.close(); }
|
|
2007
|
+
|
|
2008
|
+
// 8. Validation: tokenLookup required
|
|
2009
|
+
var threw = null;
|
|
2010
|
+
try { b.middleware.csrfProtect({}); }
|
|
2011
|
+
catch (e) { threw = e; }
|
|
2012
|
+
check("csrfProtect: tokenLookup is required", threw && /tokenLookup is required/.test(threw.message));
|
|
2013
|
+
} finally { teardownMW(); }
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
async function testMiddlewareAttachUser() {
|
|
2017
|
+
// attachUser populates req.user via session.verify + operator-supplied
|
|
2018
|
+
// userLoader. Validates token-source dispatch (cookie + Bearer header),
|
|
2019
|
+
// graceful failure modes (no token / invalid token / userLoader nulls /
|
|
2020
|
+
// userLoader throws), and that the middleware never throws or
|
|
2021
|
+
// short-circuits — gating is downstream's job.
|
|
2022
|
+
await setupTestDbForMW();
|
|
2023
|
+
try {
|
|
2024
|
+
// Create a real session; we'll exercise verify through the middleware.
|
|
2025
|
+
var s = await b.session.create({ userId: "u-1", data: { role: "member" } });
|
|
2026
|
+
var goodToken = s.token;
|
|
2027
|
+
|
|
2028
|
+
var loaderCalls = [];
|
|
2029
|
+
var userLoader = async function (verified) {
|
|
2030
|
+
loaderCalls.push(verified.userId);
|
|
2031
|
+
if (verified.userId === "u-1") return { _id: "u-1", email: "alice@example.com" };
|
|
2032
|
+
if (verified.userId === "u-suspended") return null; // user record exists but loader rejects
|
|
2033
|
+
if (verified.userId === "u-throws") throw new Error("DB blew up");
|
|
2034
|
+
return null;
|
|
2035
|
+
};
|
|
2036
|
+
|
|
2037
|
+
// userLoader is required
|
|
2038
|
+
var threw = null;
|
|
2039
|
+
try { b.middleware.attachUser({}); }
|
|
2040
|
+
catch (e) { threw = e; }
|
|
2041
|
+
check("attachUser: throws when userLoader missing",
|
|
2042
|
+
threw && /userLoader is required/.test(threw.message));
|
|
2043
|
+
|
|
2044
|
+
var mw = b.middleware.attachUser({ userLoader: userLoader });
|
|
2045
|
+
|
|
2046
|
+
// 1. No token in either source → req.user = null, next() called
|
|
2047
|
+
var req1 = _mockReq();
|
|
2048
|
+
var res1 = _mockRes();
|
|
2049
|
+
var n1 = false;
|
|
2050
|
+
await mw(req1, res1, function () { n1 = true; });
|
|
2051
|
+
check("attachUser: no token → next() called", n1 === true);
|
|
2052
|
+
check("attachUser: no token → req.user is null", req1.user === null);
|
|
2053
|
+
check("attachUser: no token → res not written", res1._captured().ended === false);
|
|
2054
|
+
|
|
2055
|
+
// 2. Valid cookie token → req.user populated, req.session set
|
|
2056
|
+
var req2 = _mockReq({ headers: { cookie: "blamejs_session=" + goodToken } });
|
|
2057
|
+
var res2 = _mockRes();
|
|
2058
|
+
var n2 = false;
|
|
2059
|
+
await mw(req2, res2, function () { n2 = true; });
|
|
2060
|
+
check("attachUser: valid cookie → next() called", n2 === true);
|
|
2061
|
+
check("attachUser: valid cookie → req.user set", req2.user && req2.user._id === "u-1");
|
|
2062
|
+
check("attachUser: valid cookie → req.session set", req2.session && req2.session.userId === "u-1");
|
|
2063
|
+
|
|
2064
|
+
// 3. Valid Bearer header → req.user populated
|
|
2065
|
+
var req3 = _mockReq({ headers: { authorization: "Bearer " + goodToken } });
|
|
2066
|
+
var res3 = _mockRes();
|
|
2067
|
+
await mw(req3, res3, function () {});
|
|
2068
|
+
check("attachUser: valid Bearer → req.user set", req3.user && req3.user._id === "u-1");
|
|
2069
|
+
|
|
2070
|
+
// 4. Cookie wins over Bearer when both present (cookie tried first)
|
|
2071
|
+
var anotherSession = await b.session.create({ userId: "u-1" });
|
|
2072
|
+
var req4 = _mockReq({ headers: {
|
|
2073
|
+
cookie: "blamejs_session=" + goodToken + "; foo=bar",
|
|
2074
|
+
authorization: "Bearer " + anotherSession.token,
|
|
2075
|
+
} });
|
|
2076
|
+
var res4 = _mockRes();
|
|
2077
|
+
await mw(req4, res4, function () {});
|
|
2078
|
+
check("attachUser: cookie precedes Bearer when both present",
|
|
2079
|
+
req4.user && req4.user._id === "u-1");
|
|
2080
|
+
|
|
2081
|
+
// 5. Invalid token → req.user = null
|
|
2082
|
+
var req5 = _mockReq({ headers: { authorization: "Bearer not-a-real-token" } });
|
|
2083
|
+
var res5 = _mockRes();
|
|
2084
|
+
var n5 = false;
|
|
2085
|
+
await mw(req5, res5, function () { n5 = true; });
|
|
2086
|
+
check("attachUser: invalid token → next() called", n5 === true);
|
|
2087
|
+
check("attachUser: invalid token → req.user is null", req5.user === null);
|
|
2088
|
+
|
|
2089
|
+
// 6. Valid session but userLoader returns null (deleted/suspended)
|
|
2090
|
+
var sSuspended = await b.session.create({ userId: "u-suspended" });
|
|
2091
|
+
var req6 = _mockReq({ headers: { authorization: "Bearer " + sSuspended.token } });
|
|
2092
|
+
var res6 = _mockRes();
|
|
2093
|
+
await mw(req6, res6, function () {});
|
|
2094
|
+
check("attachUser: userLoader returns null → req.user is null",
|
|
2095
|
+
req6.user === null);
|
|
2096
|
+
|
|
2097
|
+
// 7. userLoader throws → req.user = null, no propagation
|
|
2098
|
+
var sThrows = await b.session.create({ userId: "u-throws" });
|
|
2099
|
+
var req7 = _mockReq({ headers: { authorization: "Bearer " + sThrows.token } });
|
|
2100
|
+
var res7 = _mockRes();
|
|
2101
|
+
var n7 = false;
|
|
2102
|
+
await mw(req7, res7, function () { n7 = true; });
|
|
2103
|
+
check("attachUser: userLoader throw → next() still called", n7 === true);
|
|
2104
|
+
check("attachUser: userLoader throw → req.user is null", req7.user === null);
|
|
2105
|
+
|
|
2106
|
+
// 8. tokenFrom 'cookie' ignores Bearer
|
|
2107
|
+
var mwCookieOnly = b.middleware.attachUser({ userLoader: userLoader, tokenFrom: "cookie" });
|
|
2108
|
+
var req8 = _mockReq({ headers: { authorization: "Bearer " + goodToken } });
|
|
2109
|
+
await mwCookieOnly(req8, _mockRes(), function () {});
|
|
2110
|
+
check("attachUser: tokenFrom='cookie' ignores Bearer header",
|
|
2111
|
+
req8.user === null);
|
|
2112
|
+
|
|
2113
|
+
// 9. tokenFrom 'header' ignores cookie
|
|
2114
|
+
var mwHeaderOnly = b.middleware.attachUser({ userLoader: userLoader, tokenFrom: "header" });
|
|
2115
|
+
var req9 = _mockReq({ headers: { cookie: "blamejs_session=" + goodToken } });
|
|
2116
|
+
await mwHeaderOnly(req9, _mockRes(), function () {});
|
|
2117
|
+
check("attachUser: tokenFrom='header' ignores cookie",
|
|
2118
|
+
req9.user === null);
|
|
2119
|
+
} finally { teardownMW(); }
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
async function testMiddlewareRequireAuth() {
|
|
2123
|
+
// requireAuth gates routes. With req.user populated, next() runs.
|
|
2124
|
+
// Without it: 401 JSON, 401 text, or 302 redirect depending on
|
|
2125
|
+
// request shape + opts.
|
|
2126
|
+
await setupTestDbForMW();
|
|
2127
|
+
try {
|
|
2128
|
+
var mw = b.middleware.requireAuth();
|
|
2129
|
+
|
|
2130
|
+
// 1. Authenticated request → next() called, no response written
|
|
2131
|
+
var req1 = _mockReq();
|
|
2132
|
+
req1.user = { _id: "u-1" };
|
|
2133
|
+
var res1 = _mockRes();
|
|
2134
|
+
var n1 = false;
|
|
2135
|
+
mw(req1, res1, function () { n1 = true; });
|
|
2136
|
+
check("requireAuth: authenticated → next() called", n1 === true);
|
|
2137
|
+
check("requireAuth: authenticated → res not written", res1._captured().ended === false);
|
|
2138
|
+
|
|
2139
|
+
// 2. Unauthenticated JSON-preferring request → 401 JSON
|
|
2140
|
+
var req2 = _mockReq({ headers: { accept: "application/json" } });
|
|
2141
|
+
var res2 = _mockRes();
|
|
2142
|
+
var n2 = false;
|
|
2143
|
+
mw(req2, res2, function () { n2 = true; });
|
|
2144
|
+
var cap2 = res2._captured();
|
|
2145
|
+
check("requireAuth: unauth + JSON → next() NOT called", n2 === false);
|
|
2146
|
+
check("requireAuth: unauth + JSON → 401 status", cap2.status === 401);
|
|
2147
|
+
check("requireAuth: unauth + JSON → Content-Type JSON",
|
|
2148
|
+
cap2.headers["content-type"].indexOf("application/json") === 0);
|
|
2149
|
+
var body2 = JSON.parse(cap2.body);
|
|
2150
|
+
check("requireAuth: unauth + JSON → error body", body2.error === "Authentication required.");
|
|
2151
|
+
|
|
2152
|
+
// 3. Unauthenticated XHR (X-Requested-With) → 401 JSON
|
|
2153
|
+
var req3 = _mockReq({ headers: { "x-requested-with": "XMLHttpRequest" } });
|
|
2154
|
+
var res3 = _mockRes();
|
|
2155
|
+
mw(req3, res3, function () {});
|
|
2156
|
+
check("requireAuth: unauth + XHR → 401 status", res3._captured().status === 401);
|
|
2157
|
+
|
|
2158
|
+
// 4. Unauthenticated browser-y request → 401 text/plain
|
|
2159
|
+
var req4 = _mockReq({ headers: { accept: "text/html" } });
|
|
2160
|
+
var res4 = _mockRes();
|
|
2161
|
+
mw(req4, res4, function () {});
|
|
2162
|
+
var cap4 = res4._captured();
|
|
2163
|
+
check("requireAuth: unauth browser → 401 status", cap4.status === 401);
|
|
2164
|
+
check("requireAuth: unauth browser → text/plain",
|
|
2165
|
+
cap4.headers["content-type"].indexOf("text/plain") === 0);
|
|
2166
|
+
|
|
2167
|
+
// 4b. Content-Type: application/json on the REQUEST body is NOT
|
|
2168
|
+
// a signal — it describes what the client SENT, not what they
|
|
2169
|
+
// want back. Server-to-server POST with no Accept header lands
|
|
2170
|
+
// on the default text/plain branch (or the redirect branch when
|
|
2171
|
+
// opts.redirectTo is set).
|
|
2172
|
+
var req4c = _mockReq({ headers: { "content-type": "application/json" } });
|
|
2173
|
+
var res4c = _mockRes();
|
|
2174
|
+
mw(req4c, res4c, function () {});
|
|
2175
|
+
var cap4c = res4c._captured();
|
|
2176
|
+
check("requireAuth: req Content-Type JSON alone → text/plain (not JSON)",
|
|
2177
|
+
cap4c.status === 401 &&
|
|
2178
|
+
cap4c.headers["content-type"].indexOf("text/plain") === 0);
|
|
2179
|
+
|
|
2180
|
+
// 5. Unauthenticated browser-y request WITH redirectTo → 302
|
|
2181
|
+
var mwRedirect = b.middleware.requireAuth({ redirectTo: "/auth/login" });
|
|
2182
|
+
var req5 = _mockReq({ headers: { accept: "text/html" } });
|
|
2183
|
+
var res5 = _mockRes();
|
|
2184
|
+
mwRedirect(req5, res5, function () {});
|
|
2185
|
+
var cap5 = res5._captured();
|
|
2186
|
+
check("requireAuth: unauth + redirectTo → 302 status", cap5.status === 302);
|
|
2187
|
+
check("requireAuth: unauth + redirectTo → Location set",
|
|
2188
|
+
cap5.headers.location === "/auth/login");
|
|
2189
|
+
|
|
2190
|
+
// 6. JSON-preferring still gets 401 JSON even with redirectTo set
|
|
2191
|
+
var req6 = _mockReq({ headers: { accept: "application/json" } });
|
|
2192
|
+
var res6 = _mockRes();
|
|
2193
|
+
mwRedirect(req6, res6, function () {});
|
|
2194
|
+
check("requireAuth: JSON-prefer + redirectTo → 401 (not redirect)",
|
|
2195
|
+
res6._captured().status === 401);
|
|
2196
|
+
|
|
2197
|
+
// 7. Custom errorMessage propagates
|
|
2198
|
+
var mwCustom = b.middleware.requireAuth({ errorMessage: "Sign in to continue." });
|
|
2199
|
+
var req7 = _mockReq({ headers: { accept: "application/json" } });
|
|
2200
|
+
var res7 = _mockRes();
|
|
2201
|
+
mwCustom(req7, res7, function () {});
|
|
2202
|
+
check("requireAuth: custom errorMessage propagates",
|
|
2203
|
+
JSON.parse(res7._captured().body).error === "Sign in to continue.");
|
|
2204
|
+
|
|
2205
|
+
// 8. Custom prefersJson override
|
|
2206
|
+
var mwForce = b.middleware.requireAuth({ prefersJson: function () { return false; } });
|
|
2207
|
+
var req8 = _mockReq({ headers: { accept: "application/json" } }); // would normally be JSON
|
|
2208
|
+
var res8 = _mockRes();
|
|
2209
|
+
mwForce(req8, res8, function () {});
|
|
2210
|
+
check("requireAuth: prefersJson override forces text/plain",
|
|
2211
|
+
res8._captured().headers["content-type"].indexOf("text/plain") === 0);
|
|
2212
|
+
} finally { teardownMW(); }
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
function testEnvLoadDiffAndAudit() {
|
|
2216
|
+
// Use real file I/O via atomicFile — exercises load() end-to-end.
|
|
2217
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-env-"));
|
|
2218
|
+
try {
|
|
2219
|
+
var envPath = path.join(tmpDir, ".env");
|
|
2220
|
+
var snapPath = path.join(tmpDir, "env.snapshot.json");
|
|
2221
|
+
|
|
2222
|
+
fs.writeFileSync(envPath, "DATABASE_URL=postgres://A\nFEATURE_FOO=true\n");
|
|
2223
|
+
var res1 = b.parsers.env.load(envPath, {
|
|
2224
|
+
snapshotPath: snapPath,
|
|
2225
|
+
audit: false, // no framework db wired in this test fixture
|
|
2226
|
+
});
|
|
2227
|
+
check("env.load returns values", res1.values.DATABASE_URL === "postgres://A");
|
|
2228
|
+
check("env.load first call: 2 added", res1.diff.added.length === 2);
|
|
2229
|
+
check("env.load first call: nothing removed", res1.diff.removed.length === 0);
|
|
2230
|
+
check("env.load first call: nothing changed", res1.diff.changed.length === 0);
|
|
2231
|
+
|
|
2232
|
+
// Now change one and add another
|
|
2233
|
+
fs.writeFileSync(envPath, "DATABASE_URL=postgres://B\nFEATURE_FOO=true\nNEW_KEY=hello\n");
|
|
2234
|
+
var res2 = b.parsers.env.load(envPath, { snapshotPath: snapPath, audit: false });
|
|
2235
|
+
check("env.load second call: 1 added", res2.diff.added.length === 1 && res2.diff.added[0] === "NEW_KEY");
|
|
2236
|
+
check("env.load second call: nothing removed", res2.diff.removed.length === 0);
|
|
2237
|
+
check("env.load second call: 1 changed", res2.diff.changed.length === 1);
|
|
2238
|
+
check("env.load second call: changed key", res2.diff.changed[0].key === "DATABASE_URL");
|
|
2239
|
+
|
|
2240
|
+
// Now remove one
|
|
2241
|
+
fs.writeFileSync(envPath, "DATABASE_URL=postgres://B\nNEW_KEY=hello\n");
|
|
2242
|
+
var res3 = b.parsers.env.load(envPath, { snapshotPath: snapPath, audit: false });
|
|
2243
|
+
check("env.load third call: 1 removed", res3.diff.removed.length === 1 && res3.diff.removed[0] === "FEATURE_FOO");
|
|
2244
|
+
} finally {
|
|
2245
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
function testEnvLoadSchemaAndTypos() {
|
|
2250
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-env-"));
|
|
2251
|
+
try {
|
|
2252
|
+
var envPath = path.join(tmpDir, ".env");
|
|
2253
|
+
fs.writeFileSync(envPath,
|
|
2254
|
+
"DATABSE_URL=oops\n" + // typo of DATABASE_URL
|
|
2255
|
+
"feature_flag=true\n"); // case mismatch — wait, this is rejected by keyShape
|
|
2256
|
+
// Actually case-mismatch must use keys that pass shape. Use uppercase
|
|
2257
|
+
// mismatch instead — rewrite.
|
|
2258
|
+
fs.writeFileSync(envPath,
|
|
2259
|
+
"DATABSE_URL=oops\n" + // typo (missing 'A')
|
|
2260
|
+
"FEATURE_FLAG=true\n" + // exact match for registered
|
|
2261
|
+
"TOTALLY_UNKNOWN=other\n");
|
|
2262
|
+
var expected = {
|
|
2263
|
+
DATABASE_URL: { type: "string", sensitivity: "breaking" },
|
|
2264
|
+
FEATURE_FLAG: { type: "boolean", sensitivity: "runtime" },
|
|
2265
|
+
};
|
|
2266
|
+
var res = b.parsers.env.load(envPath, {
|
|
2267
|
+
expected: expected,
|
|
2268
|
+
audit: false,
|
|
2269
|
+
});
|
|
2270
|
+
check("env: schema coerces type when registered", res.values.FEATURE_FLAG === true);
|
|
2271
|
+
|
|
2272
|
+
// Find the typo entry in suspicious
|
|
2273
|
+
var typo = res.diff.suspicious.find(function (s) { return s.key === "DATABSE_URL"; });
|
|
2274
|
+
check("env: typo flagged as suspicious", typo && typo.suggestion === "DATABASE_URL");
|
|
2275
|
+
check("env: typo reason is single-char-typo", typo && typo.reason === "single-char-typo");
|
|
2276
|
+
|
|
2277
|
+
var unknown = res.diff.suspicious.find(function (s) { return s.key === "TOTALLY_UNKNOWN"; });
|
|
2278
|
+
check("env: unrelated unknown flagged", unknown && unknown.reason === "unknown");
|
|
2279
|
+
|
|
2280
|
+
// rejectUnknown mode refuses
|
|
2281
|
+
var threwRejectUnknown = false;
|
|
2282
|
+
try { b.parsers.env.load(envPath, { expected: expected, audit: false, rejectUnknown: true }); }
|
|
2283
|
+
catch (e) { threwRejectUnknown = e.code === "env/unknown-keys"; }
|
|
2284
|
+
check("env: rejectUnknown surfaces error", threwRejectUnknown);
|
|
2285
|
+
|
|
2286
|
+
// Required key missing
|
|
2287
|
+
fs.writeFileSync(envPath, "FEATURE_FLAG=true\n");
|
|
2288
|
+
var threwRequired = false;
|
|
2289
|
+
try {
|
|
2290
|
+
b.parsers.env.load(envPath, {
|
|
2291
|
+
expected: { DATABASE_URL: { type: "string", required: true } },
|
|
2292
|
+
audit: false,
|
|
2293
|
+
});
|
|
2294
|
+
} catch (e) { threwRequired = e.code === "env/missing-required"; }
|
|
2295
|
+
check("env: missing required key rejected", threwRequired);
|
|
2296
|
+
|
|
2297
|
+
// Bad type coercion
|
|
2298
|
+
fs.writeFileSync(envPath, "FEATURE_FLAG=yes\n");
|
|
2299
|
+
var threwBadBool = false;
|
|
2300
|
+
try {
|
|
2301
|
+
b.parsers.env.load(envPath, {
|
|
2302
|
+
expected: { FEATURE_FLAG: { type: "boolean" } },
|
|
2303
|
+
audit: false,
|
|
2304
|
+
});
|
|
2305
|
+
} catch (e) { threwBadBool = e.code === "env/bad-type"; }
|
|
2306
|
+
check("env: 'yes' for boolean rejected (no Norway)", threwBadBool);
|
|
2307
|
+
} finally {
|
|
2308
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
function testEnvLoadBreakingChange() {
|
|
2313
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-env-"));
|
|
2314
|
+
try {
|
|
2315
|
+
var envPath = path.join(tmpDir, ".env");
|
|
2316
|
+
var snapPath = path.join(tmpDir, "env.snapshot.json");
|
|
2317
|
+
var expected = {
|
|
2318
|
+
DATABASE_URL: { type: "string", sensitivity: "breaking" },
|
|
2319
|
+
};
|
|
2320
|
+
|
|
2321
|
+
fs.writeFileSync(envPath, "DATABASE_URL=postgres://A\n");
|
|
2322
|
+
b.parsers.env.load(envPath, { expected: expected, snapshotPath: snapPath, audit: false });
|
|
2323
|
+
|
|
2324
|
+
// Try to change without acknowledgement
|
|
2325
|
+
fs.writeFileSync(envPath, "DATABASE_URL=postgres://B\n");
|
|
2326
|
+
var threwBreaking = false;
|
|
2327
|
+
try {
|
|
2328
|
+
b.parsers.env.load(envPath, { expected: expected, snapshotPath: snapPath, audit: false });
|
|
2329
|
+
} catch (e) { threwBreaking = e.code === "env/breaking-change"; }
|
|
2330
|
+
check("env: breaking-sensitivity change refused", threwBreaking);
|
|
2331
|
+
|
|
2332
|
+
// With explicit allow, succeeds
|
|
2333
|
+
var ok = b.parsers.env.load(envPath, {
|
|
2334
|
+
expected: expected,
|
|
2335
|
+
snapshotPath: snapPath,
|
|
2336
|
+
audit: false,
|
|
2337
|
+
allow: ["DATABASE_URL"],
|
|
2338
|
+
});
|
|
2339
|
+
check("env: { allow: [...] } authorises breaking change",
|
|
2340
|
+
ok.values.DATABASE_URL === "postgres://B");
|
|
2341
|
+
} finally {
|
|
2342
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// ---- run() ----
|
|
2347
|
+
|
|
2348
|
+
async function run() {
|
|
2349
|
+
// session
|
|
2350
|
+
await testSession();
|
|
2351
|
+
|
|
2352
|
+
// data residency (db + storage)
|
|
2353
|
+
await testDataResidency();
|
|
2354
|
+
|
|
2355
|
+
// storage + object-store
|
|
2356
|
+
await testStorage();
|
|
2357
|
+
await testMultiBackend();
|
|
2358
|
+
await testClassificationRouting();
|
|
2359
|
+
await testResidencyEnforcement();
|
|
2360
|
+
await testRetryAndBreaker();
|
|
2361
|
+
testSigv4Primitives();
|
|
2362
|
+
await testSigv4MockServer();
|
|
2363
|
+
testGcsPrimitives();
|
|
2364
|
+
await testGcsMockServer();
|
|
2365
|
+
testAzureBlobPrimitives();
|
|
2366
|
+
await testAzureBlobMockServer();
|
|
2367
|
+
|
|
2368
|
+
// queue
|
|
2369
|
+
await testQueueLocal();
|
|
2370
|
+
await testQueueConsume();
|
|
2371
|
+
await testQueueRetryAndFail();
|
|
2372
|
+
await testQueueLeaseExpiry();
|
|
2373
|
+
await testQueueShutdown();
|
|
2374
|
+
testJobsSurface();
|
|
2375
|
+
await testJobsDefineAndEnqueue();
|
|
2376
|
+
await testJobsValidation();
|
|
2377
|
+
await testJobsMultipleHandlers();
|
|
2378
|
+
|
|
2379
|
+
// log-stream
|
|
2380
|
+
await testLogStreamLocal();
|
|
2381
|
+
await testLogStreamWebhook();
|
|
2382
|
+
await testLogStreamBidirectional();
|
|
2383
|
+
|
|
2384
|
+
// external-db
|
|
2385
|
+
await testExternalDbBasic();
|
|
2386
|
+
await testExternalDbPool();
|
|
2387
|
+
await testExternalDbTransaction();
|
|
2388
|
+
await testExternalDbResidency();
|
|
2389
|
+
await testExternalDbClassification();
|
|
2390
|
+
|
|
2391
|
+
// middleware
|
|
2392
|
+
await testMiddlewareRequestId();
|
|
2393
|
+
await testMiddlewareSecurityHeaders();
|
|
2394
|
+
await testMiddlewareErrorHandler();
|
|
2395
|
+
await testMiddlewareBotGuard();
|
|
2396
|
+
await testMiddlewareCors();
|
|
2397
|
+
await testMiddlewareRateLimit();
|
|
2398
|
+
await testMiddlewareAttachUser();
|
|
2399
|
+
await testMiddlewareRequireAuth();
|
|
2400
|
+
await testMiddlewareCsrfProtect();
|
|
2401
|
+
|
|
2402
|
+
// env-safe.load() — full lifecycle (depends on audit chain)
|
|
2403
|
+
await testEnvLoadDiffAndAudit();
|
|
2404
|
+
await testEnvLoadSchemaAndTypos();
|
|
2405
|
+
await testEnvLoadBreakingChange();
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
module.exports = {
|
|
2409
|
+
name: "Layer 4 — consumers (session, storage, queue, log-stream, external-db, middleware, env-load)",
|
|
2410
|
+
run: run,
|
|
2411
|
+
testSession: testSession,
|
|
2412
|
+
testMiddlewareAttachUser: testMiddlewareAttachUser,
|
|
2413
|
+
testMiddlewareRequireAuth: testMiddlewareRequireAuth,
|
|
2414
|
+
testMiddlewareCsrfProtect: testMiddlewareCsrfProtect,
|
|
2415
|
+
testDataResidency: testDataResidency,
|
|
2416
|
+
testStorage: testStorage,
|
|
2417
|
+
testMultiBackend: testMultiBackend,
|
|
2418
|
+
testClassificationRouting: testClassificationRouting,
|
|
2419
|
+
testResidencyEnforcement: testResidencyEnforcement,
|
|
2420
|
+
testRetryAndBreaker: testRetryAndBreaker,
|
|
2421
|
+
testSigv4Primitives: testSigv4Primitives,
|
|
2422
|
+
testSigv4MockServer: testSigv4MockServer,
|
|
2423
|
+
testGcsPrimitives: testGcsPrimitives,
|
|
2424
|
+
testGcsMockServer: testGcsMockServer,
|
|
2425
|
+
testAzureBlobPrimitives: testAzureBlobPrimitives,
|
|
2426
|
+
testAzureBlobMockServer: testAzureBlobMockServer,
|
|
2427
|
+
testQueueLocal: testQueueLocal,
|
|
2428
|
+
testQueueConsume: testQueueConsume,
|
|
2429
|
+
testQueueRetryAndFail: testQueueRetryAndFail,
|
|
2430
|
+
testQueueLeaseExpiry: testQueueLeaseExpiry,
|
|
2431
|
+
testQueueShutdown: testQueueShutdown,
|
|
2432
|
+
testJobsSurface: testJobsSurface,
|
|
2433
|
+
testJobsDefineAndEnqueue: testJobsDefineAndEnqueue,
|
|
2434
|
+
testJobsValidation: testJobsValidation,
|
|
2435
|
+
testJobsMultipleHandlers: testJobsMultipleHandlers,
|
|
2436
|
+
testLogStreamLocal: testLogStreamLocal,
|
|
2437
|
+
testLogStreamWebhook: testLogStreamWebhook,
|
|
2438
|
+
testLogStreamBidirectional: testLogStreamBidirectional,
|
|
2439
|
+
testExternalDbBasic: testExternalDbBasic,
|
|
2440
|
+
testExternalDbPool: testExternalDbPool,
|
|
2441
|
+
testExternalDbTransaction: testExternalDbTransaction,
|
|
2442
|
+
testExternalDbResidency: testExternalDbResidency,
|
|
2443
|
+
testExternalDbClassification: testExternalDbClassification,
|
|
2444
|
+
testMiddlewareRequestId: testMiddlewareRequestId,
|
|
2445
|
+
testMiddlewareSecurityHeaders: testMiddlewareSecurityHeaders,
|
|
2446
|
+
testMiddlewareErrorHandler: testMiddlewareErrorHandler,
|
|
2447
|
+
testMiddlewareBotGuard: testMiddlewareBotGuard,
|
|
2448
|
+
testMiddlewareCors: testMiddlewareCors,
|
|
2449
|
+
testMiddlewareRateLimit: testMiddlewareRateLimit,
|
|
2450
|
+
testEnvLoadDiffAndAudit: testEnvLoadDiffAndAudit,
|
|
2451
|
+
testEnvLoadSchemaAndTypos: testEnvLoadSchemaAndTypos,
|
|
2452
|
+
testEnvLoadBreakingChange: testEnvLoadBreakingChange,
|
|
2453
|
+
};
|