@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,2257 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-consolidated-schema.json",
|
|
3
|
+
"minor": "0.9",
|
|
4
|
+
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.9.57",
|
|
7
|
+
"date": "2026-05-16",
|
|
8
|
+
"headline": "`b.mail.journal` — WORM compliance journal for inbound + outbound mail",
|
|
9
|
+
"summary": "Tamper-evident, retention-bound, legal-hold-aware copy of every message that crosses the mail boundary. Composes existing framework substrate (objectStore Object Lock, cryptoField, legalHold, retention.complianceFloor, audit-sign) — no new storage / crypto / retention vocabulary.",
|
|
10
|
+
"sections": [
|
|
11
|
+
{
|
|
12
|
+
"heading": "Added",
|
|
13
|
+
"items": [
|
|
14
|
+
{
|
|
15
|
+
"title": "`b.mail.journal.create({ storage, vault, legalHold, db, regimes })`",
|
|
16
|
+
"body": "Returns a handle exposing `record({ direction, actorId, messageId, headers, envelope, bodyBytes })` (appends a sealed entry to the operator's WORM bucket + the local index table), `getById(journalId)` (round-trips one entry; unseal via vault), `list({ direction?, since?, until?, actorId?, limit? })` (cursor-bounded list), `expireSurface()` (entries past their retention floor AND not under legal hold — for operator audit, no auto-delete), and `setLegalHold(journalId, onHold)` (toggle hold flag)."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"title": "Composition over invention",
|
|
20
|
+
"body": "Composes `b.objectStore.bucketOps({ objectLockEnabled: true })` for WORM-enforced immutability at the storage layer (S3 Object Lock / Azure Immutable Blob / GCS retention-policy); `b.cryptoField.sealRow` for at-rest encryption with vault key — plaintext columns kept queryable (`journalId`, `direction`, `archivedAt`, `actorId`, `messageId`, `sizeBytes`, `regimes`, `legalHold`), everything else sealed; `b.legalHold` for exemption from retention expiry; `b.retention.complianceFloor` for per-regime windows."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"title": "Regime registry",
|
|
24
|
+
"body": "`sec-17a-4` / `finra-4511` / `hipaa` (6yr), `mifid-ii` (5yr), `sox` (7yr), `gdpr` (6yr UK ICO + ePrivacy guidance), `soc2` (1yr). Operator declares one or more via `regimes: [\"sec-17a-4\", \"finra-4511\"]`; the journal computes the longest floor across all declared regimes and tags every entry with `floorUntil = archivedAt + maxFloorMs`."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"title": "Explicit non-goals",
|
|
28
|
+
"body": "No delete surface (WORM bucket enforces immutability; operators needing GDPR Art. 17 erasure on a journaled message MUST crypto-erase via `b.cryptoField.eraseRow` — the operator's posture choice between regulatory record-keeping and right-to-be-forgotten); no automated expiry (`expireSurface()` returns the list of candidates, operators decide); no MX / submission auto-wiring (this release ships the primitive; the next release will auto-call `record()` from the MX listener + submission listener so inbound + outbound traffic journals without operator wiring)."
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"heading": "Security",
|
|
34
|
+
"items": [
|
|
35
|
+
{
|
|
36
|
+
"title": "Threat model",
|
|
37
|
+
"body": "Tamper-evidence via WORM bucket (Object Lock makes write-once enforceable at the storage layer, not just policy); per-message encryption-at-rest via vault key; index table keyed on archivedAt + message_id for forensic query without unseal; audit emit on every record / read / list / hold-change operation routes to `b.audit.safeEmit`."
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"references": [
|
|
43
|
+
{
|
|
44
|
+
"label": "SEC Rule 17a-4(f)",
|
|
45
|
+
"url": "https://www.ecfr.gov/current/title-17/chapter-II/part-240/section-240.17a-4"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"label": "FINRA Rule 4511",
|
|
49
|
+
"url": "https://www.finra.org/rules-guidance/rulebooks/finra-rules/4511"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"label": "MiFID II Article 16(7)",
|
|
53
|
+
"url": "https://www.esma.europa.eu/"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"version": "0.9.56",
|
|
59
|
+
"date": "2026-05-16",
|
|
60
|
+
"headline": "`b.mail.deploy` operator-deployment helpers — MTA-STS / DANE / AutoConfig / AutoDiscover publish surface",
|
|
61
|
+
"summary": "Closes the publish-side of the inbound mail verifiers shipped earlier in the 0.9.x line; operators standing up a new mail deployment now have four small generators that produce RFC-shaped policy text + DNS records + client auto-discovery XML.",
|
|
62
|
+
"sections": [
|
|
63
|
+
{
|
|
64
|
+
"heading": "Added",
|
|
65
|
+
"items": [
|
|
66
|
+
{
|
|
67
|
+
"title": "`b.mail.deploy.mtaStsPublish({ domain, mode, mxHosts, maxAgeSec, policyId? })`",
|
|
68
|
+
"body": "Generates the RFC 8461 §3.2 `/.well-known/mta-sts.txt` policy text + `_mta-sts.<domain>` TXT record advice. Wildcard `*.mx.<domain>` entries accepted per §3.2.1. `mode` constrained to `enforce` / `testing` / `none`; `maxAgeSec` capped at 1 year. Pairs with the existing inbound MTA-STS verifier on the receiving side — operators publishing the policy AND verifying inbound peers run the same vocabulary."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"title": "`b.mail.deploy.danePublish({ certPem, mxHost, port?, usage?, selector?, matchType? })`",
|
|
72
|
+
"body": "Generates RFC 7672 + RFC 6698 TLSA record string. Computes SHA-256 (or SHA-512) SubjectPublicKeyInfo hash from the operator-supplied PEM cert via `node:crypto.X509Certificate` + `crypto.createHash`. Defaults to DANE-EE / SPKI / SHA-256 — RFC 7672 §3.1.3 recommended posture because it survives intermediate-CA changes as long as the leaf key is stable. Refuses `matchType: 0` (exact match) to keep the record size bounded."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"title": "`b.mail.deploy.autoConfigXml({ domain, displayName?, imap?, pop3?, smtp?, jmap? })`",
|
|
76
|
+
"body": "Thunderbird's `autoconfig.<domain>/mail/config-v1.1.xml` payload. Outlook, Apple Mail, and Evolution read the same file when present. Every operator-supplied string XML-escaped (`&` / `<` / `>` / `\"` / `'`) so a hostile `displayName` can't inject new XML nodes."
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"title": "`b.mail.deploy.autoDiscoverXml({ email, imap?, pop3?, smtp? })`",
|
|
80
|
+
"body": "Outlook's `autodiscover/autodiscover.xml` response shape (MS-OXDISCO + MS-OXDSCLI). Operator extracts the email from the inbound POST body via their route handler; this generator returns the response XML. Refuses CR / LF / NUL / control bytes in the email field (XML-injection class). These are text generators, not route handlers — operators wire the output into `b.staticServe` (mta-sts.txt + autoconfig.xml) or their own POST handler (autodiscover, which is request-conditional). No new network surface, no I/O — pure deterministic functions."
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"references": [
|
|
86
|
+
{
|
|
87
|
+
"label": "RFC 8461 (MTA-STS)",
|
|
88
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8461"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"label": "RFC 7672 (DANE for SMTP)",
|
|
92
|
+
"url": "https://www.rfc-editor.org/rfc/rfc7672"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"label": "RFC 6698 (TLSA records)",
|
|
96
|
+
"url": "https://www.rfc-editor.org/rfc/rfc6698"
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"version": "0.9.55",
|
|
102
|
+
"date": "2026-05-16",
|
|
103
|
+
"headline": "`b.safeSieve` parser + `b.mail.sieve` interpreter (RFC 5228 base grammar)",
|
|
104
|
+
"summary": "Lifts `b.mail.agent.sieve.put` from a stub and wires the RFC 5228 grammar through to the agent. Ships base-grammar coverage that handles ~80% of operator-written scripts; deferred extensions refused at parse time per RFC 5228 §3.2.",
|
|
105
|
+
"sections": [
|
|
106
|
+
{
|
|
107
|
+
"heading": "Added",
|
|
108
|
+
"items": [
|
|
109
|
+
{
|
|
110
|
+
"title": "`b.safeSieve` — bounded RFC 5228 parser",
|
|
111
|
+
"body": "Refuses oversized scripts (64 KiB strict / 256 KiB balanced / 1 MiB permissive), block-nesting beyond `maxDepth` (32/64/128), per-string byte cap (4/16/64 KiB), string-list count cap (256/1024/4096), command-arg cap (32/64/128), elsif-chain cap (32/64/128). Refuses C0 / DEL / NUL control bytes outside string literals, bare CR / bare LF (RFC 5228 §2.1 requires CRLF), unknown capabilities at `require`, AND RFC-defined-but-not-implemented capabilities so scripts depending on `vacation` / `variables` / `imap4flags` / etc. fail at parse time instead of silently mis-executing. Three profiles + HIPAA/PCI-DSS/GDPR/SOC2 posture cascades (all map to strict). Grammar coverage: `require` / `if` / `elsif` / `else` / tests (`address` / `header` / `envelope` / `exists` / `size` / `not` / `allof` / `anyof` / `true` / `false`) / actions (`keep` / `fileinto` / `discard` / `redirect` / `stop`) / match-types (`:is` / `:contains` / `:matches`) / comparators / address-parts / string lists / quoted strings / multi-line `text:` strings with dot-stuffing / `#` and `/* */` comments / number suffixes."
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"title": "`b.mail.sieve` — pure AST walker running under a gas counter",
|
|
115
|
+
"body": "Default 10 000 ops; cap 1 000 000. The interpreter reads only from operator-supplied `env` (`{ headers, envelope, sizeBytes, bodyBytes }`) and never mutates it; side-effects (`fileinto` / `redirect` / `discard`) surface as entries in the returned action list for the caller's delivery code to dispatch against the mail store. `b.mail.sieve.run(ast, env, { maxGas })` walks a parsed AST; `b.mail.sieve.runScript(script, env, opts)` parses + runs in one call; `b.mail.sieve.create({ profile, compliancePosture, maxGas, audit })` returns a stateful handle the JMAP `SieveScript/validate` method + MX delivery hook compose."
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"title": "`b.mail.agent.sieve.put` wiring",
|
|
119
|
+
"body": "The agent's `sieve.put` method now parse-validates via `b.safeSieve` after the existing `b.guardMailSieve` shape check. Operator's persistence step receives a `{ ok: true, requiredCaps }` shape; scripts with unknown / unimplemented capabilities or grammar errors throw `mail-agent/sieve-parse-error` with the first issue's snippet. `agent.sieve.list` and `agent.sieve.activate` stay deferred — the operator owns the persistence layer; the next release that ships a default sieve-script store backend will light those up."
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"heading": "Security",
|
|
125
|
+
"items": [
|
|
126
|
+
{
|
|
127
|
+
"title": "Threat-model coverage",
|
|
128
|
+
"body": "Runaway scripts defended by the gas counter (per-script cap) + per-tests cap on `allof` / `anyof` sub-count; parser-bomb defended by the depth + elsif-chain + string-list caps; SMTP-style header injection defended by the C0 / DEL / NUL refusals (Sieve scripts arrive from operator UIs that may forward operator-controlled bytes); RFC 5228 §10.1 security considerations on `redirect` defended by leaving address normalization to the operator's delivery agent (the interpreter only captures the literal address string)."
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
"references": [
|
|
134
|
+
{
|
|
135
|
+
"label": "RFC 5228 (Sieve mail filtering language)",
|
|
136
|
+
"url": "https://www.rfc-editor.org/rfc/rfc5228"
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"version": "0.9.54",
|
|
142
|
+
"date": "2026-05-16",
|
|
143
|
+
"headline": "`b.mail.server.jmap` JMAP Core + Mail listener (RFC 8620 + RFC 8621) + `b.guardJmap` wire-protocol validator",
|
|
144
|
+
"summary": "JMAP lands as the framework's primary mail-access protocol per the mail-stack roadmap. JSON-over-HTTP, batched, push-capable, and unlike IMAP/POP3 doesn't carry decades of stateful-protocol scar tissue. Modern mail clients (Fastmail apps, Cyrus + JMAP proxy, Stalwart-compatible MUAs) connect here.",
|
|
145
|
+
"sections": [
|
|
146
|
+
{
|
|
147
|
+
"heading": "Added",
|
|
148
|
+
"items": [
|
|
149
|
+
{
|
|
150
|
+
"title": "`b.guardJmap` — wire-protocol validator for JMAP Core + Mail",
|
|
151
|
+
"body": "Validates `Invocation` arrays (`[method, args, callId]` per RFC 8620 §3.2), refuses back-reference pipelining past depth 8 (DoS class against the resolver), caps `using[]` capability count, request body size, per-invocation arg-size, and per-batch invocation count. Refuses any string containing C0 / DEL / NUL controls (header-injection class). JSON parsing routes through `b.safeJson` so prototype-pollution + recursion-bomb shapes refused at the parser boundary. Three profiles + HIPAA / PCI-DSS / GDPR / SOC2 posture cascades (all map to strict). Fuzz harness ships in `fuzz/guard-jmap.fuzz.js`."
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"title": "`b.mail.server.jmap` — JMAP Core endpoint mounted as an HTTP route handler",
|
|
155
|
+
"body": "NOT a separate TCP server. Implements the Session resource (`.well-known/jmap` per RFC 8620 §2 + §6.2 — declares supported capabilities, accountId mapping, eventSource URLs, downloadUrl / uploadUrl templates), the API endpoint (POST then `methodResponses[]` per RFC 8620 §3.3 invocation pipeline), upload route (POST blob — guard-* gated by Content-Type), download route (GET sealed blob), and EventSource push (RFC 8620 §7 — `text/event-stream` SSE; opt-in WebSocket via RFC 8887)."
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"title": "Operator-supplied `methods: { [name]: fn(args, ctx) }` registry handles per-method dispatch",
|
|
159
|
+
"body": "The listener owns request validation, back-ref resolution, accountId isolation, error mapping (RFC 8620 §3.5.2 method errors / §3.6.1 request errors), and SSE state-change notification; operators implement Email/get, Email/set, Mailbox/get, etc. against their `b.mailStore`. Per-tenant `accountId` isolation — every method invocation carries the authenticated actor's accountId; cross-account access refused at the dispatcher. Back-reference pipelining (`#prev-call-result`) resolved with depth cap. RFC 8620 §3.6.1 request errors returned as JMAP-shaped problem details, not generic HTTP 4xx bodies. What v1 does NOT ship: WebSocket push framing details (operator opts via separate WS upgrade route), `pushSubscription` storage backend (operator wires per-tenant), JMAP for Calendars / Contacts, Sieve filter management (RFC 9404), `Mailbox/changes` Tombstones beyond the 30-day window."
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
"references": [
|
|
165
|
+
{
|
|
166
|
+
"label": "RFC 8620 (JMAP Core)",
|
|
167
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8620"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"label": "RFC 8621 (JMAP for Mail)",
|
|
171
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8621"
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"version": "0.9.53",
|
|
177
|
+
"date": "2026-05-16",
|
|
178
|
+
"headline": "`b.mail.server.pop3` POP3 listener (RFC 1939) + `b.guardPop3Command` wire-protocol validator",
|
|
179
|
+
"summary": "POP3 listener for clients that want pure download-and-delete semantics rather than IMAP's server-side store (Thunderbird-with-POP, mutt, fetchmail, getmail, K-9 in POP mode, headless backup-fetchers).",
|
|
180
|
+
"sections": [
|
|
181
|
+
{
|
|
182
|
+
"heading": "Added",
|
|
183
|
+
"items": [
|
|
184
|
+
{
|
|
185
|
+
"title": "`b.guardPop3Command` — wire-protocol validator",
|
|
186
|
+
"body": "RFC 1939 + RFC 2449 CAPA + RFC 2595 STLS + RFC 5034 AUTH. Refuses bare-CR / bare-LF / NUL / C0 / DEL (smuggling defense identical to the SMTP/IMAP guards), enforces RFC 1939 §3 4-character verb shape, per-line cap (255 chars strict / 512 balanced / 1024 permissive per §3 + §4 recommendation), per-verb arg-arity. Verbs: USER / PASS / APOP / AUTH / STLS / CAPA / STAT / LIST / RETR / DELE / NOOP / RSET / TOP / UIDL / QUIT. Strict refuses USER / PASS / mech-bearing AUTH pre-TLS (RFC 2595 §2.1 + RFC 5034 §4) and refuses APOP entirely (MD5 challenge-response — broken since CVE-2007-1558). Balanced + permissive accept both with audit. Three profiles + HIPAA / PCI-DSS / GDPR / SOC2 posture cascades (all map to strict). Fuzz harness ships in `fuzz/guard-pop3-command.fuzz.js`."
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"title": "`b.mail.server.pop3` — POP3 listener state machine",
|
|
190
|
+
"body": "AUTHORIZATION to TRANSACTION to UPDATE per RFC 1939 §3 lifecycle. Composes `b.guardPop3Command` + `b.mail.server.rateLimit` (per-IP concurrent + rate + AUTH-failure budget, default-on) + operator-supplied `b.mailStore` backend + operator-supplied SASL authenticator. STLS-injection defense per CVE-2021-33515 class — pre-handshake `lineBuffer` cleared at TLS upgrade so pipelined commands sent before STLS can't take effect after. STLS state gating per RFC 2595 §4 — STLS refused outside AUTHORIZATION so a TLS re-key can't land mid-session on top of established mailbox state. RFC 1939 §3 dot-stuffing on RETR / TOP — every line beginning with `.` in the message body emits as `..` on the wire so the multi-line terminator stays unambiguous. RFC 2449 CAPA advertises AUTH=<mech> only for operator-wired mechanisms (no hardcoded `AUTH=PLAIN`)."
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"title": "Audit lifecycle",
|
|
194
|
+
"body": "`mail.server.pop3.{connect, auth_attempt, auth_success, auth_failed, auth_refused_cleartext, transaction_start, retr, top, dele, rset, quit, stls_handshake_failed, stls_upgraded, listening, closed, socket_error, handler_threw}`. What v1 does NOT ship: APOP server (refused under strict; balanced + permissive accept but operator wires the shared-secret check), RFC 5034 SASL response framing for multi-step mechanisms (operator-supplied authenticator returns `{ pending, challenge }` per the same shape as IMAP/SMTP submission), draft-melnikov-pop3-mime-imap-uidplus cross-protocol UID alignment, RFC 2595 §5 `PIPELINING` capability (not in CAPA — RFC 1939's response shape doesn't carry length so pipelining can't be safely parsed by the client without out-of-band info)."
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
"references": [
|
|
200
|
+
{
|
|
201
|
+
"label": "RFC 1939 (POP3)",
|
|
202
|
+
"url": "https://www.rfc-editor.org/rfc/rfc1939"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"label": "RFC 2595 (STLS)",
|
|
206
|
+
"url": "https://www.rfc-editor.org/rfc/rfc2595"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"label": "RFC 5034 (POP3 SASL)",
|
|
210
|
+
"url": "https://www.rfc-editor.org/rfc/rfc5034"
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"version": "0.9.52",
|
|
216
|
+
"date": "2026-05-16",
|
|
217
|
+
"headline": "`b.mail.create` recipient + sender `b.guardDomain` hardening (default-on)",
|
|
218
|
+
"summary": "Symmetric to the inbound listener wiring shipped earlier — every outbound `mail.send({ to, cc, bcc, from })` now routes the domain part of each address through `b.guardDomain.validate` by default.",
|
|
219
|
+
"sections": [
|
|
220
|
+
{
|
|
221
|
+
"heading": "Changed",
|
|
222
|
+
"items": [
|
|
223
|
+
{
|
|
224
|
+
"title": "Outbound `mail.send` addresses gate through `b.guardDomain` by default",
|
|
225
|
+
"body": "Defends CVE-2017-5469-class IDN homograph spoofs, RFC 6761 special-use domain names in production sends, RFC 1035 §2.3.4 label-length violations, and CVE-2021-22931-class bare-IP-as-domain (DNS-rebinding allowlist-bypass). RFC 5321 §4.1.3 address-literal form skipped (bracket-syntax already constrains). EAI (RFC 6531) recipients get RFC 5891 ToASCII conversion before validation so `<x@münchen.example>` passes the gate as `xn--mnchen-3ya.example`. Operator opt-outs: `b.mail.create({ guardDomain: false })` skips; `b.mail.create({ guardDomain: { profile: \"permissive\" } })` relaxes. Default profile mirrors `b.mail.create({ profile })` if set, else strict."
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
],
|
|
230
|
+
"references": [
|
|
231
|
+
{
|
|
232
|
+
"label": "CVE-2017-5469 (Punycode display)",
|
|
233
|
+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-5469"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"label": "RFC 6761 (special-use domain names)",
|
|
237
|
+
"url": "https://www.rfc-editor.org/rfc/rfc6761"
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"version": "0.9.50",
|
|
243
|
+
"date": "2026-05-16",
|
|
244
|
+
"headline": "Mail-deployment helpers — 0xE1 envelope migration path + `b.mail.dkim.bootstrap` + `b.mail.server.tls.context`",
|
|
245
|
+
"summary": "Three operator-facing primitives that unblock deployment workflows for legacy envelope migration, turnkey DKIM keypair issuance, and TLS context reload.",
|
|
246
|
+
"sections": [
|
|
247
|
+
{
|
|
248
|
+
"heading": "Added",
|
|
249
|
+
"items": [
|
|
250
|
+
{
|
|
251
|
+
"title": "`b.crypto.decrypt(ct, keys, { allowLegacy: true })`",
|
|
252
|
+
"body": "Pre-v1 the envelope magic byte was bumped from 0xE1 to 0xE2 to enforce a NIST SP 800-56C r2 §4.1 FixedInfo / RFC 9180 §5.1 suite-binding KDF input. 0xE1 envelopes lack this binding; default behavior is hard-refusal with a clear migration message. Operators with at-rest data sealed pre-bump pass `{ allowLegacy: true }` as the third arg to read the old envelope, then re-seal via `b.crypto.encrypt` to migrate. Each legacy decrypt emits a `system.crypto.decrypt.allow_legacy` audit event so the migration window is visible in the audit log. The audit emits `outcome: \"success\"` only after a successful return + `outcome: \"failure\"` on throw — corrupted blobs / wrong keys / unsupported KEMs surface honestly in the audit chain."
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"title": "`b.mail.dkim.bootstrap({ domain, selector, algorithm? })`",
|
|
256
|
+
"body": "Turnkey DKIM keypair + DNS TXT record + ready-to-use signer for operators deploying outbound mail. Returns `{ privateKeyPem, publicKeyPem, dnsName, dnsTxtValue, dnsRecord, signer() }`. Default algorithm is `ed25519-sha256` (RFC 8463 §2); operators with receivers that don't support Ed25519 pass `algorithm: \"rsa-sha256\"` (RFC 6376, default 2048-bit per RFC 8301 §3.1 guidance; refused < 1024). `algorithm: \"dual\"` mints both keypairs and the signer emits two DKIM-Signature headers per RFC 8463 §3 dual-signing pattern for max receiver compat. DNS TXT records that exceed 255 octets are split per RFC 1035 §3.3.14."
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"title": "`b.mail.server.tls.context({ certFile, keyFile, vault?, watch? })`",
|
|
260
|
+
"body": "Operator-UX helper for the `tlsContext` opt that `b.mail.server.mx` + `b.mail.server.submission` require at create-time (no implicit plaintext mode). Three things every deployment needed and was reinventing: sealed-key unwrap (operators passing `vault: b.vault` get unsealing for `b.vault.sealPemFile`-stored keys), cert-rotation in-process reload (`watch: true` polls mtimes every 30s, builds a fresh `SecureContext` on change, fires `onReload` listeners + emits `mail.server.tls.context_reloaded` audit; mid-rotation read failures keep the prior good context live + emit a `reload_failed` audit), and boot-fail surface (missing/unreadable file, unsealable key, mismatched cert/key, bad PEM — all typed `MailServerTlsError` codes). ACME provisioning stays in `b.acme` (RFC 8555 + RFC 9773 ARI); this primitive just loads what's on disk and reloads when it changes. `b.mail.server.mx`'s `tlsContext`-required error message now points operators at `b.mail.server.tls.context` + `b.acme` so the boot dead-end becomes a one-line fix."
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
],
|
|
265
|
+
"references": [
|
|
266
|
+
{
|
|
267
|
+
"label": "RFC 8463 (Ed25519 in DKIM)",
|
|
268
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8463"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"label": "RFC 6376 (DKIM Signatures)",
|
|
272
|
+
"url": "https://www.rfc-editor.org/rfc/rfc6376"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"label": "RFC 9180 §5.1 (HPKE KDF inputs)",
|
|
276
|
+
"url": "https://www.rfc-editor.org/rfc/rfc9180#section-5.1"
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"version": "0.9.49",
|
|
282
|
+
"date": "2026-05-16",
|
|
283
|
+
"headline": "`b.mail.server.imap` IMAP4rev2 listener (RFC 9051) + `b.guardImapCommand` wire-protocol validator",
|
|
284
|
+
"summary": "Modern MUAs (Thunderbird, Apple Mail, mutt, K-9, FairEmail) connect here to read + manage messages without operators running dovecot/cyrus alongside.",
|
|
285
|
+
"sections": [
|
|
286
|
+
{
|
|
287
|
+
"heading": "Added",
|
|
288
|
+
"items": [
|
|
289
|
+
{
|
|
290
|
+
"title": "`b.guardImapCommand` — wire-protocol validator for IMAP4rev2",
|
|
291
|
+
"body": "RFC 9051 (August 2021; obsoletes RFC 3501). Refuses bare-CR/LF/NUL/C0/DEL outside literals (smuggling defense analogous to SMTP), enforces RFC 9051 §2.2.2 literal framing (mid-line `{n}` openers refused via `detectLiteralSmuggling`; LITERAL+ per RFC 7888 refused pre-AUTH per §1), per-verb shape, line cap (8 KiB strict / 16 KiB balanced / 64 KiB permissive), literal cap (64 MiB strict / 128 MiB balanced / 256 MiB permissive). Strict + balanced + permissive profiles + HIPAA/PCI-DSS/GDPR/SOC2 compliance postures (all map to strict)."
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"title": "`b.mail.server.imap` — IMAP4rev2 listener state machine",
|
|
295
|
+
"body": "NOT-AUTHENTICATED to STARTTLS to AUTH to SELECTED to LOGOUT. Commands: CAPABILITY / NOOP / LOGOUT / ID / STARTTLS / AUTHENTICATE / LOGIN (refused under strict per RFC 9051 §6.3.4) / ENABLE / SELECT / EXAMINE / LIST / STATUS / NAMESPACE / APPEND (incl. zero-byte literals `{0}` per RFC 9051 §6.3.12) / CHECK / CLOSE / UNSELECT / EXPUNGE / FETCH / STORE / UID FETCH/STORE (with `useUid: true` thread to mailStore per RFC 9051 §6.4.9) / IDLE + DONE (RFC 2177; 29-min bandwidth timeout per §3). CAPABILITY advertises AUTH=<mech> only for operator-wired mechanisms."
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"title": "STARTTLS-injection + literal-injection + mailbox-traversal defenses",
|
|
299
|
+
"body": "STARTTLS-injection defense per CVE-2021-33515 class — pre-handshake receive buffer cleared at TLS upgrade so pipelined commands sent before TLS can't take effect after. Literal-injection defense per CVE-2018-19518. Mailbox-name traversal refusal. Per-connection state lives on the `state` object so concurrent connections don't clobber. Composes `b.guardImapCommand` + `b.mail.server.rateLimit` (per-IP concurrent + rate + AUTH-failure budget, default-on) + operator-supplied `b.mailStore` backend + operator-supplied SASL authenticator. What v1 does NOT ship: SEARCH expressions (operator wires `mailStore.search`), NOTIFY (RFC 5465), METADATA (RFC 5464), CATENATE (RFC 4469), URLAUTH (RFC 4467), IMAPSIEVE (RFC 6785), COMPRESS=DEFLATE (RFC 4978; CRIME-class), CONDSTORE/QRESYNC per-FETCH delta."
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"heading": "Detectors",
|
|
305
|
+
"items": [
|
|
306
|
+
{
|
|
307
|
+
"title": "Two new detectors for IMAP-specific footguns",
|
|
308
|
+
"body": "`literalSize > 0` zero-rejection footgun + hardcoded SASL mech in caps array. Both shapes were caught during review; the detectors prevent reappearance across listeners."
|
|
309
|
+
}
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
],
|
|
313
|
+
"references": [
|
|
314
|
+
{
|
|
315
|
+
"label": "RFC 9051 (IMAP4rev2)",
|
|
316
|
+
"url": "https://www.rfc-editor.org/rfc/rfc9051"
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"label": "CVE-2018-19518 (IMAP literal injection)",
|
|
320
|
+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-19518"
|
|
321
|
+
}
|
|
322
|
+
]
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"version": "0.9.47",
|
|
326
|
+
"date": "2026-05-15",
|
|
327
|
+
"headline": "`b.mail.server.submission` outbound SMTP submission listener + per-IP DoS defenses + `b.guardDomain` wiring on mail-listener domain crossings",
|
|
328
|
+
"summary": "Outbound submission listener for port 587 (explicit STARTTLS) or port 465 (implicit TLS per RFC 8314). Per-IP rate / concurrency / AUTH-failure budgets default-on as belt-and-suspenders to kernel/proxy limits. Domain-shape guard wired into every domain crossing in both mail listeners.",
|
|
329
|
+
"sections": [
|
|
330
|
+
{
|
|
331
|
+
"heading": "Added",
|
|
332
|
+
"items": [
|
|
333
|
+
{
|
|
334
|
+
"title": "`b.mail.server.submission` — outbound SMTP submission listener",
|
|
335
|
+
"body": "Where the MX listener accepts inbound mail from the internet, this listener accepts outbound mail from authenticated MUAs / app-side mail-senders on port 587 or 465. Composes the framework's existing primitives: `b.guardSmtpCommand` for wire-protocol shape + smuggling defense, `b.safeSmtp` for DATA-body parsing, the operator's SASL authenticator for credentials, and an operator-supplied `agent.handoff` for outbound routing through `b.mail.send`. Inherits MX listener's SMTP-smuggling defense (CVE-2023-51764 / -51765 / -51766 / 2024-32178 / RFC 5321 §2.3.8) and STARTTLS-injection defense (CVE-2021-38371 Exim, CVE-2021-33515 Dovecot)."
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"title": "Authentication discipline — AUTH required, AUTH-needs-TLS, implicit-TLS, identity binding, recipient policy",
|
|
339
|
+
"body": "AUTH required before MAIL FROM under strict + balanced profiles (RFC 6409 §3); pre-STARTTLS AUTH refused with 538 5.7.11 under strict + balanced (RFC 4954 §4); permissive opts down for legacy operator-acknowledged downgrade. Operator-supplied `auth.verify(mechanism, credentials)` async predicate decides the credential check; multi-step SASL mechanisms (SCRAM-SHA-256, GS2-* family) supported via `{ pending: true, challenge }` return shape per RFC 4954 §4. Implicit-TLS mode (`implicitTls: true` to port 465 per RFC 8314 §3.3) wraps every connection in TLS from the SYN. Identity binding under strict — `MAIL FROM:<x@y>` MUST match an entry in the authenticated actor's mailbox set; refused with 553 5.7.1. Recipient policy hook (`opts.recipientPolicy`) wires per-RCPT decisions (block destination / outbound budget / deny list); refusal returns 550 5.7.1, policy-engine failure returns 451 4.7.1 (transient)."
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"title": "`b.mail.server.rateLimit` — per-IP DoS defense wired into both listeners",
|
|
343
|
+
"body": "Per-IP concurrent connection cap (`maxConcurrentConnectionsPerIp`, default 10) — a single hostile peer cannot open thousands of TCP slots and starve legitimate senders. Per-IP connection rate (`connectionsPerIpPerMinute`, default 60). Per-IP AUTH-failure budget (`authFailuresPerIpPer15Min`, default 10; submission listener only) — credential-stuffing class. Slow-loris / `minBytesPerSecond` floor (default 100 bytes/sec) on the DATA-body phase complements `idleTimeoutMs`. Refused connections receive `421 4.7.0 Too many connections from your IP`. Operators pass `rateLimit: false` to disable for tests, a shared handle via `b.mail.server.rateLimit.create({...})` to share one budget across multiple listeners, or an opts object to override defaults."
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
"title": "`b.guardDomain` wiring on every mail-listener domain crossing",
|
|
347
|
+
"body": "HELO / EHLO greeting, MAIL FROM domain, RCPT TO domain, and operator-supplied `opts.localDomains` all route through `b.guardDomain.validate` (default-on; opt-out via `guardDomain: false`). Defends CVE-2017-5469-class IDN homograph spoofs, refuses RFC 6761 special-use domain names in production (`.localhost`, `.test`, `.invalid`, `.example`), enforces RFC 1035 §2.3.4 label-length caps, and refuses bare IPv4/IPv6 as a domain (CVE-2021-22931 class allowlist-bypass via DNS rebinding). RFC 5321 §4.1.3 address literals (`[1.2.3.4]` / `[IPv6:...]`) skip guardDomain — already constrained by `b.guardSmtpCommand`'s bracket-syntax validator. `opts.localDomains` is pre-validated at `create()` time so an operator who typed an IDN homograph into their allowlist gets a `mail-server-mx/bad-local-domain` boot failure instead of a silently-weakened gate."
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"title": "`b.selfUpdate.compareTags(a, b)`",
|
|
351
|
+
"body": "The internal `_compareTags` helper (used by `b.selfUpdate.poll` / `pickRelease`) is now part of the public API. Returns `-1` / `0` / `+1`. Strips a leading `v` / `V`, then walks dot-separated components: numeric pairs compared numerically, non-numeric components (release suffixes) fall back to lexicographic compare. Follows SemVer 2.0.0 §11 precedence for the numeric prefix; pre-release identifier comparison is lexicographic rather than the full SemVer-mandated alphanumeric rule."
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"title": "`b.metrics.snapshot.render({ format: \"prometheus\" })` field-type metadata",
|
|
355
|
+
"body": "Previously every numeric field rendered as `# TYPE <name> gauge` regardless of name, which broke `rate()` queries against counter-shaped series. The renderer now auto-detects per the Prometheus naming convention + OpenMetrics 1.0.0 §6.2: field names ending in `_total` render as `counter`; everything else renders as `gauge`. Operators with metrics that don't fit the convention opt the right type via `opts.fieldTypes: { fieldName: \"counter\" | \"gauge\" }`. Behavior change for operators scraping a long-running deployment — `rate(*_total[5m])` queries start returning correct answers once the new types reach the scrape target."
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
"heading": "Fixed",
|
|
361
|
+
"items": [
|
|
362
|
+
{
|
|
363
|
+
"title": "RCPT TO cap-check counts in-flight async verdicts",
|
|
364
|
+
"body": "When `opts.recipientPolicy` is async, the recipient-limit guard ran before the policy promise resolved and the accepted recipient was appended later. Under SMTP PIPELINING (RFC 2920) each new RCPT TO saw the same `state.rcpts.length == 0` (prior commands hadn't pushed yet), so the cap-check passed for every command and `state.rcpts` grew past `maxRcptsPerMessage` once all verdicts resolved. Fix: track in-flight verdicts in `state.rcptsPending`; cap-check counts BOTH committed AND in-flight against `maxRcptsPerMessage`; defense-in-depth re-check inside the `.then()` before push. `_resetTransaction` zeroes the pending counter."
|
|
365
|
+
}
|
|
366
|
+
]
|
|
367
|
+
}
|
|
368
|
+
],
|
|
369
|
+
"references": [
|
|
370
|
+
{
|
|
371
|
+
"label": "RFC 6409 (Message Submission)",
|
|
372
|
+
"url": "https://www.rfc-editor.org/rfc/rfc6409"
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
"label": "RFC 4954 (SMTP AUTH)",
|
|
376
|
+
"url": "https://www.rfc-editor.org/rfc/rfc4954"
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
"label": "RFC 8314 (Cleartext deprecation)",
|
|
380
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8314"
|
|
381
|
+
}
|
|
382
|
+
]
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"version": "0.9.46",
|
|
386
|
+
"date": "2026-05-15",
|
|
387
|
+
"headline": "`b.mail.server.mx` — inbound SMTP / MX listener + `b.safeSmtp` parser + `b.guardSmtpCommand.detectBodySmuggling`",
|
|
388
|
+
"summary": "First framework-native mail listener — drives the RFC 5321 CONNECT then EHLO then [STARTTLS then EHLO] then MAIL then RCPT then DATA then DATA-body then QUIT state machine. Composes the existing mail-gate substrates (helo / rbl / greylist / guardEnvelope / dmarc / safeMime / guardEmail / guardSmtpCommand / mail.agent).",
|
|
389
|
+
"sections": [
|
|
390
|
+
{
|
|
391
|
+
"heading": "Added",
|
|
392
|
+
"items": [
|
|
393
|
+
{
|
|
394
|
+
"title": "`b.safeSmtp.findDotTerminator(buf)` + `b.safeSmtp.dotUnstuff(buf)`",
|
|
395
|
+
"body": "Wire-protocol parsing extracted to a reusable safe module — where the body terminator is, how to reverse dot-stuffing per RFC 5321 §4.5.2. Same primitives ship for the upcoming submission / IMAP / JMAP listeners and for any operator-side tooling that needs to parse SMTP bytes (proxies, log analyzers, test fixtures) without booting a full server."
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"title": "`b.guardSmtpCommand.detectBodySmuggling(buf)`",
|
|
399
|
+
"body": "Wire-protocol security gate for CVE-2023-51764 / CVE-2023-51765 / CVE-2023-51766 / CVE-2024-32178 bare-LF dot-terminator detection. The DATA body's `\\r\\n.\\r\\n` terminator is matched on canonical CRLF only; bare-LF dot-terminators are detected and refused with 554 5.7.0."
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"title": "`b.mail.server.mx` — inbound SMTP / MX listener",
|
|
403
|
+
"body": "Composes the existing mail-gate substrates into one operator-facing inbound listener. Defenses baked in: open-relay default-deny via `localDomains` allowlist (RCPT TO non-local refused with 550 5.7.1 unless `relayAllowedFor: [{ cidr, scope }]` opts the destination in explicitly); STARTTLS-injection defense (CVE-2021-38371 Exim, CVE-2021-33515 Dovecot — command buffer + body collector cleared at upgrade time so pre-handshake pipelined commands can't take effect post-handshake); TLS posture (`tlsContext` is required — no implicit plaintext-only mode); resource exhaustion (per-command line cap default 1 KiB, DATA body cap default 50 MiB per RFC 5321 §4.5.3.1.7, per-recipient cap default 100 per RFC 5321 §4.5.3.1.8, idle timeout default 5 minutes per RFC 5321 §4.5.3.2.7). RFC 1870 §3 SIZE param parsed at MAIL FROM time + refused with 552 5.3.4 if oversize. RFC 2920 PIPELINING + RFC 6152 8BITMIME + RFC 2034 ENHANCEDSTATUSCODES advertised in EHLO capabilities. RFC 3463 enhanced status codes embedded in every reply. RFC 6531 SMTPUTF8 / RFC 5891 IDN deliberately NOT advertised until the operator's downstream accepts Unicode mailbox-local-part bytes."
|
|
404
|
+
}
|
|
405
|
+
]
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"heading": "Security",
|
|
409
|
+
"items": [
|
|
410
|
+
{
|
|
411
|
+
"title": "Audit lifecycle",
|
|
412
|
+
"body": "`mail.server.mx.{connect,helo,mail_from,rcpt_to,data_accepted,data_refused,delivered,tls_handshake_failed,smtp_smuggling_detected,relay_refused,listening,closed,handler_threw,socket_error}`. What v1 does NOT ship: AUTH / submission auth (port-587 listener is its own slice), Sieve filtering (composes via `b.mail.agent` at delivery), outbound DSN generation (deferred to submission slice), 8BITMIME / SMTPUTF8 transcoding (advertised but parser-agnostic)."
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
}
|
|
416
|
+
],
|
|
417
|
+
"references": [
|
|
418
|
+
{
|
|
419
|
+
"label": "RFC 5321 (SMTP)",
|
|
420
|
+
"url": "https://www.rfc-editor.org/rfc/rfc5321"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
"label": "CVE-2023-51764 (Postfix SMTP smuggling)",
|
|
424
|
+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-51764"
|
|
425
|
+
}
|
|
426
|
+
]
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
"version": "0.9.45",
|
|
430
|
+
"date": "2026-05-15",
|
|
431
|
+
"headline": "`b.crypto.toBase64Url` / `fromBase64Url` helpers + lib-wide `.replace(/X+$/, ...)` ReDoS-shape sweep",
|
|
432
|
+
"summary": "The trailing-greedy regex base64url-by-hand pattern was duplicated across nine framework call sites. The trailing `/=+$/` regex is polynomial-ReDoS-shaped per CodeQL `js/polynomial-redos`. Both directions now route through linear-time helpers.",
|
|
433
|
+
"sections": [
|
|
434
|
+
{
|
|
435
|
+
"heading": "Added",
|
|
436
|
+
"items": [
|
|
437
|
+
{
|
|
438
|
+
"title": "`b.crypto.toBase64Url(buf)` / `fromBase64Url(s)`",
|
|
439
|
+
"body": "Buffer / Uint8Array / string to RFC 4648 §5 base64url string via Node's built-in `\"base64url\"` encoding (linear time, no regex backtracking surface), and the inverse decoder."
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
"heading": "Changed",
|
|
445
|
+
"items": [
|
|
446
|
+
{
|
|
447
|
+
"title": "Nine-site sweep across JWT / DPoP / OAuth / SD-JWT VC / DoH / GCS service-account JWT / pagination cursors",
|
|
448
|
+
"body": "Every site now consumes the helpers; the symmetric `_b64urlDecode` 5-site sweep follows the same shape (one validated typed-error guard then `bCrypto.fromBase64Url`). `lib/argon2-builtin.js` retains its own `_b64NoPad` helper (PHC strings use standard base64 alphabet `+/` not url-safe `-_`); converted from `.replace(/=+$/, \"\")` to a linear `charCodeAt`+`slice` loop."
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"heading": "Detectors",
|
|
454
|
+
"items": [
|
|
455
|
+
{
|
|
456
|
+
"title": "`inline-base64url-three-replace` + `mountinfo-options-bind-check`",
|
|
457
|
+
"body": "Any future site that reaches for either pattern trips the gate at n=1. KNOWN_CLUSTERS entry added for the JWT-family verification cluster (`dpop.verify` / `jwt._requireNumericDate` / `oauth.verifyBackchannelLogoutToken`) that surfaced after the redos sweep shifted line offsets; structurally distinct RFC primitives (RFC 9449 DPoP / RFC 7519 JWT / OIDC Back-Channel Logout) sharing a replayStore.checkAndInsert + numeric-date-bound shingle."
|
|
458
|
+
}
|
|
459
|
+
]
|
|
460
|
+
}
|
|
461
|
+
],
|
|
462
|
+
"references": [
|
|
463
|
+
{
|
|
464
|
+
"label": "RFC 4648 §5 (base64url)",
|
|
465
|
+
"url": "https://www.rfc-editor.org/rfc/rfc4648#section-5"
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
"label": "CodeQL js/polynomial-redos",
|
|
469
|
+
"url": "https://codeql.github.com/codeql-query-help/javascript/js-polynomial-redos/"
|
|
470
|
+
}
|
|
471
|
+
]
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"version": "0.9.44",
|
|
475
|
+
"date": "2026-05-15",
|
|
476
|
+
"headline": "`b.storage.chunkScratch` resumable-chunked-upload primitive + `b.agent.tenant` cryptoField adoption helper",
|
|
477
|
+
"summary": "Two operator-deployment primitives that close gaps every consumer was reinventing.",
|
|
478
|
+
"sections": [
|
|
479
|
+
{
|
|
480
|
+
"heading": "Added",
|
|
481
|
+
"items": [
|
|
482
|
+
{
|
|
483
|
+
"title": "`b.storage.chunkScratch(opts?)` — resumable chunked upload primitive",
|
|
484
|
+
"body": "Operators handling large file uploads (multipart-form / tus / S3-multipart-style flows) have reinvented the per-assembly directory layout + atomic finalize + GC of partial assemblies pattern. This primitive owns it once. Returns a handle with 10 lifecycle methods: `saveChunk` (per-chunk `maxChunkBytes` cap default 16 MiB; envelope-encrypted via the framework vault), `getChunk`, `chunkExists`, `listChunks` (sorted indices), `countChunks`, `removeChunk`, `assemble` (monotonic 0..N-1 index check; refuses on gap or expectedTotal mismatch), `removeAssembly`, `listAssemblies`, `listStaleAssemblies({ olderThanMs })` + `gc({ olderThanMs })` for partial uploads abandoned mid-stream (default stale window 24h). `assemblyId` shape is validated to refuse path-traversal (`..`), slash / backslash, NUL / C0 / DEL, dot-prefix, and oversize (>128 bytes). Backend is the operator-configured `b.storage` backend (no new backend concept). Audit events: `system.storage.chunk_scratch.chunk_saved` / `assembled` / `removed` / `gc`. Wire-protocol reference: tus.io v1.0.0, RFC 9110 §14.4 Content-Range, draft-ietf-httpbis-resumable-upload-08. Threat-model: CVE-2018-1000656-class path-traversal in upload paths defended via the assemblyId validator; storage exhaustion from abandoned uploads defended via the GC primitive; chunk-out-of-order replay defended via `assemble`'s monotonic index check."
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
"title": "`b.agent.tenant` cryptoField adoption helper",
|
|
488
|
+
"body": "`sealField(tenantId, table, field, plaintext)` / `unsealField(...)` / `sealRowForTenant(tenantId, table, row)` / `unsealRowForTenant(tenantId, table, row)`. `b.cryptoField.sealRow` uses the singleton vault keypair — every tenant's sealed data decrypts under the same framework key, which fails the cross-tenant cryptographic isolation that HIPAA §164.312(a)(2)(iv) Encryption-at-rest, GDPR data-residency-per-tenant, and PCI scope-isolation deployments require. The adoption helper derives a per-tenant 32-byte AEAD key via `b.crypto.namespaceHash(\"agent.tenant.derive.cryptoField:<table>\", tenantId)` (NIST SP 800-108 r1 §5.1 KDF-in-Counter-mode shape using SHA3-512) and routes each sealed field through `b.crypto.encryptPacked` (XChaCha20-Poly1305 per draft-irtf-cfrg-xchacha-03; 24-byte nonce making random-nonce generation safe at framework scale) with AAD-bound context (`tenantId|table|field`) per RFC 8439 §2.5 so a ciphertext from tenant A literally cannot decrypt as tenant B's row — even with the wrong tenantId the Poly1305 tag check fails. Threat-model coverage: cross-tenant data exposure class (CVE-2019-19528 was an early multi-tenant example where shared encryption keys allowed cross-tenant decrypt with DB access; this primitive's AAD-binding + per-tenant key derivation defends the class by construction). Ciphertext shape: `\"tnt-v1:\" + base64(packed)`, distinguishable from `vault.seal`-sealed cells (which start with `\"vault:\"`) so a storage layer can mix both. `sealRowForTenant` adopts the existing `b.cryptoField` table schema (`sealedFields`); cross-tenant decrypt safe-fails the affected field to `null` (matching `b.cryptoField.unsealRow`'s posture)."
|
|
489
|
+
}
|
|
490
|
+
]
|
|
491
|
+
}
|
|
492
|
+
]
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"version": "0.9.43",
|
|
496
|
+
"date": "2026-05-15",
|
|
497
|
+
"headline": "`b.testHarness.start` + `b.middleware.composePipeline` + `b.watcher` `mode: \"auto\"`",
|
|
498
|
+
"summary": "Three downstream-consumer DX primitives that close operator-friction gaps.",
|
|
499
|
+
"sections": [
|
|
500
|
+
{
|
|
501
|
+
"heading": "Added",
|
|
502
|
+
"items": [
|
|
503
|
+
{
|
|
504
|
+
"title": "`b.testHarness.start(opts?)` — isolated-boot helper",
|
|
505
|
+
"body": "Collapses the ~20-line mkdtemp + env-var setup + vault.init + teardown pattern every consumer was reinventing in `tests/helpers/`. Returns a handle exposing `{ dataDir, dbPath, vaultDir, env, stop() }`. Generates a mkdtemp-based isolated dataDir under `os.tmpdir()` with `b.crypto.generateToken(4)` random suffix, sets `<prefix>_DATA_DIR` / `_DB_PATH` / `_VAULT_DIR` env vars, optionally awaits `b.vault.init` in plaintext mode. Concurrent harnesses with `initVault: true` share the process-global vault state via internal reference counting; the last `stop()` releases vault."
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
"title": "`b.middleware.composePipeline(entries, opts?)` — order-aware middleware composer",
|
|
509
|
+
"body": "Canonical-position registry for 14 framework middlewares (`requestId=5` / `apiEncrypt=10` / `bodyParser=20` / `cspNonce=22` / `securityHeaders=25` / `csrf=30` / `idempotency=30` / `fetchMetadata=32` / `rateLimit=40` / `botGuard=42` / `requireAuth=50` / `attachUser=52` / `handler=60` / `errorHandler=90`). Conflict detection at registration time refuses duplicate names, duplicate explicit-position values, and non-monotonic positions. Strict mode (`opts.strict: true`) refuses canonical-name position mismatches; default `false` runs but emits `system.middleware.compose.canonical_mismatch` audit. Sync throws inside middleware propagate to `finalNext`. Boot-time `system.middleware.compose.pipeline_built` audit lists final ordered entries."
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
"title": "`b.watcher.create({ root, mode: \"auto\", ... })` — Docker bind-mount fallback",
|
|
513
|
+
"body": "Inside a Linux container with a host bind-mount, `fs.watch` returns no events across gRPC-FUSE / VirtioFS / 9p / NFS / CIFS / vboxsf boundaries; `mode: \"auto\"` reads `/proc/self/mountinfo`, finds the mount carrying the watcher root, and falls back to `mode: \"poll\"` when the fstype is non-inotify OR when `/.dockerenv` is present AND mountinfo field 4 (root within source filesystem) is `!= \"/\"` (bind-mount signature — the kernel exposes the bound source path in this field; regular mounts always carry `/`). Native Linux mounts + non-Linux hosts (FSEvents / ReadDirectoryChangesW) keep `mode: \"fs\"`. The chosen backend + reason emits as `watcher.mode_auto_decision` on the audit chain. `mode: \"fs\"` (default) and `mode: \"poll\"` (explicit) unchanged; `mode: \"auto\"` is opt-in."
|
|
514
|
+
}
|
|
515
|
+
]
|
|
516
|
+
}
|
|
517
|
+
]
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
"version": "0.9.42",
|
|
521
|
+
"date": "2026-05-15",
|
|
522
|
+
"headline": "`b.middleware.idempotencyKey` `bodyFingerprint` hook + misordered-mount detector",
|
|
523
|
+
"summary": "Adds an operator-supplied body-extractor hook for the idempotency middleware so canonicalized payloads don't trip the same-key-different-body refusal, plus audit signals when the middleware is mounted before the body parser.",
|
|
524
|
+
"sections": [
|
|
525
|
+
{
|
|
526
|
+
"heading": "Added",
|
|
527
|
+
"items": [
|
|
528
|
+
{
|
|
529
|
+
"title": "`opts.bodyFingerprint: (req) => Buffer|string|object|null`",
|
|
530
|
+
"body": "Lets operators supply a custom body extractor instead of relying on the default `req._rawBody || req.body` lookup; useful when the parsed-body shape needs canonicalization (sorted keys, stripped metadata) before the fingerprint hash so retry-with-equivalent-payload doesn't trip the §4.3 same-key-different-body refusal. Hook return is normalized to Buffer (Buffer passthrough; string to UTF-8 bytes; object/array via `JSON.stringify` to bytes; null/undefined to empty fingerprint). Throws inside the hook emit `idempotency.body_fingerprint_failed` audit (warning) and treat the body as empty."
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
"title": "Mount-order constraint and audit signal",
|
|
534
|
+
"body": "Idempotency must run AFTER body-parser; the hook reads request state at the moment idempotency executes, so a misordered mount silently degrades the fingerprint to method+path. `b.middleware.composePipeline` places bodyParser=20 / idempotency=30 by default. Body-bearing methods (POST/PUT/PATCH) that arrive without parsed-body OR raw-body data now emit `idempotency.empty_body_fingerprint` audit (warning) carrying `hasRawBody` / `hasParsedBody` / `hasFingerprintHook` so a misconfigured pipeline is detectable from audit logs."
|
|
535
|
+
}
|
|
536
|
+
]
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
"version": "0.9.41",
|
|
542
|
+
"date": "2026-05-15",
|
|
543
|
+
"headline": "Operator-friction ergonomic helpers — `b.storage.listBackends` rootDir + `b.problemDetails.send` shortcut",
|
|
544
|
+
"summary": "Three small additive surfaces, no behavior change for existing callers.",
|
|
545
|
+
"sections": [
|
|
546
|
+
{
|
|
547
|
+
"heading": "Added",
|
|
548
|
+
"items": [
|
|
549
|
+
{
|
|
550
|
+
"title": "`b.storage.listBackends()` surfaces `rootDir` for local-protocol backends",
|
|
551
|
+
"body": "Sourced from the live backend (with config-reload propagation) so downstream path-traversal guards + scratch-dir derivation read the canonical path directly from the framework instead of re-deriving from operator-supplied opts. Remote protocols (sigv4 / gcs / azure-blob / http-put) don't carry a rootDir; the field stays absent for those."
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
"title": "`b.problemDetails.send(res, fields)` — bare wire-shape emit shortcut",
|
|
555
|
+
"body": "Lets routes migrate incrementally from inline `res.status(400).json({ error: ... })` to RFC 9457 problem-details without restructuring the handler around an error throw. Equivalent to `respond(res, create(fields))` in one call; same `application/problem+json` content type + `Cache-Control: no-store`."
|
|
556
|
+
}
|
|
557
|
+
]
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
"heading": "Fixed",
|
|
561
|
+
"items": [
|
|
562
|
+
{
|
|
563
|
+
"title": "`b.mail.send` CR/LF/NUL refusal confirmed already in place",
|
|
564
|
+
"body": "At `lib/mail.js:275` / `:309` / `:1808` per RFC 5321 §2.3.8 + RFC 5322 §3.2.5 header-injection defense — operators with inline `validateEmailAddr` wrappers can retire them. No new API, just confirmation that the existing primitive already covers the wire-protocol injection class (CVE-2026-32178 .NET System.Net.Mail header injection defended at the framework boundary)."
|
|
565
|
+
}
|
|
566
|
+
]
|
|
567
|
+
}
|
|
568
|
+
],
|
|
569
|
+
"references": [
|
|
570
|
+
{
|
|
571
|
+
"label": "RFC 9457 (Problem Details for HTTP APIs)",
|
|
572
|
+
"url": "https://www.rfc-editor.org/rfc/rfc9457"
|
|
573
|
+
}
|
|
574
|
+
]
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
"version": "0.9.40",
|
|
578
|
+
"date": "2026-05-15",
|
|
579
|
+
"headline": "`b.guardListId` — RFC 2919 List-Id header validator",
|
|
580
|
+
"summary": "Companion to the List-Unsubscribe validator; gates outbound mailing-list mail so the List-Id carries a well-formed identifier downstream filters + bulk-sender pipelines reliably route on.",
|
|
581
|
+
"sections": [
|
|
582
|
+
{
|
|
583
|
+
"heading": "Added",
|
|
584
|
+
"items": [
|
|
585
|
+
{
|
|
586
|
+
"title": "`b.guardListId.validate(headerValue, opts?)`",
|
|
587
|
+
"body": "Parses bracketed (`<my-list.example.com>`), phrase-prefixed (`My Newsletter <my-list.example.com>`), and bare-identifier forms per RFC 2919 §2. Returns `{ action, listId, label, namespace, phrase, reason }`. Action one of `accept` / `refuse`."
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
"title": "RFC 2919 §3 caps + ABNF",
|
|
591
|
+
"body": "list-id capped at 255 octets; header value capped at RFC 5322 §2.1.1 line cap (998 bytes); per-label shape per RFC 5322 §3.2.3 dot-atom-text."
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
"title": "Phrase-smuggling defense + CRLF/NUL/C0/DEL refusal",
|
|
595
|
+
"body": "Phrase MUST NOT contain `<` / `>` (would smuggle a second bracketed identifier through the parser). Trailing content after `>` refused. Nested or unmatched brackets refused. Header-injection defense per RFC 5322 §3.2.5 + CVE-2026-32178 wire-protocol surface class."
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
"title": "`localhost` namespace + FQDN enforcement",
|
|
599
|
+
"body": "Strict requires the recommended 32-hex random component in the `localhost` namespace label (the RFC's SHOULD becomes operator-strict for HIPAA / PCI / GDPR / SOC2 postures); balanced / permissive accept without. FQDN namespace enforced under strict / balanced — list-id with single-label namespace refused unless permissive. Heuristic label / namespace split — last 2 dot-segments become namespace; consumers needing PSL-accurate org-domain extraction compose `b.publicSuffix.organizationalDomain`. Three profiles + posture cascade (`hipaa` / `pci-dss` / `gdpr` / `soc2` map to strict). Fuzz harness ships in `fuzz/guard-list-id.fuzz.js`. Registered as standalone guard with `KIND=\"list-id\"`. Threat-model: List-Id forging (RFC 2919 §8 explicitly notes the identifier is NOT an authentication signal; operators wanting authentication compose `b.mail.auth.dmarc` / `arc.verify`), bulk-sender bucket-drop (Gmail 2024 keys on List-Id presence for Precedence: list / 5000+ daily-send mail)."
|
|
600
|
+
}
|
|
601
|
+
]
|
|
602
|
+
}
|
|
603
|
+
],
|
|
604
|
+
"references": [
|
|
605
|
+
{
|
|
606
|
+
"label": "RFC 2919 (List-Id)",
|
|
607
|
+
"url": "https://www.rfc-editor.org/rfc/rfc2919"
|
|
608
|
+
}
|
|
609
|
+
]
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
"version": "0.9.39",
|
|
613
|
+
"date": "2026-05-15",
|
|
614
|
+
"headline": "`b.guardListUnsubscribe` — RFC 2369 + RFC 8058 List-Unsubscribe / List-Unsubscribe-Post validator",
|
|
615
|
+
"summary": "Gates the outbound submission path so messages carrying a List-Id (or any mailing-list shape) emit headers Gmail / Yahoo / Outlook one-click unsubscribe machinery actually accepts.",
|
|
616
|
+
"sections": [
|
|
617
|
+
{
|
|
618
|
+
"heading": "Added",
|
|
619
|
+
"items": [
|
|
620
|
+
{
|
|
621
|
+
"title": "`b.guardListUnsubscribe.validate({ listUnsubscribe, listUnsubscribePost }, opts?)`",
|
|
622
|
+
"body": "Returns `{ action, reason, uris, hasHttpsUri, hasMailtoUri, postHeaderOk, oneClickReady }`."
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
"title": "Gmail / Yahoo bulk-sender 2024 enforcement",
|
|
626
|
+
"body": "Under strict requires at least one `https://` URI in the header (mailto: alone refused) + the paired `List-Unsubscribe-Post: List-Unsubscribe=One-Click` value EXACTLY (case-sensitive — Gmail silently fails one-click on mixed-case variants)."
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
"title": "Always-refused schemes",
|
|
630
|
+
"body": "`javascript:` / `data:` / `file:` / `vbscript:` / `blob:` refused regardless of profile (XSS / file-read class in mail-client rendering). `http://` refused under strict / balanced — one-click endpoint MUST be TLS per RFC 8058 §2. Permissive accepts http for audit-only legacy use."
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
"title": "Header-injection defense + bounded surface",
|
|
634
|
+
"body": "CRLF, NUL, C0 controls, DEL refused at validate time (RFC 5322 §3.2.5). Per-URI byte cap (2 KiB strict / 4 KiB permissive), URI-count cap (4 / 8 / 16), header total byte cap (4 / 4 / 8 KiB). RFC 3986 §3.1 scheme shape; RFC 2369 §3.1 angle-bracket URI list. HTTPS URIs validated through `b.safeUrl.parse` with the framework's HTTPS allowlist. Three profiles + posture cascade (`hipaa` / `pci-dss` / `gdpr` / `soc2` map to strict). Fuzz harness ships in `fuzz/guard-list-unsubscribe.fuzz.js`. Registered as a standalone guard with KIND=\"list-unsubscribe\". Threat-model coverage: unsubscribe-link injection via machine-generated newsletter templates, open-redirect via List-Unsubscribe (operator validates target host downstream via own safeRedirect allowlist)."
|
|
635
|
+
}
|
|
636
|
+
]
|
|
637
|
+
}
|
|
638
|
+
],
|
|
639
|
+
"references": [
|
|
640
|
+
{
|
|
641
|
+
"label": "RFC 2369 (List-Unsubscribe header)",
|
|
642
|
+
"url": "https://www.rfc-editor.org/rfc/rfc2369"
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
"label": "RFC 8058 (One-Click)",
|
|
646
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8058"
|
|
647
|
+
}
|
|
648
|
+
]
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
"version": "0.9.38",
|
|
652
|
+
"date": "2026-05-15",
|
|
653
|
+
"headline": "Republish — prefix npm tarball path with `./` so npm doesn't mis-classify it as a git spec",
|
|
654
|
+
"summary": "Two prior publish workflow runs both failed at exit 128 — npm 10+ interprets a relative tarball path containing `/` (`dist/blamejs-core-0.9.X.tgz`) as a git spec and attempts `git ls-remote ssh://git@github.com/dist/...tgz`, which the runner's SSH credentials can't auth against. v0.9.29 through v0.9.37 never reached npm as a result; v0.9.28 remained the latest published version.",
|
|
655
|
+
"sections": [
|
|
656
|
+
{
|
|
657
|
+
"heading": "Fixed",
|
|
658
|
+
"items": [
|
|
659
|
+
{
|
|
660
|
+
"title": "`./` prefix on the tarball path in `npm-publish.yml`",
|
|
661
|
+
"body": "No operator-facing primitive change vs the previous release — operators upgrading from v0.9.28 see the full bundled surface delivered by the in-between releases: agent.trace + agent.snapshot, safeDns + network.dns.resolver, guardSmtpCommand, mail.rbl, mail.greylist + lib/ip-utils, mail.helo, guardEnvelope, guardDsn."
|
|
662
|
+
}
|
|
663
|
+
]
|
|
664
|
+
}
|
|
665
|
+
]
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
"version": "0.9.37",
|
|
669
|
+
"date": "2026-05-15",
|
|
670
|
+
"headline": "`b.guardDsn` — RFC 3464 Delivery Status Notification parser",
|
|
671
|
+
"summary": "Reads the `message/delivery-status` MIME-part body bounces / delayed-delivery notices / successful-delivery confirmations carry and surfaces the per-recipient action + RFC 3463 enhanced status code so operator-side delivery-failure routing reads one shape regardless of MTA wording.",
|
|
672
|
+
"sections": [
|
|
673
|
+
{
|
|
674
|
+
"heading": "Added",
|
|
675
|
+
"items": [
|
|
676
|
+
{
|
|
677
|
+
"title": "`b.guardDsn.parse(deliveryStatusBody, opts?)`",
|
|
678
|
+
"body": "Returns `{ perMessage: { reportingMta, originalEnvelopeId?, arrivalDate?, receivedFromMta? }, perRecipients: [{ finalRecipient, action, status, statusClass, diagnosticCode? }, ...], worstStatusClass, action }`."
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
"title": "RFC 3464 mandatory-field enforcement",
|
|
682
|
+
"body": "Reporting-MTA required per §2.2.2; per-recipient Final-Recipient (§2.3.2), Action (§2.3.3) from `{ failed | delayed | delivered | relayed | expanded }` vocabulary, and Status (§2.3.4) in RFC 3463 `D.D.D` form all required. Missing-field surfaces as a typed error (`guard-dsn/missing-{reporting-mta|final-recipient|action|status}`)."
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
"title": "RFC 3463 status-class verdict — `2.x.y` deliver / `4.x.y` retry / `5.x.y` invalidate",
|
|
686
|
+
"body": "First digit drives routing. Worst class across recipients wins so a single permanent failure in a multi-recipient bounce flips `action: invalidate`."
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
"title": "Defenses — bounded body cap + recipient cap + header-line cap + CRLF/NUL/C0/DEL refusal",
|
|
690
|
+
"body": "Bounded body cap (256 KiB strict / 1 MiB balanced / 4 MiB permissive), per-DSN recipient cap (256 / 1024 / 4096), RFC 5322 §2.1.1 header-line cap (998 bytes), CRLF / NUL / C0 / DEL refusal for header-injection defense (CVE-2026-32178 .NET System.Net.Mail class on the inbound parse path). RFC 5322 §2.2 continuation lines: values can wrap onto subsequent lines starting with whitespace; parser folds correctly. `rfc822;` address-type prefix stripped per RFC 3464 §2.3.2 so consumers see canonical mailbox form. Three profiles + posture cascade (`hipaa` / `pci-dss` / `gdpr` / `soc2` map to strict). Fuzz harness ships in `fuzz/guard-dsn.fuzz.js`."
|
|
691
|
+
}
|
|
692
|
+
]
|
|
693
|
+
}
|
|
694
|
+
],
|
|
695
|
+
"references": [
|
|
696
|
+
{
|
|
697
|
+
"label": "RFC 3464 (DSN format)",
|
|
698
|
+
"url": "https://www.rfc-editor.org/rfc/rfc3464"
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
"label": "RFC 3463 (enhanced status codes)",
|
|
702
|
+
"url": "https://www.rfc-editor.org/rfc/rfc3463"
|
|
703
|
+
}
|
|
704
|
+
]
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
"version": "0.9.36",
|
|
708
|
+
"date": "2026-05-15",
|
|
709
|
+
"headline": "`b.guardEnvelope` — RFC 7489 §3.1 DMARC Identifier Alignment validator",
|
|
710
|
+
"summary": "Focused gate exposing the From-header-vs-SPF/DKIM alignment primitive so the MX listener can short-circuit on alignment fail before the full DMARC TXT lookup, and operator middleware can reuse alignment without dragging in the full DMARC orchestrator.",
|
|
711
|
+
"sections": [
|
|
712
|
+
{
|
|
713
|
+
"heading": "Added",
|
|
714
|
+
"items": [
|
|
715
|
+
{
|
|
716
|
+
"title": "`b.guardEnvelope.check(ctx, opts?)`",
|
|
717
|
+
"body": "ctx carries `fromHeaderDomain` + `spfResult: { result, domain }` + `dkimResults: [{ result, signingDomain }]`. Returns `{ spf, dkim, aligned, action }` — `aligned: true` when at least one of SPF/DKIM is identifier-aligned (RFC 7489 §3.1: From-domain matches SPF MailFrom OR DKIM d= under chosen mode)."
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
"title": "Strict vs relaxed match (RFC 7489 §3.1.1 / §3.1.2)",
|
|
721
|
+
"body": "Strict requires exact FQDN match, relaxed (RFC 7489 §6.2 default) requires same organizational domain via `b.publicSuffix.organizationalDomain` (Public Suffix List). Per-call override via `spfMode: strict | relaxed` + `dkimMode`."
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
"title": "Verdict shape — per-signature visibility",
|
|
725
|
+
"body": "`spf: { aligned, mode, domain, fromDomain, spfPass }`, `dkim: [<verdict>...]` (one per signature so multi-signer messages with mixed pass/fail are visible), `aligned: bool` (any-of), `action: accept | refuse`. Strict + balanced profiles refuse on alignment fail; permissive computes alignment but always accepts (operator score-tags downstream)."
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
"title": "Threat-model — envelope-vs-header spoofing + public-suffix confusion",
|
|
729
|
+
"body": "`MAIL FROM:<service@aws-bounces.com>` passes SPF for aws-bounces.com but `From: payments@your-bank.example` — refused under strict. Attacker can't claim `co.uk` as an org domain because PSL classifies it as a public suffix; `victim.co.uk` vs `attacker.co.uk` have different effective org domains. Same-org-different-subdomain attack under strict mode (operator opts to relaxed for legitimate cross-subdomain mail). Posture cascades (`hipaa` / `pci-dss` / `gdpr` / `soc2` map to strict). Fuzz harness ships in `fuzz/guard-envelope.fuzz.js`. Registered as a standalone guard via `KIND: \"envelope-alignment\"` and NAME: \"envelope\"."
|
|
730
|
+
}
|
|
731
|
+
]
|
|
732
|
+
}
|
|
733
|
+
],
|
|
734
|
+
"references": [
|
|
735
|
+
{
|
|
736
|
+
"label": "RFC 7489 (DMARC)",
|
|
737
|
+
"url": "https://www.rfc-editor.org/rfc/rfc7489"
|
|
738
|
+
}
|
|
739
|
+
]
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
"version": "0.9.35",
|
|
743
|
+
"date": "2026-05-15",
|
|
744
|
+
"headline": "`b.mail.helo` — RFC 5321 §4.1.1.1 HELO/EHLO validation + RFC 8601 §2.7.6 FCrDNS verifier",
|
|
745
|
+
"summary": "Substrate for the upcoming MX listener's EHLO boundary. Composes the validating DNS resolver for forward-confirmed reverse-DNS.",
|
|
746
|
+
"sections": [
|
|
747
|
+
{
|
|
748
|
+
"heading": "Added",
|
|
749
|
+
"items": [
|
|
750
|
+
{
|
|
751
|
+
"title": "`b.mail.helo.evaluate({ ip, claimedName, resolver }, opts?)`",
|
|
752
|
+
"body": "Returns `{ action, shape, fcrdns, genericRdns, reason }`. Action one of `accept` / `reject-shape` / `soft-fail-fcrdns` / `match-self-refused` / `literal-mismatch`."
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
"title": "Shape gate — RFC 5321 §2.3.5 LDH labels",
|
|
756
|
+
"body": "FQDN required under strict (operator opts down to permissive); bare host refused; localhost / localhost.localdomain refused per RFC 6761 §6.3; oversize past RFC 1035 §2.3.4 cap refused."
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
"title": "Address-literal handling (RFC 5321 §4.1.3)",
|
|
760
|
+
"body": "`[1.2.3.4]` IPv4 and `[IPv6:2001:db8::1]` IPv6 accepted when matches the connection IP, refused otherwise. IPv6 compare uses `ipUtils.expandIpv6Hex` canonical form so a literal in expanded notation matches a compressed connection-IP representation."
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
"title": "FCrDNS check (RFC 8601 §2.7.6 / RFC 1912 §2.1)",
|
|
764
|
+
"body": "When resolver supplied, queries PTR for connection IP (in-addr.arpa for IPv4, ip6.arpa for IPv6), then A/AAAA for each PTR result; verdict requires at least one forward IP equals the connection IP. Verdict surfaces `fcrdns: { rdnsNames, forwardIps, matchedIp }` so operator audit pipelines see exactly why FCrDNS passed/failed."
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
"title": "Generic-rDNS heuristic + HELO-self spoofing defense",
|
|
768
|
+
"body": "Ships pattern list for consumer-ISP dynamic-pool naming (`dynamic`, `dial-?up`, `dsl`, `cable`, `pool[-_]`, `ppp[0-9]`, `broadband`, IPv4-in-name). Operator extends per-deployment via `genericRdnsPatterns: [<regex>...]`. Verdict flags `genericRdns: true` for MX policy to layer with RBL + greylist. Operator-supplied `selfNames: [...]` list refuses peers claiming our own MX hostnames (`match-self-refused`)."
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
"title": "Per-profile FCrDNS enforcement",
|
|
772
|
+
"body": "`strict` requires FCrDNS for both v4 + v6, `balanced` requires v4 only (consumer IPv6 PTR records are spotty across ISPs and over-rejecting hurts legitimate cloud / VPS senders), `permissive` skips FCrDNS entirely. Posture cascades (`hipaa` / `pci-dss` / `gdpr` / `soc2` map to strict). Threat-model: HELO spoofing (selfNames defense), botnet residential-IP class (generic-rDNS + RBL composition), DNS poisoning of PTR (inherits resolver's safeDns caps + AD-bit surface + CVE coverage)."
|
|
773
|
+
}
|
|
774
|
+
]
|
|
775
|
+
}
|
|
776
|
+
]
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
"version": "0.9.34",
|
|
780
|
+
"date": "2026-05-15",
|
|
781
|
+
"headline": "`b.mail.greylist` — RFC 6647 SMTP greylisting + IPv6-expansion helper extracted to `lib/ip-utils.js`",
|
|
782
|
+
"summary": "Greylisting primitive that the MX listener composes, with GDPR-grade triplet hashing and a shared IPv6-expansion helper extracted from three inlined copies.",
|
|
783
|
+
"sections": [
|
|
784
|
+
{
|
|
785
|
+
"heading": "Added",
|
|
786
|
+
"items": [
|
|
787
|
+
{
|
|
788
|
+
"title": "`b.mail.greylist.create({ profile, posture, store, minDelayMs, whitelistTtlMs, maxEntries, allowedSources, audit })`",
|
|
789
|
+
"body": "Facade with `.check({ ip, mailFrom, rcptTo, now? })` returning a verdict (`defer` / `accept` / `accept-first-pass`) and `.gc({ olderThanMs })` for retention. RFC 6647 §4.4 recommended triplet (IP + RFC 5321 MailFrom + first RcptTo) with CIDR aggregation per profile (default /24 IPv4, /64 IPv6 — matches retry-cluster behavior of Gmail / Outlook / AWS SES). RFC 6647 §4.5 retry window (default minDelayMs = 5 min) + whitelist TTL (default 36 days). Operator returns SMTP `451 4.7.1` on defer; legitimate MTAs retry and pass through."
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
"title": "Privacy by construction — namespace-hashed triplet",
|
|
793
|
+
"body": "GDPR Art. 5(1)(c) data-minimization: triplet is namespace-hashed (`b.crypto.namespaceHash(\"mail.greylist\", ...)` sha3-512) so the on-disk key is unlinkable to the PII triplet; operator dumps of the greylist table never leak sender/recipient pairs."
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
"title": "Pluggable backend + allowed-source bypass + whitelist-TTL re-greylisting",
|
|
797
|
+
"body": "`{ get, put, delete, gc }` interface. In-memory default for single-process MX; operator wires sqlite-backed or external DB for multi-process MX fleets so a retry landing on a different process sees the first-attempt fingerprint. `allowedSources: [ip, ...]` skips greylisting per RFC 6647 §6.2 (listservs, transactional partners, healthcare 2FA, etc.). RFC 6647 §4.5: when a previously-passed fingerprint exceeds its TTL without recent traffic, the next attempt is deferred again and a fresh fingerprint planted."
|
|
798
|
+
}
|
|
799
|
+
]
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
"heading": "Changed",
|
|
803
|
+
"items": [
|
|
804
|
+
{
|
|
805
|
+
"title": "Shared IPv6 expansion extracted to `lib/ip-utils.js`",
|
|
806
|
+
"body": "Replaces 3 inlined implementations (`lib/mail-auth.js` `_ipv6Expand`, `lib/mail-rbl.js` `_expandIpv6`, `lib/mail-greylist.js` `_expandIpv6`). The shared helper handles RFC 4291 §2.5.5.2 IPv4-in-IPv6 dual-stack form (`::ffff:1.2.3.4`) which the per-module copies were inconsistent on. mail-auth keeps an `expandIpv6Groups` view (returns 8 x uint16 for SPF / DMARC bitwise CIDR eval); mail-rbl and mail-greylist use the `expandIpv6Hex` view (returns the 32-hex-char canonical form for RFC 5782 §2.4 reverse-DNS construction and CIDR aggregation respectively). Three profiles + posture cascades (`hipaa` / `pci-dss` / `gdpr` / `soc2` map to strict). Threat-model coverage: snowshoe + single-attempt botnet flood, fingerprint-store poisoning (operator-supplied IPs hashed before storage; `maxEntries` cap with FIFO eviction), CIDR-aggregation bypass."
|
|
807
|
+
}
|
|
808
|
+
]
|
|
809
|
+
}
|
|
810
|
+
],
|
|
811
|
+
"references": [
|
|
812
|
+
{
|
|
813
|
+
"label": "RFC 6647 (SMTP greylisting)",
|
|
814
|
+
"url": "https://www.rfc-editor.org/rfc/rfc6647"
|
|
815
|
+
}
|
|
816
|
+
]
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
"version": "0.9.33",
|
|
820
|
+
"date": "2026-05-15",
|
|
821
|
+
"headline": "`b.mail.rbl` — RFC 5782 DNSBL + DNSWL query primitive composing the validating resolver",
|
|
822
|
+
"summary": "Substrate for the upcoming MX listener's IP-reputation check on every accepted connection + the submission listener's outbound-rate / spam-source list.",
|
|
823
|
+
"sections": [
|
|
824
|
+
{
|
|
825
|
+
"heading": "Added",
|
|
826
|
+
"items": [
|
|
827
|
+
{
|
|
828
|
+
"title": "`b.mail.rbl.create({ resolver, blocklists, allowlists, profile, posture, withReason, audit })`",
|
|
829
|
+
"body": "Facade over `b.network.dns.resolver`. Surface: `instance.query(ip, qopts)` for IP-based lookups, `instance.queryDomain(domain, qopts)` for domain blocklists (Spamhaus DBL / SURBL pattern). Returns `{ listed, allowed, neutral, errors }` so MX policy code reads one shape regardless of which list fired."
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
"title": "`b.mail.rbl.reverseIp(ip)` — pure helper for the RFC 5782 reverse-DNS construction",
|
|
833
|
+
"body": "IPv4 octets reversed (§2.1, `192.0.2.99` becomes `99.2.0.192`), IPv6 nibble-reversed across all 128 bits (§2.4). Handles compressed `::` notation per RFC 4291 §2.2."
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
"title": "A-record return surface + TXT-reason lazy fetch + NXDOMAIN-means-not-listed",
|
|
837
|
+
"body": "Exposes raw `127.0.0.x` semantic byte so operator MX policy can inspect sub-list codes (Spamhaus convention: `.2` SBL, `.4` XBL, etc.). RFC 5782 §2.1 explicitly forbids treating these as routable addresses. Opt-in TXT-reason fetch via `{ withReason: true }` so operators can render the listing reason back to the peer via SMTP 550 message. NXDOMAIN is treated as the neutral verdict per RFC 5782 §2.1.1, not an error."
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
"title": "DoS-by-list defense — per-list concurrency cap + timeout",
|
|
841
|
+
"body": "Default 8 concurrent in strict; per-list timeout default 5s strict / 10s balanced / 20s permissive — a slow upstream list can't stall the MX listener. Three profiles + posture cascades (`hipaa` / `pci-dss` / `gdpr` / `soc2` map to strict). Threat-model inherits the resolver's CVE coverage (CVE-2008-1447 Kaminsky, CVE-2022-3204 NRDelegationAttack, CVE-2023-50387 / CVE-2023-50868 KeyTrap + NSEC3-encloser, CVE-2024-1737 BIND9 large-RRset)."
|
|
842
|
+
}
|
|
843
|
+
]
|
|
844
|
+
}
|
|
845
|
+
],
|
|
846
|
+
"references": [
|
|
847
|
+
{
|
|
848
|
+
"label": "RFC 5782 (DNSBL / DNSWL)",
|
|
849
|
+
"url": "https://www.rfc-editor.org/rfc/rfc5782"
|
|
850
|
+
}
|
|
851
|
+
]
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
"version": "0.9.32",
|
|
855
|
+
"date": "2026-05-15",
|
|
856
|
+
"headline": "`b.guardSmtpCommand` — SMTP command-line validator with bare-CR / bare-LF smuggling defense + per-verb shape + RFC 5321 caps",
|
|
857
|
+
"summary": "Gates every SMTP command verb the framework's MX and submission listeners will accept. Refuses bare CR / bare LF anywhere in a command per RFC 5321 §2.3.8 — defends the 2023/2024 SMTP-smuggling CVE family at the wire-protocol surface.",
|
|
858
|
+
"sections": [
|
|
859
|
+
{
|
|
860
|
+
"heading": "Added",
|
|
861
|
+
"items": [
|
|
862
|
+
{
|
|
863
|
+
"title": "Smuggling defense at the command-line level",
|
|
864
|
+
"body": "Refuses bare `\\r` or `\\n` anywhere in a command (RFC 5321 §2.3.8); defends CVE-2023-51764 (Postfix), CVE-2023-51765 (Sendmail), CVE-2023-51766 (Exim), and CVE-2026-32178 (.NET `System.Net.Mail` SMTP header injection). Operators with peers that legitimately speak bare-LF (rare; legacy Sendmail-to-Sendmail) opt down to the `permissive` profile with audit emit per accepted bare-LF line."
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
"title": "STARTTLS command-buffer defense",
|
|
868
|
+
"body": "Refuses trailing payload on `STARTTLS` lines (defends CVE-2021-38371 Exim STARTTLS response injection and CVE-2021-33515 Dovecot lib-smtp STARTTLS command injection at the per-line shape; the stateful buffer-drain check is the listener's responsibility and lands with the MX release)."
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
"title": "Per-verb argument shape — `EHLO` / `HELO` / `MAIL FROM` / `RCPT TO` / `BDAT` / `AUTH` / zero-arg verbs",
|
|
872
|
+
"body": "RFC 5321 §3 / §4.1: `EHLO` / `HELO` require exactly one domain or address-literal arg (`[IPv6:...]` form per RFC 5321 §4.1.3); `MAIL FROM:<reverse-path>` + `RCPT TO:<forward-path>` with optional ESMTP extension params per RFC 5321 §4.1.1.11; `BDAT <chunk-size> [LAST]` per RFC 3030 CHUNKING; `AUTH <SASL-mech> [<initial-response>]` per RFC 4954 with RFC 4422 mechanism-name charset; zero-arg verbs (`DATA` / `RSET` / `QUIT` / `STARTTLS`) refused with any trailing args."
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
"title": "Byte caps + NUL/C0/DEL refusal",
|
|
876
|
+
"body": "Command line capped at 512 bytes per RFC 5321 §4.5.3.1.1 (strict); SMTPUTF8 / EAI peers (RFC 6531) routed through `balanced` profile with 1024-byte cap. Forward / reverse path 256 bytes, domain 255 bytes (RFC 1035 §2.3.4), local-part 64 bytes (RFC 5321 §4.5.3.1.1). Every byte below `0x20` and `0x7f` refused; non-ASCII refused under `strict` (SMTPUTF8 must be negotiated by peer before relaxing). Three profiles + posture cascades (`hipaa` / `pci-dss` / `gdpr` / `soc2`) all map to `strict`. Registers in `b.guardAll.allGuards()`'s `STANDALONE_GUARDS`. Fuzz harness ships in `fuzz/guard-smtp-command.fuzz.js`."
|
|
877
|
+
}
|
|
878
|
+
]
|
|
879
|
+
}
|
|
880
|
+
],
|
|
881
|
+
"references": [
|
|
882
|
+
{
|
|
883
|
+
"label": "RFC 5321 §2.3.8 (CR / LF separation)",
|
|
884
|
+
"url": "https://www.rfc-editor.org/rfc/rfc5321#section-2.3.8"
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
"label": "CVE-2023-51764 (Postfix SMTP smuggling)",
|
|
888
|
+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-51764"
|
|
889
|
+
}
|
|
890
|
+
]
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
"version": "0.9.31",
|
|
894
|
+
"date": "2026-05-15",
|
|
895
|
+
"headline": "`b.safeDns` + `b.network.dns.resolver` — bounded DNS-response parser and validating stub resolver",
|
|
896
|
+
"summary": "Substrate for every framework consumer that walks the DNS (DKIM TXT lookup, MTA-STS verify, DANE TLSA, BIMI / VMC discovery, SVCB / HTTPS records, RBL queries, AutoConfig / AutoDiscover, MX / submission).",
|
|
897
|
+
"sections": [
|
|
898
|
+
{
|
|
899
|
+
"heading": "Added",
|
|
900
|
+
"items": [
|
|
901
|
+
{
|
|
902
|
+
"title": "`b.safeDns.parseResponse(buf, opts?)` — pure-functional DNS wire-format parser",
|
|
903
|
+
"body": "Caps every dimension an attacker can grow: response bytes (4 KiB strict default), label count per name, compression-pointer chain depth (RFC 1035 §4.1.4 loop defense), CNAME chain depth, per-section RR count (answer 64 / authority 32 / additional 32), TXT rdata total length, EDNS0 advertised buffer (RFC 6891 §6.1.3). Type decoders for A / AAAA / CNAME / NS / PTR / MX / TXT / SOA / SRV / DS / DNSKEY / RRSIG / TLSA. AAAA renders in canonical RFC 5952 form with longest-zero-run compression, single-zero-group preservation, and IPv4-mapped (`::ffff:n.n.n.n`) handling."
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
"title": "`b.network.dns.resolver.create({ profile, posture, maxTtlMs, minTtlMs, serveStale, transport, audit })`",
|
|
907
|
+
"body": "Validating stub resolver. Every query parses through `b.safeDns`; cache keyed on `{ name, type }` with TTL = min across answer RRs (RFC 2181 §5.2), clamped to `[minTtlMs, maxTtlMs]`."
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
"title": "Serve-stale on upstream failure or malformed response (RFC 8767)",
|
|
911
|
+
"body": "Operator-configurable stale window (default 6h; RFC 8767 §6 caps at 7 days). Returned entries carry `{ stale: true, fromCache: true }`."
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
"title": "CNAME chain following with depth cap",
|
|
915
|
+
"body": "`followCnames(name, type, opts)` — per-hop depth check through `b.safeDns.checkCnameChainDepth` (default cap 8, matching BIND9's canonical-name-translation cap; RFC 1912 §2.4 chain-loop defense)."
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
"title": "AD-bit surface (RFC 4035 §3.2.3)",
|
|
919
|
+
"body": "Every response surfaces `{ validated: <AD-bit> }`. Per-call `validate: true` refuses responses with AD=0. Full local RRSIG verification deferred to operator-supplied validating recursive (Unbound / BIND9 / Knot) — root trust anchor management (RFC 5011 lifecycle) doesn't belong in a stub."
|
|
920
|
+
}
|
|
921
|
+
]
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
"heading": "Security",
|
|
925
|
+
"items": [
|
|
926
|
+
{
|
|
927
|
+
"title": "CVE coverage at parse-time bounds",
|
|
928
|
+
"body": "CVE-2008-1447 (Kaminsky cache-poisoning — bounded parse + random query ID + DoH TLS transport); CVE-2022-3204 (NRDelegationAttack — RR caps on authority + additional); CVE-2023-50387 / CVE-2023-50868 (KeyTrap / NSEC3-encloser — DNSKEY+RRSIG+NSEC3 counts bounded at parse time); CVE-2024-1737 (BIND9 large-RRset resource exhaustion). Three profiles (`strict` / `balanced` / `permissive`); posture cascades (`hipaa` / `pci-dss` / `gdpr` / `soc2`) all map to strict — DNS is critical-path substrate, no posture loosens it. QNAME minimization (RFC 9156) deliberately not in the stub — the operator's upstream recursive does QNAME-min."
|
|
929
|
+
}
|
|
930
|
+
]
|
|
931
|
+
}
|
|
932
|
+
],
|
|
933
|
+
"references": [
|
|
934
|
+
{
|
|
935
|
+
"label": "RFC 1035 (DNS wire format)",
|
|
936
|
+
"url": "https://www.rfc-editor.org/rfc/rfc1035"
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
"label": "RFC 8767 (serve-stale)",
|
|
940
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8767"
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
"label": "RFC 4035 §3.2.3 (DNSSEC AD bit)",
|
|
944
|
+
"url": "https://www.rfc-editor.org/rfc/rfc4035#section-3.2.3"
|
|
945
|
+
}
|
|
946
|
+
]
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
"version": "0.9.30",
|
|
950
|
+
"date": "2026-05-14",
|
|
951
|
+
"headline": "`b.agent.snapshot` — drain then snapshot in-flight state; restart then restore and resume",
|
|
952
|
+
"summary": "Closes the agent + orchestration substrate playbook — every primitive is now operationally durable across deploys + crashes. Mail-stack DNS resolver resumes next.",
|
|
953
|
+
"sections": [
|
|
954
|
+
{
|
|
955
|
+
"heading": "Added",
|
|
956
|
+
"items": [
|
|
957
|
+
{
|
|
958
|
+
"title": "`b.agent.snapshot.create({ orchestrator, backend, audit, policy })`",
|
|
959
|
+
"body": "Facade. Surface: `takeSnapshot(opts)`, `persist(snap)`, `loadLatest({ tenantId })`, `loadById(id)`, `restore(snap, { allowSchemaVersionMismatch, refuseOnTopologyChange })`, `list({ tenantId, sinceMs })`, `gc({ olderThanMs })`."
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
"title": "Snapshot envelope shape",
|
|
963
|
+
"body": "Carries `snapshotId` + `takenAt` + `frameworkVersion` + `schemaVersion` (currently 1) + `tenantId` + `orchestratorState` (agents / elections / consumers) + `inFlight` (streams / sagas / outboxJobs / busSubscribers / pendingDeliveries) + `idempotencyCache` (hot subset). Pluggable backend via `{ put, get, list, delete }` interface; operator wires durable storage (sqlite / object-store / external)."
|
|
964
|
+
},
|
|
965
|
+
{
|
|
966
|
+
"title": "Cluster topology change handling — re-shard-and-resume default",
|
|
967
|
+
"body": "Restore audit emits `agent.snapshot.topology-change` at HEIGHTENED severity carrying snapshot consumer count vs current, reshardedShards (set diff of topic names), affectedInFlight count, affectedSagas + affectedStreams counts so operator monitoring pipelines see exactly what changed across the deploy. Opt-in refuse via `restoreOpts.refuseOnTopologyChange` for strict-topology operators."
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
"title": "Schema-version mismatch refused by default",
|
|
971
|
+
"body": "Operator opts in via `restoreOpts.allowSchemaVersionMismatch` for explicit major-version migrations."
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
"title": "`b.guardSnapshotEnvelope`",
|
|
975
|
+
"body": "Envelope shape validator. Refuses missing `snapshotId` / `takenAt` / `frameworkVersion` / `orchestratorState` / `inFlight`, bad `schemaVersion` (non-integer / non-positive), oversize (default 50 MiB / 200 MiB / 1 GiB per profile), in-flight count cap (default 65536 strict). Audit lifecycle: `agent.snapshot.taken` / `persisted` / `restored` / `topology-change` / `gc`. Fuzz harness ships in `fuzz/guard-snapshot-envelope.fuzz.js`."
|
|
976
|
+
}
|
|
977
|
+
]
|
|
978
|
+
}
|
|
979
|
+
]
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
"version": "0.9.29",
|
|
983
|
+
"date": "2026-05-14",
|
|
984
|
+
"headline": "`b.agent.trace` — distributed tracing through every agent boundary",
|
|
985
|
+
"summary": "Composes `b.tracing` (W3C trace context). Cross-process trace continuity without per-handler wiring; operators get a full waterfall across hosts.",
|
|
986
|
+
"sections": [
|
|
987
|
+
{
|
|
988
|
+
"heading": "Added",
|
|
989
|
+
"items": [
|
|
990
|
+
{
|
|
991
|
+
"title": "`b.agent.trace.create({ tracing, audit, sampleRate, perMethod })`",
|
|
992
|
+
"body": "Facade. Surface: `startSpan(name, opts)`, `injectIntoEnvelope(envelope, span)`, `extractFromEnvelope(envelope)`, `recordResult(span, result, error?)`, `shouldSample(method)`, `formatAttributes(info)`."
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
"title": "W3C Trace Context propagation across queue / event-bus / sub-agent boundaries",
|
|
996
|
+
"body": "`injectIntoEnvelope` adds `_trace: { traceparent, tracestate }` to envelopes; `extractFromEnvelope` parses + validates the incoming envelope's trace context via `b.guardTraceContext`."
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
"title": "Per-method sampling",
|
|
1000
|
+
"body": "Global `sampleRate` (0..1) + `perMethod` override; useful for high-volume operators that want full traces for `send` but 10% for `fetch`."
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
"title": "Span attribute formatter",
|
|
1004
|
+
"body": "`formatAttributes({ method, dispatchMode, tenantId, postureSet, shard, resultStatus, elapsedMs })` produces W3C-compliant span attributes (`agent.method`, `agent.tenant_id`, `agent.posture` as JSON-array, etc.) so OpenTelemetry exporters render the agent waterfall consistently across all observability stacks."
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
"title": "`b.guardTraceContext`",
|
|
1008
|
+
"body": "W3C Trace Context shape validator. Refuses traceparent of wrong length (W3C §3.2.1 requires exactly 55 chars), non-hex chars, all-zero trace-id (§3.2.2.3) / span-id (§3.2.2.4), version `ff` (W3C-forbidden), version not in profile allowlist, oversized tracestate, too many tracestate entries. Length-bound before regex test so hostile input can't burn regex-engine CPU. Fuzz harness ships in `fuzz/guard-trace-context.fuzz.js`."
|
|
1009
|
+
}
|
|
1010
|
+
]
|
|
1011
|
+
}
|
|
1012
|
+
],
|
|
1013
|
+
"references": [
|
|
1014
|
+
{
|
|
1015
|
+
"label": "W3C Trace Context Level 2",
|
|
1016
|
+
"url": "https://www.w3.org/TR/trace-context-2/"
|
|
1017
|
+
}
|
|
1018
|
+
]
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
"version": "0.9.28",
|
|
1022
|
+
"date": "2026-05-14",
|
|
1023
|
+
"headline": "`b.agent.postureChain` — set-based compliance posture propagated across every agent boundary",
|
|
1024
|
+
"summary": "Compliance regimes (HIPAA / PCI-DSS / GDPR / SOC2) protect DIFFERENT regulated-data classes — they're orthogonal, not a linear lattice. Set semantics match how real-world regulations actually overlap (a clinic that processes payment cards operates under BOTH HIPAA + PCI).",
|
|
1025
|
+
"sections": [
|
|
1026
|
+
{
|
|
1027
|
+
"heading": "Added",
|
|
1028
|
+
"items": [
|
|
1029
|
+
{
|
|
1030
|
+
"title": "`b.agent.postureChain.create({ audit })`",
|
|
1031
|
+
"body": "Facade with `isSubset(targetSet, sourceSet)` (the core check: target.set superset-of source.set), `union(...sets)`, `canDelegate(sourceSet, targetSet, methodName)`, `appendHop(envelope, hopName)`, `validate(envelope, agentPostureSet)`, `declareRegime(name)`, `REGIMES` (frozen array)."
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
"title": "Cross-boundary downgrade refusal",
|
|
1035
|
+
"body": "`validate(envelope, agentPostureSet)` throws `agent-posture-chain/downgrade-refused` when the target (agent) posture set is missing any regime the source (envelope) carries. Audit emit at HEIGHTENED with the missing regime list + chain trail so operator review pipelines surface every downgrade attempt."
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
"title": "Hop trail tracking with recursion guard",
|
|
1039
|
+
"body": "`appendHop(envelope, hopName)` immutably extends `{ chainTrail, enteredAt, hopCount }`. Hop count caps at default 16 — defends infinite recursion across agent delegation. Per-hop timestamps must be monotonic non-negative numbers (guard rejects non-monotonic, length mismatches, etc.)."
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
"title": "`b.guardPostureChain`",
|
|
1043
|
+
"body": "Envelope shape validator. Refuses oversized hop trail (cap = 16 hops strict), non-ASCII hop names (operator-greppable), C0 / DEL forbidden, duplicate hops in trail (recursion guard), duplicate regimes in `postureSet`, non-monotonic `enteredAt` timestamps, `enteredAt` length mismatch with `chainTrail`. Built-in regimes: HIPAA, PCI-DSS, GDPR, SOC2. Operator declares custom regimes via `chain.declareRegime(\"healthcare-tier-1\")`; duplicate declarations refused. Empty source subset-of any target — unscoped calls flow freely. Fuzz harness ships in `fuzz/guard-posture-chain.fuzz.js`."
|
|
1044
|
+
}
|
|
1045
|
+
]
|
|
1046
|
+
}
|
|
1047
|
+
]
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
"version": "0.9.27",
|
|
1051
|
+
"date": "2026-05-14",
|
|
1052
|
+
"headline": "`b.agent.saga` — multi-step coordination with compensation cascade",
|
|
1053
|
+
"summary": "Exactly-once via the framework's outbox + idempotency primitives; reverse-order compensations on failure; survives orchestrator restart via persisted saga state.",
|
|
1054
|
+
"sections": [
|
|
1055
|
+
{
|
|
1056
|
+
"heading": "Added",
|
|
1057
|
+
"items": [
|
|
1058
|
+
{
|
|
1059
|
+
"title": "`b.agent.saga.create({ name, steps, audit })`",
|
|
1060
|
+
"body": "Every step has `{ name, run: async (ctx, state) => {}, compensate?: async (ctx, state) => {} }`. `saga.run(ctx, initialState, opts)` walks steps in order; each `run` mutates the shared `state` object. On step throw the framework fires every previously-completed step's `compensate` in REVERSE order (steps that hadn't completed aren't compensated)."
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
"title": "Compensation failure semantics",
|
|
1064
|
+
"body": "A compensate that throws emits `agent.saga.compensation_failed` audit at CRITICAL severity, halts further compensations, and the saga's final error message carries both the original step failure + the compensation failure so operator alert pipelines see the full context."
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
"title": "No saga-level retry — composition discipline",
|
|
1068
|
+
"body": "Saga's value-add is COMPENSATION, not retry. Step.run wraps `b.retry` if needed (with the idempotency substrate available, internal retry inside step.run is side-effect-safe). Keeps the saga primitive focused on the coordination contract."
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
"title": "Audit lifecycle",
|
|
1072
|
+
"body": "`agent.saga.started` / `step_started` / `step_completed` / `step_failed` / `compensation_started` / `compensation_completed` / `compensation_failed` / `failed` / `completed`. Each event carries `sagaId` + `name` + `stepName` + `stepIndex` for operator dashboards."
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
"title": "`b.guardSagaConfig`",
|
|
1076
|
+
"body": "Saga-creation config validator. Refuses empty steps array, duplicate step names, non-function `run`, non-function `compensate` (when provided), non-ASCII saga name, oversized step count (default 32). Ships profile + posture vocabulary uniform with rest of guard family. Fuzz harness ships in `fuzz/guard-saga-config.fuzz.js`."
|
|
1077
|
+
}
|
|
1078
|
+
]
|
|
1079
|
+
}
|
|
1080
|
+
]
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
"version": "0.9.26",
|
|
1084
|
+
"date": "2026-05-14",
|
|
1085
|
+
"headline": "`b.agent.tenant` — multi-tenant isolation as a first-class primitive",
|
|
1086
|
+
"summary": "Replaces the per-operator wiring of `actor.tenantId === registeredTenant` (which tends to leak across handlers) with one centralized scope. Archive-default destroy semantics match SEC 17a-4, FINRA 4511, HIPAA, and MiFID II retention obligations; crypto-erasure requires explicit step-up + dual-control.",
|
|
1087
|
+
"sections": [
|
|
1088
|
+
{
|
|
1089
|
+
"heading": "Added",
|
|
1090
|
+
"items": [
|
|
1091
|
+
{
|
|
1092
|
+
"title": "`b.agent.tenant.create({ backend, audit, permissions })`",
|
|
1093
|
+
"body": "Facade with `register(tenantId, { posture, archivePolicy, metadata })`, `unregister(tenantId, opts)`, `lookup(tenantId)`, `list({})`, `check(actor, agentTenantId)`, `derivedKey(tenantId, purpose)`, `auditFor(tenantId)`, `listArchived()`. Pluggable backend; in-memory default."
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
"title": "Cross-tenant gate (`check`)",
|
|
1097
|
+
"body": "Refuses calls where `actor.tenantId !== agentTenantId` unless the actor holds `framework-cross-tenant-admin` scope (every cross-tenant access by an admin emits `agent.tenant.cross_tenant_access` audit at HEIGHTENED severity for operator review)."
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
"title": "Per-tenant derived keys + per-tenant audit",
|
|
1101
|
+
"body": "`derivedKey` composes `b.crypto.namespaceHash` for deterministic per-tenant key derivation from `purpose` + `tenantId`. Cross-tenant decrypt refused at the vault boundary by construction — each tenant's seal-key derivation differs, so even with disk access an attacker can't cross-decrypt. `auditFor` returns an audit wrapper that auto-tags every emit with `metadata.tenantId` so each tenant's audit trail is independently filterable."
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
"title": "Archive-default destroy semantics",
|
|
1105
|
+
"body": "`unregister(tenantId)` archives by default (retention-safe, matches SEC 17a-4 6yr / FINRA 4511 / HIPAA §164.530(j) / MiFID II Art 16(7) compliance needs). Destruction requires explicit `{ destroy: true, stepUpToken, dualControlApprover, reason, actor }` — four preconditions all required together. The framework validates the SHAPE; the operator's step-up middleware + dual-control middleware grants the actual tokens upstream. Missing any precondition refuses with a specific `agent-tenant/destroy-requires-step-up` / `-dual-control` / `-reason` / `-actor` code so operators see exactly what's missing."
|
|
1106
|
+
},
|
|
1107
|
+
{
|
|
1108
|
+
"title": "`b.guardTenantId`",
|
|
1109
|
+
"body": "Tenant-id shape validator. Refuses non-ASCII (operator-greppable in audit logs across stack boundaries), path-traversal (`..` / `/` / `\\` / NUL / C0 / DEL), oversized (default 64 bytes), reserved `ROOT` / `FRAMEWORK` / `*`, leading `.`. Posture vocabulary uniform (`strict` / `balanced` / `permissive` profiles; hipaa / pci-dss / gdpr / soc2 postures pin strict). Fuzz harness ships in `fuzz/guard-tenant-id.fuzz.js`."
|
|
1110
|
+
}
|
|
1111
|
+
]
|
|
1112
|
+
}
|
|
1113
|
+
]
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
"version": "0.9.25",
|
|
1117
|
+
"date": "2026-05-14",
|
|
1118
|
+
"headline": "`b.agent.eventBus` — typed cross-agent publish/subscribe with schema enforcement and cross-tenant subscribe refusal",
|
|
1119
|
+
"summary": "Substrate for every agent-to-agent reaction (`mail.scan.malware-detected` refuses source at MX, `mail.crypto.key-rotated` invalidates vault recipient cache, `ai.classify.prompt-injection-detected` quarantines the agent).",
|
|
1120
|
+
"sections": [
|
|
1121
|
+
{
|
|
1122
|
+
"heading": "Added",
|
|
1123
|
+
"items": [
|
|
1124
|
+
{
|
|
1125
|
+
"title": "`b.agent.eventBus.create({ pubsub, audit, permissions })`",
|
|
1126
|
+
"body": "Facade over operator-supplied `b.pubsub` (or any `{ publish, subscribe, unsubscribe }`-shaped backend). Surface: `registerTopic(name, { schema, posture, permissions, tenantScope })`, `publish(name, payload, { actor })`, `subscribe(name, handler, { actor })` returns unsubscribe fn, `listTopics({ actor })`."
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
"title": "Topic registration with schema",
|
|
1130
|
+
"body": "Declared at boot via flat key->type map (`string` / `number` / `boolean` / `integer` / `isoDateTime` / `array` / `object`; suffix `?` marks optional). Unknown topics refuse publish + subscribe so typos fail loudly. Duplicate registration refused (operator must `unregister` first when the surface gains that surface in a later substrate release)."
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
"title": "Schema enforcement at every boundary + permission gating + cross-tenant isolation",
|
|
1134
|
+
"body": "Every payload validated against schema before `pubsub.publish` AND at each delivery (subscriber-side re-validation defends in-flight tampering). `permissions.publish` + `permissions.subscribe` per-topic scope arrays; `b.permissions.check(actor, scope)` on every publish + subscribe call. `tenantScope: true` topics carry the publisher's `tenantId` in the wire envelope; subscriber's actor must declare a matching tenantId or the event is silently dropped at delivery with `agent.event_bus.cross_tenant_drop` audit."
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
"title": "`b.guardEventBusTopic` + `b.guardEventBusPayload`",
|
|
1138
|
+
"body": "Topic-name validator refuses dot-count < 3 (operators use `<domain>.<source>.<event>` shape), oversized (default 128 bytes), non-ASCII, reserved `framework.*` prefix, path-traversal shapes, slash, backslash, C0 / DEL. Payload validator bounded byte cap (default 64 KiB — events are metadata, not bulk data; publishers reference bulk data via `b.objectStore` IDs); type-check cascade refuses non-finite numbers, malformed ISO-8601 dateTime (length-bound to 64 chars before regex test so a hostile input can't burn regex-engine CPU), missing required fields, unknown fields (schemas must be exhaustive). Fuzz harnesses ship in `fuzz/guard-event-bus-topic.fuzz.js` + `fuzz/guard-event-bus-payload.fuzz.js`."
|
|
1139
|
+
}
|
|
1140
|
+
]
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
"heading": "Changed",
|
|
1144
|
+
"items": [
|
|
1145
|
+
{
|
|
1146
|
+
"title": "Shared agent-audit helper extracted to `lib/agent-audit.js`",
|
|
1147
|
+
"body": "Factored out the identical `_safeAudit` wrapper that the orchestrator, idempotency, stream, and eventBus substrate modules carried inline. The four agent substrate modules now compose `agentAudit.safeAudit`."
|
|
1148
|
+
}
|
|
1149
|
+
]
|
|
1150
|
+
}
|
|
1151
|
+
]
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
"version": "0.9.24",
|
|
1155
|
+
"date": "2026-05-14",
|
|
1156
|
+
"headline": "`b.agent.stream` — async-iterable variants for agent methods that yield N rows",
|
|
1157
|
+
"summary": "Cursor-backed delivery with built-in backpressure for the agent substrate. JMAP `Email/queryChanges` against million-message mailboxes returns an array today (OOM on client + agent before the response ships); this primitive lets handlers yield rows one at a time without buffering the full result.",
|
|
1158
|
+
"sections": [
|
|
1159
|
+
{
|
|
1160
|
+
"heading": "Added",
|
|
1161
|
+
"items": [
|
|
1162
|
+
{
|
|
1163
|
+
"title": "`b.agent.stream.create({ openCursor, cursorOpts, batchSize, orchestrator, actor, kind, audit })`",
|
|
1164
|
+
"body": "Returns an object implementing `[Symbol.asyncIterator]` so operators write `for await (var row of stream) { ... }`. Operator supplies an `openCursor(cursorOpts)` factory returning a cursor with `fetchBatch(batchSize) -> { rows, nextCursor, done }` + optional `close()` + optional `lastSeenCursor()`."
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
"title": "Backpressure built-in",
|
|
1168
|
+
"body": "Async-generator semantics; each `next()` call yields one row from an in-memory batch buffer; the cursor only refetches when the buffer drains. Pulling slowly applies backpressure to the store side; a slow client can't OOM the server."
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
"title": "Auto cursor close on every exit path",
|
|
1172
|
+
"body": "Consumer `break` calls iterator `.return()`, consumer `throw` calls `.throw()`, natural exhaustion calls `.next()` until done — all three close the cursor via `try`/`finally`-style `_closeOnce` and emit `agent.stream.closed` audit with reason (`exhausted` / `consumer-break` / `consumer-throw` / `drain` / `error`)."
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
"title": "Drain marker on orchestrator drain",
|
|
1176
|
+
"body": "When orchestrator drain fires mid-stream, the next `next()` call returns ONE final `{ _drainMarker: true, lastSeenCursor: <opaque>, reason: \"drain\" }` row + closes the cursor. Clients reconnecting via JMAP-WebSocket / IMAP NOTIFY pass `lastSeenCursor` back to resume from the same position against the new agent post-deploy. Composes the orchestrator's `registerStream` / `unregisterStream` / `isDraining` hooks."
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
"title": "`b.guardStreamArgs`",
|
|
1180
|
+
"body": "Validates `b.agent.stream.create` opts. Refuses non-integer `batchSize`, batchSize out of `[1, 1024]` strict-profile range, empty `kind` string, function / regex / Buffer / `__proto__` keys inside `cursorOpts` (structured-clone-unsafe). Ships strict / balanced / permissive profiles + hipaa / pci-dss / gdpr / soc2 postures. Fuzz harness ships in `fuzz/guard-stream-args.fuzz.js`."
|
|
1181
|
+
}
|
|
1182
|
+
]
|
|
1183
|
+
}
|
|
1184
|
+
],
|
|
1185
|
+
"references": [
|
|
1186
|
+
{
|
|
1187
|
+
"label": "RFC 8620 §5.5 (JMAP Core — position-paginated responses)",
|
|
1188
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8620#section-5.5"
|
|
1189
|
+
}
|
|
1190
|
+
]
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
"version": "0.9.23",
|
|
1194
|
+
"date": "2026-05-14",
|
|
1195
|
+
"headline": "CodeQL alert sweep — 30 closures across 6 rule classes + wiki @primitive validator extracted into static gates",
|
|
1196
|
+
"summary": "Regression cleanup of the earlier CodeQL batch — the framework-wide require-binding rename shifted line numbers and the suppression comments stopped tracking. Plus the wiki primitive validator now runs at static-gate time so the same nit class is caught pre-push instead of after a ~90s wiki-e2e cycle.",
|
|
1197
|
+
"sections": [
|
|
1198
|
+
{
|
|
1199
|
+
"heading": "Fixed",
|
|
1200
|
+
"items": [
|
|
1201
|
+
{
|
|
1202
|
+
"title": "`js/file-system-race` (10 sites)",
|
|
1203
|
+
"body": "`lib/atomic-file.js:_readSyncCore` migrated from `statSync`+`readFileSync` to the canonical TOCTOU-safe-read scaffold (open fd then `fstatSync` then `readSync` loop then `closeSync` in finally; ENOENT now surfaces from `openSync`); `lib/restore-rollback.js` marker write switched to `openSync(..., \"wx\", 0o600)` + `writeSync` + `fsyncSync` + EEXIST tolerance; `lib/network-tls.js` new `_readPathFile` fd-based reader; `lib/backup/bundle.js` fd-narrowed read with short-read detection; `lib/static.js` content-safety gate converted to `fsp.open` filehandle pattern; `lib/vault/seal-pem-file.js`, `lib/atomic-file.js:fsyncDir`, `test/30-chain.js`, `examples/wiki/test/validate-{env,cli}-snapshot.js`, `examples/wiki/lib/source-doc-parser.js` got suppression refresh OR fd-narrowed refactor per shape."
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
"title": "`js/insecure-temporary-file` (6 sites)",
|
|
1207
|
+
"body": "`lib/mtls-ca.js` new `_writeExclusive` helper using `openSync(..., \"wx\", mode)` + `writeSync` + `fsyncSync`; `lib/vault/rotate.js` + `lib/http-client.js` got suppression refresh pointing at the operator-supplied stagingDir / 64-bit CSPRNG suffix defenses."
|
|
1208
|
+
},
|
|
1209
|
+
{
|
|
1210
|
+
"title": "`js/path-injection` (2 sites in `lib/static.js`)",
|
|
1211
|
+
"body": "Suppression refresh pointing at the upstream `_resolveSafe` sandbox check (lexical resolve + `startsWith(rootResolved + sep)` + realpath escape guard + guardFilename gate)."
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
"title": "`js/remote-property-injection` (7 sites)",
|
|
1215
|
+
"body": "`lib/middleware/csrf-protect.js` cookie-parse output + `lib/websocket.js` `_parseExtensionHeader` params switched to `Object.create(null)` so attacker-controlled keys have no prototype chain to pollute; `lib/middleware/body-parser.js` multipart fields got suppression refresh pointing at the POISONED_KEYS gate; `test/40-consumers.js` + `test/00-primitives.js` test fixtures suppressed with `test/` scope justification."
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
"title": "`PinnedDependenciesID` (2 workflow files)",
|
|
1219
|
+
"body": "Every `uses:` line was already SHA-pinned; the remaining alerts pointed at the `npm install --no-audit --no-fund` step in `.github/workflows/npm-publish.yml` + `ci.yml`. Added explicit `name@version` specifiers (esbuild + postject, mirroring `package.json`'s exact pins) so CodeQL recognizes the install as pinned without committing a lockfile (which the framework's zero-npm-runtime-deps posture forbids — vendored stack only)."
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
"title": "`js/regex/missing-regexp-anchor` (1 site)",
|
|
1223
|
+
"body": "`scripts/build-vendored-sbom.js` host-extractor regex anchored to `^https?://github.com/` so an attacker-controlled `entry.source` containing `github.com` as a path or query substring can't misdirect purl dispatch into the github branch."
|
|
1224
|
+
}
|
|
1225
|
+
]
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
"heading": "Added",
|
|
1229
|
+
"items": [
|
|
1230
|
+
{
|
|
1231
|
+
"title": "Wiki @primitive validator extracted into static gates",
|
|
1232
|
+
"body": "`examples/wiki/lib/source-comment-block-validator.js` (shared engine, pure module, no side effects) + `scripts/validate-source-comment-blocks.js` (framework-level standalone wrapper, runs in 419-466ms, no `@blamejs/core` / vault / DB / network dependencies). Existing wiki-e2e gate refactored to delegate to the shared engine; CI invocation unchanged. The release workflow's static-gate step now lists `node scripts/validate-source-comment-blocks.js` after `codebase-patterns.test.js`. Each suppression comment references the active defense by file:line so future CodeQL re-runs OR future readers know which suppression applies."
|
|
1233
|
+
}
|
|
1234
|
+
]
|
|
1235
|
+
}
|
|
1236
|
+
]
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
"version": "0.9.22",
|
|
1240
|
+
"date": "2026-05-14",
|
|
1241
|
+
"headline": "`b.agent.idempotency` — cross-dispatch idempotency keys at every consumer boundary",
|
|
1242
|
+
"summary": "Second agent-substrate primitive. Provides JMAP retry-safe semantics from day one — operator-supplied keys are namespace-hashed before disk, replay counts are audited, and reuse with different args refuses via a request-fingerprint check.",
|
|
1243
|
+
"sections": [
|
|
1244
|
+
{
|
|
1245
|
+
"heading": "Added",
|
|
1246
|
+
"items": [
|
|
1247
|
+
{
|
|
1248
|
+
"title": "`b.agent.idempotency.create({ store, audit, ttlMs, maxResultBytes, fingerprintArgs })`",
|
|
1249
|
+
"body": "Pluggable backing store via `{ get, put, delete, gc }`; in-memory default for single-process deployments, operator wires durable backends (sqlite-backed adapter or external). Surface: `instance.get(method, actorId, key)` returns cached envelope `{ result, firstAt, lastReplayedAt, replayCount, requestFingerprint }` or null; `instance.put(method, actorId, key, result, { args, requestFingerprint })` serializes via `b.safeJson.stringify`, persists with TTL, and refuses key-reuse-different-args via the request-fingerprint (sha3-512 of args sans key); `instance.invalidate(method, actorId, key)` is the saga-compensation escape hatch; `instance.gc({ olderThanMs })` for periodic cleanup."
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
"title": "Keys hashed at the boundary",
|
|
1253
|
+
"body": "Operator-supplied keys never reach disk in raw form — namespace-hashed via `b.crypto.namespaceHash(\"agent.idempotency\", method + \"\\0\" + actorId + \"\\0\" + key)`. Cross-actor + cross-method isolation by construction: an attacker can't replay another actor's mutation by guessing the key."
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
"title": "Replay tracking + audit emit",
|
|
1257
|
+
"body": "Every cache hit increments `replayCount` and bumps `lastReplayedAt`; audit emits `agent.idempotency.replay` with the truncated actor-id hash + first/replay timestamps so operator pipelines surface retry storms."
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
"title": "`b.guardIdempotencyKey` — operator-supplied key shape validator",
|
|
1261
|
+
"body": "Refuses oversized (default 256 bytes), control chars (C0/NUL/DEL — defends audit-log injection), slash + backslash (defends operators routing keys through filesystem paths), path-traversal (`..`), non-ASCII under strict (operator-greppable in audit logs across stack boundaries; permissive opts down for legacy Unicode tenant IDs). Fuzz harness in `fuzz/guard-idempotency-key.fuzz.js`."
|
|
1262
|
+
},
|
|
1263
|
+
{
|
|
1264
|
+
"title": "`test/helpers/json-round-trip.js` — JSON round-trip test helper",
|
|
1265
|
+
"body": "New `assertJsonRoundTrip(shape, label)` catches the bug class Codex flagged on PR #51 (v0.9.21 `register()` stored an agent function ref on the backend row, lost in DB/JSON serialization). Refuses on function fields, Buffer fields, Date objects, Symbol keys, BigInt values, non-finite numbers, and cycles. Applied retroactively to the v0.9.21 agent-orchestrator backend row in a regression test."
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
"title": "Result size cap (default 1 MiB)",
|
|
1269
|
+
"body": "Refuses with `agent-idempotency/result-too-big` so operators discover OOM-prone result shapes locally instead of at runtime. ClusterFuzzLite matrix entries added."
|
|
1270
|
+
}
|
|
1271
|
+
]
|
|
1272
|
+
}
|
|
1273
|
+
],
|
|
1274
|
+
"references": [
|
|
1275
|
+
{
|
|
1276
|
+
"label": "RFC 8620 JMAP Core (idempotency)",
|
|
1277
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8620.html"
|
|
1278
|
+
}
|
|
1279
|
+
]
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
"version": "0.9.21",
|
|
1283
|
+
"date": "2026-05-14",
|
|
1284
|
+
"headline": "`b.agent.orchestrator` — framework-level supervisor for every agent blamejs ships",
|
|
1285
|
+
"summary": "First of the agent/orchestration substrate primitives built before the mail-stack resumes. Provides a registry, sharded-topic dispatch, leader election, drain coordination, and a health aggregator — but does NOT supervise OS processes; spawn / restart-on-crash / pod scheduling stay with pm2 / systemd / k8s / Nomad.",
|
|
1286
|
+
"sections": [
|
|
1287
|
+
{
|
|
1288
|
+
"heading": "Added",
|
|
1289
|
+
"items": [
|
|
1290
|
+
{
|
|
1291
|
+
"title": "`b.agent.orchestrator.create(opts)` — registry + dispatch + election + drain + health",
|
|
1292
|
+
"body": "Facade with `register(name, agent, opts)` / `lookup(name)` / `unregister(name)` / `list({ kind, tenantId })` registry; `spawnConsumers({ agent, queue, shards, taskTopic, maxConcurrency })` for sharded-topic dispatch (FNV-1a consistent-hash via `b.agent.orchestrator.shardFor(key, shards)`; per-shard topic suffix `<base>.<shard>`); `elect({ resource })` composing `b.cluster` DB-row leader election (returns `{ isLeader, fencingToken, leaderId }` — single-process deployments get a trivial-leader, cluster deployments delegate); `drain({ timeoutMs })` stopping every spawned consumer and audit-emitting elapsed/count; `health()` aggregating per-agent + per-consumer + per-election state into one shape ready for `b.middleware.healthcheck`."
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
"title": "Pluggable backend — `{ get, set, delete, list }`",
|
|
1296
|
+
"body": "In-memory default ships for single-process deployments; operator wires `b.config.loadDbBacked`-shaped or external for restart-survival of the registry state."
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
"title": "`b.guardAgentRegistry` — registry-op shape validator",
|
|
1300
|
+
"body": "Refuses non-ASCII agent names (NFC + ASCII-only — operator-greppable in audit logs), path-traversal shapes (`..` / `/` / `\\` / NUL / C0 / DEL), oversized (default 64 bytes), reserved `FRAMEWORK.*` / `ROOT.*` prefix, duplicate-on-register, register without `agentKind`. Ships `strict` / `balanced` / `permissive` profiles and `hipaa` / `pci-dss` / `gdpr` / `soc2` postures (all pin strict). Fuzz harness in `fuzz/guard-agent-registry.fuzz.js`."
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
"title": "Drain auto-wires into `b.appShutdown`",
|
|
1304
|
+
"body": "When operator supplies an `appShutdown` instance, SIGTERM delivers a clean drain of all spawned consumers and stream-registry signal before the process exits."
|
|
1305
|
+
},
|
|
1306
|
+
{
|
|
1307
|
+
"title": "Stream-registry hooks — `registerStream` / `unregisterStream` / `isDraining`",
|
|
1308
|
+
"body": "Substrate for the upcoming `b.agent.stream` async-iterable variants so they can check the drain flag and emit drain-markers cleanly across deploys."
|
|
1309
|
+
}
|
|
1310
|
+
]
|
|
1311
|
+
}
|
|
1312
|
+
],
|
|
1313
|
+
"references": [
|
|
1314
|
+
{
|
|
1315
|
+
"label": "FNV-1a hash",
|
|
1316
|
+
"url": "https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function"
|
|
1317
|
+
}
|
|
1318
|
+
]
|
|
1319
|
+
},
|
|
1320
|
+
{
|
|
1321
|
+
"version": "0.9.20",
|
|
1322
|
+
"date": "2026-05-14",
|
|
1323
|
+
"headline": "`b.mail.agent` — the standardization contract for every mail protocol blamejs ships",
|
|
1324
|
+
"summary": "Every above-the-wire mail surface (JMAP, IMAP, POP3, ManageSieve, MX listener, submission) translates protocol calls into `agent.X(args)`. RBAC, posture enforcement, audit emission, dispatch, and worker isolation are owned at the agent.",
|
|
1325
|
+
"sections": [
|
|
1326
|
+
{
|
|
1327
|
+
"heading": "Added",
|
|
1328
|
+
"items": [
|
|
1329
|
+
{
|
|
1330
|
+
"title": "`b.mail.agent.create` — facade with 23 methods",
|
|
1331
|
+
"body": "Read surface (`search` / `fetch` / `thread` / `folders` / `quota`) backed by v0.9.19 `b.mailStore` and runs immediately. Move surface (`move` / `flag` / `delete`) backed by the new `mailStore.moveMessages` substrate; soft-delete moves to Trash and tags `\\Deleted` (hard expunge wires later with retention-floor enforcement). Write surface (`compose` / `send` / `reply` / `forward`), Sieve (`sieve.list/put/activate`), identity (`identity.set` / `vacation.set`), MDN (`mdn.send/parse/allowList`), regulated export, and migration `import` throw `mail-agent/not-implemented` with a `wiredAt` tag naming the release that lights them up — defer-with-condition for v1-defensible scope."
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
"title": "Dispatch contract — `local` / `queue` / `auto`",
|
|
1335
|
+
"body": "`local` runs every method in-process. `queue` publishes envelopes to `mail.agent.tasks` via `b.queue.enqueue`; an `agent.consumer({ agent, queue })` running in a dedicated process or replicas across hosts pulls and executes. Posture metadata travels with each envelope; the consumer re-validates against its own posture before unseal so no posture downgrade survives the queue boundary. `auto` routes fast-path ops (`fetch` / `folders` / `flag` / `quota`) locally and heavy ops (`search` / `export`) to queue/workerPool when configured."
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
"title": "Worker isolation",
|
|
1339
|
+
"body": "`dispatch.workerPool` (composes `b.workerPool`) validated at create-time. The agent reserves `vaultKeyDelivery: \"in-worker\"` default vs `\"main-only\"` (posture-conditional — HIPAA/PCI/GDPR default to main-only when the worker-script path wires at the Sieve slice)."
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
"title": "`b.mail.agent.consumer` — queue-side facade for multi-host load-spreading",
|
|
1343
|
+
"body": "Carries its own `store` and re-validates posture at the boundary so a misconfigured consumer can't observe sealed data above its posture set."
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
"title": "Five new guards through `b.gateContract`",
|
|
1347
|
+
"body": "`b.guardMailQuery` (search/fetch filter shape: bounded depth/keys/array-length, function/regex/Buffer/cycle refusal, `__proto__` key refusal, projection-column allowlist via `FILTERABLE_COLUMNS`, posture-required actor fields HIPAA->`purposeOfUse` / PCI->`pciScope` / GDPR->`lawfulBasis`); `b.guardMailCompose` (identity-vs-From alignment, recipient deduplication, attachment-byte cap default 25 MiB, body shape — exactly one of text/html unless `allowMultipartAlternative`, C0 control-char refusal in headers); `b.guardMailReply` (References-chain cap default 100 defends infinite-loop forwards, In-Reply-To continuity per RFC 5322 §3.6.4 — last References must match In-Reply-To, quoted-original byte cap, forwarded-attachment cardinality cap); `b.guardMailMove` (system-folder allowlist for INBOX/Sent/Drafts/Trash/Junk/Archive, admin-scope or `allowedFolders` gate for arbitrary destinations, path-traversal refusal, slash refusal — IMAP `.` hierarchy separator only); `b.guardMailSieve` (pre-parser shape: script-byte cap default 64 KiB, line-count cap defends one-byte-line bombs, name shape — path-traversal / slash / backslash refusal, actor-ownership check). Each ships `strict` / `balanced` / `permissive` profiles and `hipaa` / `pci-dss` / `gdpr` / `soc2` postures (all pin strict)."
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
"title": "`b.mailStore.moveMessages(fromFolder, toFolder, objectIds)`",
|
|
1351
|
+
"body": "Per IMAP4rev2 §6.6.2, both folders bump modseq on move; agent.move composes this. Fuzz harnesses ship for every new guard, and the v0.9.19 substrates (`safe-mime` / `guard-message-id`) are now wired into the ClusterFuzzLite matrix."
|
|
1352
|
+
}
|
|
1353
|
+
]
|
|
1354
|
+
}
|
|
1355
|
+
],
|
|
1356
|
+
"references": [
|
|
1357
|
+
{
|
|
1358
|
+
"label": "RFC 5322 Internet Message Format",
|
|
1359
|
+
"url": "https://www.rfc-editor.org/rfc/rfc5322.html"
|
|
1360
|
+
},
|
|
1361
|
+
{
|
|
1362
|
+
"label": "RFC 9051 IMAP4rev2",
|
|
1363
|
+
"url": "https://www.rfc-editor.org/rfc/rfc9051.html"
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
"label": "RFC 8620 JMAP Core",
|
|
1367
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8620.html"
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
"label": "RFC 8621 JMAP Mail",
|
|
1371
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8621.html"
|
|
1372
|
+
}
|
|
1373
|
+
]
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
"version": "0.9.19",
|
|
1377
|
+
"date": "2026-05-14",
|
|
1378
|
+
"headline": "First mail-stack substrates — `b.mailStore` + `b.safeMime` + `b.guardMessageId`",
|
|
1379
|
+
"summary": "Byte-level mail-store foundation that every above-the-wire mail primitive composes. Ships a bounded RFC 5322 / MIME parser, a Message-Id header-injection gate, and a sealed-by-default mail store with per-folder CONDSTORE modseq counters and JMAP cross-protocol object identifiers.",
|
|
1380
|
+
"sections": [
|
|
1381
|
+
{
|
|
1382
|
+
"heading": "Added",
|
|
1383
|
+
"items": [
|
|
1384
|
+
{
|
|
1385
|
+
"title": "`b.safeMime` — bounded RFC 5322 / 2045 / 2046 / 2047 / EAI MIME parser",
|
|
1386
|
+
"body": "Caps every dimension an attacker can grow: total parts (default 64), nesting depth (default 16), boundary length (default 70 per RFC 2046 §5.1.1), header bytes (default 64 KiB), header line (default 998 per RFC 5322 §2.1.1), body bytes (default 25 MiB), message bytes (default 50 MiB). Charset and transfer-encoding allowlists. Surface: `parse(bytes, opts) -> tree`, `walk(tree, visitor)`, `findFirst(tree, predicate)`, `extractText(tree, opts)` (RFC 2046 §5.1.4 last-wins for `multipart/alternative`), `extractAttachments(tree, opts)`. Includes RFC 2047 Q+B encoded-word decoding and RFC 2231 charset'lang'value filename decoding. Throws `safe-mime/<code>` on every cap exceeded, malformed boundary, unknown charset/CTE, or control chars in headers. Defends CVE-2024-39929 (Exim MIME parser) and CVE-2025-30258 (gnumail truncated-MIME-tree class). Fuzz harness in `fuzz/safe-mime.fuzz.js`."
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
"title": "`b.guardMessageId` — RFC 5322 §3.6.4 Message-Id validator",
|
|
1390
|
+
"body": "Gates Message-Id / In-Reply-To / References at the mail-store append boundary and at future MX inbound + submission outbound paths. Refuses oversized (>998 bytes), bare CR/LF/NUL/C0/DEL (defends `From:` / `Bcc:` smuggling via folded continuation), unbracketed under strict, empty value, missing `@`, nested angle brackets, bidi codepoints (CVE-2021-42574 RTLO class in mail-header context). Profiles `strict` / `balanced` / `permissive`; postures `hipaa` / `pci-dss` / `gdpr` / `soc2` all pin strict. Surface: `validate(value, opts)`, `validateList(value, opts)` with References-chain cap = 100, `compliancePosture(posture)`. Fuzz harness in `fuzz/guard-message-id.fuzz.js`."
|
|
1391
|
+
},
|
|
1392
|
+
{
|
|
1393
|
+
"title": "`b.mailStore` — byte-level sealed mail-store substrate",
|
|
1394
|
+
"body": "Pluggable backend (sqlite default; operator wires `b.externalDb` Postgres or any `{ prepare(sql) -> { run, get, all } }`-shaped object). Surface: `create(opts)` returning `{ appendMessage, fetchByObjectId, queryByModseq, setFlags, createFolder, listFolders, threadFor, quota, setLegalHold }`. Sealed by default via `b.cryptoField.sealRow` — `subject` / `from_addr` / `to_addrs` / `body_text` / `body_html` route through a vault-managed AEAD envelope on insert + unseal on fetch. Plaintext (forensic-queryable without unsealing): `objectid`, `modseq`, `internal_date`, `received_at`, `size_bytes`, `flags`, `legal_hold`, `from_hash`, `message_id_hash`. Per-folder monotonic `modseq` counter (RFC 7162 CONDSTORE substrate); per-message `objectid` (RFC 8474 JMAP cross-protocol identity). Threading at append time via In-Reply-To + References chain walk. Quota substrate per folder; legal-hold composes existing `b.legalHold`. Schema bootstraps with six IMAP4rev2 default folders (INBOX / Sent / Drafts / Trash / Junk / Archive) and JMAP role mapping. Append composes `b.safeMime.parse` (bounded inbound) and `b.guardMessageId.validate` (header-injection gate)."
|
|
1395
|
+
}
|
|
1396
|
+
]
|
|
1397
|
+
}
|
|
1398
|
+
],
|
|
1399
|
+
"references": [
|
|
1400
|
+
{
|
|
1401
|
+
"label": "RFC 5322 Internet Message Format",
|
|
1402
|
+
"url": "https://www.rfc-editor.org/rfc/rfc5322.html"
|
|
1403
|
+
},
|
|
1404
|
+
{
|
|
1405
|
+
"label": "RFC 2045 MIME Part One",
|
|
1406
|
+
"url": "https://www.rfc-editor.org/rfc/rfc2045.html"
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
"label": "RFC 2046 MIME Part Two",
|
|
1410
|
+
"url": "https://www.rfc-editor.org/rfc/rfc2046.html"
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
"label": "RFC 2047 MIME encoded-words",
|
|
1414
|
+
"url": "https://www.rfc-editor.org/rfc/rfc2047.html"
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
"label": "RFC 2231 MIME parameter values",
|
|
1418
|
+
"url": "https://www.rfc-editor.org/rfc/rfc2231.html"
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
"label": "RFC 7162 IMAP CONDSTORE / QRESYNC",
|
|
1422
|
+
"url": "https://www.rfc-editor.org/rfc/rfc7162.html"
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
"label": "RFC 8474 JMAP object identifiers",
|
|
1426
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8474.html"
|
|
1427
|
+
},
|
|
1428
|
+
{
|
|
1429
|
+
"label": "CVE-2024-39929 Exim MIME parser",
|
|
1430
|
+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-39929"
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
"label": "CVE-2025-30258 gnumail",
|
|
1434
|
+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-30258"
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
"label": "CVE-2021-42574 RTLO trojan source",
|
|
1438
|
+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-42574"
|
|
1439
|
+
}
|
|
1440
|
+
]
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
"version": "0.9.18",
|
|
1444
|
+
"date": "2026-05-14",
|
|
1445
|
+
"headline": "CodeQL alert sweep — 18 closures across 4 rule classes + SECURITY.md hardening checklist + MIGRATING.md out-of-band breaks",
|
|
1446
|
+
"summary": "Pre-existing CodeQL security findings on `main` accumulated over many releases, surfaced explicitly when the previous rename sweep changed line content. This release closes them all.",
|
|
1447
|
+
"sections": [
|
|
1448
|
+
{
|
|
1449
|
+
"heading": "Fixed",
|
|
1450
|
+
"items": [
|
|
1451
|
+
{
|
|
1452
|
+
"title": "`js/file-system-race` (6 sites)",
|
|
1453
|
+
"body": "TOCTOU between `fs.existsSync()` / `fs.statSync()` and a subsequent file op. Fixed via the framework's canonical TOCTOU-safe-read scaffold (open fd first then `fstatSync` then `readSync` loop then `closeSync` in `finally`) at `lib/atomic-file.js` (`_readSyncCore`), `lib/restore-rollback.js` (marker write switched to exclusive-create `wx` + EEXIST-tolerant), `lib/network-tls.js` (`_readPathFile` extraction with per-file ENOENT tolerance), `lib/backup/bundle.js` (open-fd-first plus required-vs-skip branch routing), `lib/static.js` (request-serve hot path narrowed to single fd). `lib/vault/seal-pem-file.js` retained as-is with a CodeQL suppression — the site has an in-line `lstat.ino === fstat.ino` inode-equality defense that refuses with `seal-pem-file/toctou-detected` if an attacker swaps the file between `lstat` and `open`."
|
|
1454
|
+
},
|
|
1455
|
+
{
|
|
1456
|
+
"title": "`js/insecure-temporary-file` (6 sites)",
|
|
1457
|
+
"body": "Predictable temp paths. `lib/vault/rotate.js` now uses `mkdtempSync` for a per-rotation random scratch dir + plain filenames inside (replaces the predictable `_blamejs_rotate.tmp.db` / `_blamejs_verify.tmp.db` paths in `stagingDir`). `lib/mtls-ca.js` switched to exclusive-create `openSync(..., \"wx\", 0o600)` + `writeSync` + `fsyncSync` so an attacker pre-creating the path is refused at `EEXIST`. `lib/atomic-file.js` (`fsyncDir`), `lib/vault/rotate.js` (`_fsyncFileByPath`), `lib/http-client.js` (atomic tmp path) retained as-is with suppressions — `dirPath` / `p` are operator-supplied framework data paths (not `os.tmpdir`-reachable), and `tmpPath` carries 16 hex chars of crypto-random suffix."
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
"title": "`js/path-injection` (2 sites in `lib/static.js`)",
|
|
1461
|
+
"body": "`nodeFs.createReadStream(absPath)` in `_readMeta` and the request-serve hot path. Suppression comments added referencing the upstream `_resolveSafe` lexical-resolve + `startsWith(rootResolved + nodePath.sep)` + realpath escape check — `absPath` is sandbox-validated against `root` before reaching these lines."
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
"title": "`js/remote-property-injection` (4 sites)",
|
|
1465
|
+
"body": "`lib/websocket.js` (`ext.params: {}` to `Object.create(null)`), `lib/middleware/csrf-protect.js` (`var out = {}` to `Object.create(null)` for cookie-parse output). `lib/middleware/body-parser.js` (multipart `fields[currentField] = ...`) retained as-is with suppression — `currentField` is gated upstream by `POISONED_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"])` refusing the field BEFORE assignment with a 400 BodyParserError."
|
|
1466
|
+
}
|
|
1467
|
+
]
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
"heading": "Added",
|
|
1471
|
+
"items": [
|
|
1472
|
+
{
|
|
1473
|
+
"title": "SECURITY.md hardening checklist gains 5 lines",
|
|
1474
|
+
"body": "Covers `b.middleware.idempotencyKey.dbStore` (hash + seal defaults), `b.metrics.snapshot` (out-of-process metrics export), `b.selfUpdate.standaloneVerifier` (zero-dep install-pipeline verifier), `b.pqcAgent.reload` (TLS-posture refresh without restart), `b.crypto.hashFilesParallel` (parallel SBOM/integrity-sweep hashing)."
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
"title": "MIGRATING.md — Out-of-band breaking changes section",
|
|
1478
|
+
"body": "The previous release's dbStore schema break is the first entry; `scripts/gen-migrating.js` extended with an `OUT_OF_BAND_BREAKS` table so future schema/on-disk format breaks land in MIGRATING.md without operators needing to grep CHANGELOG."
|
|
1479
|
+
}
|
|
1480
|
+
]
|
|
1481
|
+
}
|
|
1482
|
+
]
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
"version": "0.9.17",
|
|
1486
|
+
"date": "2026-05-14",
|
|
1487
|
+
"headline": "`node:` prefix consistency + internal-binding leak prevention — two new detectors + 192-site cleanup",
|
|
1488
|
+
"summary": "Two enforceable invariants the prior release's detectors didn't cover.",
|
|
1489
|
+
"sections": [
|
|
1490
|
+
{
|
|
1491
|
+
"heading": "Changed",
|
|
1492
|
+
"items": [
|
|
1493
|
+
{
|
|
1494
|
+
"title": "Every `require()` of a Node built-in uses the `node:` prefix",
|
|
1495
|
+
"body": "153 `require()` rewrites across 79 framework files. Three reasons: (a) userland packages on npm CAN be named after built-ins, so without the `node:` prefix a typo or `npm install` accident could shadow the built-in; (b) the prefix is a clearer at-a-glance signal that the dependency is on Node, not on a userland module; (c) bundler / SEA static-trace passes treat `node:` prefix as an unambiguous Node-builtin marker."
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
"title": "Two follow-on require-binding canonicalizations",
|
|
1499
|
+
"body": "`lib/ws-client.js` now destructures `var { EventEmitter } = require(\"node:events\")` (was binding the entire `events` module to a class-shaped name) and `lib/process-spawn.js` renames inline `nodeChild` to `childProcess` (matches the module-level `childProcess` lazyRequire in `lib/dev.js`)."
|
|
1500
|
+
}
|
|
1501
|
+
]
|
|
1502
|
+
},
|
|
1503
|
+
{
|
|
1504
|
+
"heading": "Detectors",
|
|
1505
|
+
"items": [
|
|
1506
|
+
{
|
|
1507
|
+
"title": "`node-builtin-prefix`",
|
|
1508
|
+
"body": "Refuses `require(\"<X>\")` of a Node built-in (`fs`, `path`, `crypto`, `stream`, `tls`, `url`, `os`, `net`, `http`, `http2`, `https`, `zlib`, `dgram`, `events`, `child_process`, `readline`, etc.) without the `node:` prefix. Skips JSDoc `@example` block continuation lines (`*`-prefixed), so operator-facing examples that show `var fs = require(\"fs\")` aren't rewritten — operators write their own bindings however they prefer."
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
"title": "`internal-binding-in-prose`",
|
|
1512
|
+
"body": "Internal binding names (`nodeFs` / `nodePath` / `nodeCrypto` / `nodeStream` / `nodeTls` / `nodeUrl` / `bCrypto` / `retryHelper`) must NOT appear in operator-facing surface: JSDoc/comment continuation lines or string literals (error messages, audit metadata). Operators see the public API name (`path` / `fs` / `crypto` / `retry` / etc.), never the framework's internal alias. 39 prose-leak fixes across 16 files."
|
|
1513
|
+
}
|
|
1514
|
+
]
|
|
1515
|
+
}
|
|
1516
|
+
]
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
"version": "0.9.16",
|
|
1520
|
+
"date": "2026-05-14",
|
|
1521
|
+
"headline": "Operator-facing prose cleanup + `require-binding-name` detector covers `lazyRequire` + `dbStore` seal round-trip test",
|
|
1522
|
+
"summary": "Three classes of follow-up to the previous release's framework-wide rename sweep.",
|
|
1523
|
+
"sections": [
|
|
1524
|
+
{
|
|
1525
|
+
"heading": "Fixed",
|
|
1526
|
+
"items": [
|
|
1527
|
+
{
|
|
1528
|
+
"title": "Operator-facing prose leaks in JSDoc + error messages",
|
|
1529
|
+
"body": "The previous release's mechanical rename pattern `<OLD>.` to `<NEW>.` also caught occurrences inside JSDoc `@opts` comments and error-message string literals, so operators reading `b.keychain.create(opts)` saw `// absolute nodePath; required if file fallback may engage` instead of `// absolute path`. Fixed in `lib/db.js` (stream-limit error), `lib/keychain.js` (fallback-file error + 3 JSDoc lines), `lib/restore-bundle.js` (staging-dir error), `lib/watcher.js` (fs.watch failure error). Operators see plain English; internal binding names stay internal."
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
"title": "`require-binding-name` detector extended to cover `lazyRequire`",
|
|
1533
|
+
"body": "The previous detector only matched plain `var X = require(\"M\")` and missed the framework's `var X = lazyRequire(function () { return require(\"M\"); })` pattern (used to break load cycles). 34 additional inconsistencies surfaced (`auditFwk` / `auditMod` / `auditModule` / `lazyAudit` to `audit`, `crypto` / `fwCrypto` to `bCrypto`, `dbMod` / `dbModule` to `db`, etc.) — every minority site renamed per the same canonical-name map."
|
|
1534
|
+
}
|
|
1535
|
+
]
|
|
1536
|
+
},
|
|
1537
|
+
{
|
|
1538
|
+
"heading": "Added",
|
|
1539
|
+
"items": [
|
|
1540
|
+
{
|
|
1541
|
+
"title": "`dbStore` seal round-trip test with vault initialized",
|
|
1542
|
+
"body": "The previous test suite covered seal-falls-back-when-vault-not-ready and cross-process-sealed-row-preserved, but did NOT exercise the actual default-on seal/unseal path because the test environment didn't `b.vault.init(...)`. New `testDbStoreSealRoundTripWithVault` bootstraps a plaintext vault, builds a dbStore with `seal: true`, writes a record + reads it back, and asserts (a) `headers` + `body` columns carry the `vault:` envelope on disk, (b) the round-trip restores the original values, and (c) `status_code` stays plaintext so forensic SELECTs still work without unsealing."
|
|
1543
|
+
}
|
|
1544
|
+
]
|
|
1545
|
+
}
|
|
1546
|
+
]
|
|
1547
|
+
},
|
|
1548
|
+
{
|
|
1549
|
+
"version": "0.9.15",
|
|
1550
|
+
"date": "2026-05-13",
|
|
1551
|
+
"headline": "`dbStore` hashes keys + seals body/headers by default + framework-wide `require()` binding-name consistency",
|
|
1552
|
+
"summary": "Closes two operator-surfaced gaps: PII leaking through plaintext idempotency-key columns and inconsistent require-binding names across the lib. The `tableName` column schema changes between releases.",
|
|
1553
|
+
"sections": [
|
|
1554
|
+
{
|
|
1555
|
+
"heading": "Changed",
|
|
1556
|
+
"items": [
|
|
1557
|
+
{
|
|
1558
|
+
"title": "`b.middleware.idempotencyKey.dbStore` — keys hashed + body/headers sealed by default",
|
|
1559
|
+
"body": "Operator-supplied idempotency keys sometimes carry PII (order numbers, emails, vendor prefixes); the `k` column previously stored them raw, leaving every DB dump as a PII surface. Now sha3-512 namespace-hashes the key via `b.crypto.namespaceHash(\"idempotency-key\", key)` before insert/lookup — round-trips are transparent (operators still pass raw keys), but the DB never sees the original. The schema also splits the previous single-`v` JSON-envelope column into discrete `fingerprint` / `status_code` / `headers` / `body` / `expires_at` columns; `headers` + `body` are sealed via `b.cryptoField.sealRow` (vault-managed AEAD envelope) when vault is initialized, so a DB dump leaks neither cached response bodies nor headers. Non-sealed columns stay forensic-queryable. Both defaults are operator-opt-out via `opts.hashKeys: false` and `opts.seal: false`; the seal path silently falls back to plaintext + emits an `idempotency.seal_skipped_no_vault` audit warning on first use when vault isn't ready, so test fixtures and boot scripts still work."
|
|
1560
|
+
},
|
|
1561
|
+
{
|
|
1562
|
+
"title": "Framework-wide `require()` binding-name consistency sweep",
|
|
1563
|
+
"body": "184 require-binding renames across 108 framework files. Node built-ins get a `node<X>` prefix (`nodeFs` / `nodePath` / `nodeCrypto` / `nodeStream` / `nodeTls` / `nodeUrl`) so a local var named `fs` / `path` / `crypto` can never shadow them; the framework's own `lib/crypto.js` binds as `bCrypto` (matches the `b.crypto` public-namespace shape and doesn't shadow node:crypto). Modules without a declared canonical fall back to majority-wins (most-sites name wins, alphabetical tiebreak). Fix is rename, not allowlist — every minority site was updated. `b.graphqlFederation` internal `_timingSafeEqual` now routes through `b.crypto.timingSafeEqual` (was re-implementing the length-tolerant wrapper inline)."
|
|
1564
|
+
}
|
|
1565
|
+
]
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
"heading": "Detectors",
|
|
1569
|
+
"items": [
|
|
1570
|
+
{
|
|
1571
|
+
"title": "`require-binding-name`",
|
|
1572
|
+
"body": "Enforces consistent `var X = require(\"M\")` names framework-wide via a `CANONICAL_REQUIRE_BINDINGS` map. Inconsistent names made `grep` across the lib unreliable and let reviewers miss shadowing bugs (`var crypto = require(\"crypto\")` collides with the framework's own `b.crypto`)."
|
|
1573
|
+
}
|
|
1574
|
+
]
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
"heading": "Migration",
|
|
1578
|
+
"items": [
|
|
1579
|
+
{
|
|
1580
|
+
"title": "Schema break — drop any existing `dbStore` table before upgrading",
|
|
1581
|
+
"body": "v0.9.15's split columns are incompatible with the previous single-`v` column. Operators run `DROP TABLE <tableName>;` (or pick a fresh `tableName`) before upgrading. Pre-v1 framework breaks across patch versions for security correctness."
|
|
1582
|
+
}
|
|
1583
|
+
]
|
|
1584
|
+
}
|
|
1585
|
+
]
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
"version": "0.9.14",
|
|
1589
|
+
"date": "2026-05-13",
|
|
1590
|
+
"headline": "`safeSql.quoteIdentifier` adopted framework-wide + raw-SQL-identifier detector + bundled republish of v0.9.13 surface",
|
|
1591
|
+
"summary": "A reviewer-flagged race in `dbStore.get` surfaced a wider gap — multiple framework primitives concatenated SQL identifiers raw. This release routes every such site through the existing `b.safeSql.quoteIdentifier`, adds a detector that seals the bug class, and re-ships the v0.9.13 surface plus three additional primitives that the previous publish's smoke gate prevented from reaching npm.",
|
|
1592
|
+
"sections": [
|
|
1593
|
+
{
|
|
1594
|
+
"heading": "Fixed",
|
|
1595
|
+
"items": [
|
|
1596
|
+
{
|
|
1597
|
+
"title": "`dbStore.get` expired-row cleanup scoped by observed `expires_at`",
|
|
1598
|
+
"body": "Previously the expired-row cleanup was an unconditional `DELETE WHERE k = ?` between SELECT and DELETE — in a multi-process deployment another process could upsert the same key in the race window, and the unconditional delete would erase the fresh row. Fix: scope the delete by the observed `expires_at`."
|
|
1599
|
+
},
|
|
1600
|
+
{
|
|
1601
|
+
"title": "Every `db.prepare(\"CREATE TABLE \" + tableName + ...)` site routes through `b.safeSql.quoteIdentifier`",
|
|
1602
|
+
"body": "Sites refactored: `lib/audit.js` segregation-of-duties trigger DDL, `lib/dsr.js` ticket store, `lib/inbox.js` message-receive table, `lib/middleware/idempotency-key.js` dbStore, `lib/vault/rotate.js` column-rotation DDL."
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
"title": "Bounded JSON parse for two file/DB-backed primitives",
|
|
1606
|
+
"body": "`b.metrics.snapshot.read` and `b.middleware.idempotencyKey.dbStore.get` previously used bare `JSON.parse` with `allow:bare-json-parse` markers; both are read by processes separate from where they were written (CLI/sidecar reads daemon-written snapshot; multi-process fleet shares DB) where a hostile or misbehaving writer could plant a multi-GB value and OOM the reader. Both now route through `b.safeJson.parse(raw, { maxBytes: 4 MiB })`."
|
|
1607
|
+
},
|
|
1608
|
+
{
|
|
1609
|
+
"title": "`b.apiSnapshot.read` now passes `maxBytes: 64 MiB` to `safeJson.parse`",
|
|
1610
|
+
"body": "The framework-generated snapshot file outgrew safeJson's 1 MiB default."
|
|
1611
|
+
}
|
|
1612
|
+
]
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
"heading": "Added",
|
|
1616
|
+
"items": [
|
|
1617
|
+
{
|
|
1618
|
+
"title": "`b.crypto.hashFilesParallel(filePaths, opts?)`",
|
|
1619
|
+
"body": "Parallel multi-digest hashing for many files in a single read pass per file. Worker-pool concurrency cap (default `min(8, paths.length)`, 1..256), operator-tunable `algorithms` list (default `[\"sha256\", \"sha3-512\"]`), optional `onProgress(completed, total)` callback (throws swallowed). Returns rows in the same order as input. The common consumer-side reason to reach for this is SBOM regeneration / vendor-data integrity sweeps / release-asset bundling — situations where N files each need both SHA-256 and SHA-3-512 digests."
|
|
1620
|
+
},
|
|
1621
|
+
{
|
|
1622
|
+
"title": "`b.pqcAgent.reload()` — TLS-posture refresh without restart",
|
|
1623
|
+
"body": "Tear down the lazily-built default agent and reset to null so the next `b.pqcAgent.agent` access rebuilds against current TLS posture + `b.network.tls.applyToContext` output. Long-running daemons that rotate the framework's TLS posture (TLS-pinset reload, certificate-pinset refresh, `C.TLS_GROUP_PREFERENCE` update behind a feature flag) need a way to re-source the outbound `https.Agent` without forking a new process. `reload()` calls `.destroy()` on the existing default agent (Node closes idle keep-alive sockets, lets in-flight sockets complete) then nulls the cache. Agents handed out via explicit `b.pqcAgent.create()` are unaffected. Returns `{ destroyed: boolean }`."
|
|
1624
|
+
},
|
|
1625
|
+
{
|
|
1626
|
+
"title": "`b.middleware.idempotencyKey.dbStore({ db, tableName?, init? })`",
|
|
1627
|
+
"body": "Persistent-backed store for the `idempotencyKey` middleware. Same three-method interface as `memoryStore` (`get` / `set` / `delete`) but stores records in any sqlite-shaped database (`{ prepare(sql) -> { run, get, all } }`) — the framework's internal `b.db`, an operator-supplied better-sqlite3 instance, or a custom adapter. Use this when (a) multiple processes share the request-handling fleet so retries can land on a different process than the original, (b) the daemon may restart between original and retry, or (c) audit / compliance review needs to walk historic idempotency-cache decisions. TTL is lazily enforced at read time; `set()` upserts on conflict so concurrent retries on different processes don't error. The `tableName` is validated via `b.safeSql.validateIdentifier` (ASCII identifier shape, 63-char cap, no reserved words)."
|
|
1628
|
+
}
|
|
1629
|
+
]
|
|
1630
|
+
},
|
|
1631
|
+
{
|
|
1632
|
+
"heading": "Detectors",
|
|
1633
|
+
"items": [
|
|
1634
|
+
{
|
|
1635
|
+
"title": "`raw-sql-identifier-interpolation`",
|
|
1636
|
+
"body": "Seals the bug class — refuses raw identifier concatenation into SQL strings. Variables whose names signal already-quoted identifiers (`q<X>` / `Q_<X>` / `quoted<X>` prefix) are skipped so future primitives that use the helper read naturally."
|
|
1637
|
+
}
|
|
1638
|
+
]
|
|
1639
|
+
},
|
|
1640
|
+
{
|
|
1641
|
+
"heading": "Migration",
|
|
1642
|
+
"items": [
|
|
1643
|
+
{
|
|
1644
|
+
"title": "Operators jump from v0.9.12 directly to v0.9.14",
|
|
1645
|
+
"body": "The previous release's npm-publish workflow failed at the wiki-e2e gate (a reviewer fix removed the `opts` parameter from `b.selfUpdate.standaloneVerifier.verify` but the `@signature` JSDoc still declared four arguments). v0.9.13's git tag and GitHub release reached operators but the npm tarball did not. v0.9.14 carries the entire v0.9.13 shipped surface plus the three additional primitives above."
|
|
1646
|
+
}
|
|
1647
|
+
]
|
|
1648
|
+
}
|
|
1649
|
+
]
|
|
1650
|
+
},
|
|
1651
|
+
{
|
|
1652
|
+
"version": "0.9.13",
|
|
1653
|
+
"date": "2026-05-13",
|
|
1654
|
+
"headline": "Circuit-breaker opts-shape fix + `b.selfUpdate.standaloneVerifier` + `b.metrics.snapshot` + `b.retry.withBreaker`",
|
|
1655
|
+
"summary": "Two existing-primitive fixes plus three new operator-facing primitives.",
|
|
1656
|
+
"sections": [
|
|
1657
|
+
{
|
|
1658
|
+
"heading": "Fixed",
|
|
1659
|
+
"items": [
|
|
1660
|
+
{
|
|
1661
|
+
"title": "`b.circuitBreaker.create({...})` accepts the documented opts-object call shape",
|
|
1662
|
+
"body": "The factory was rejecting the documented call shape with `name must be a non-empty string, got object` — every consumer following the docstring hit a hard error. The factory now reads `opts.name` (defaulting to empty string) and forwards to the internal `CircuitBreaker(name, opts)` constructor."
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
"title": "Circuit-breaker open-circuit error code documented to match runtime",
|
|
1666
|
+
"body": "The open-circuit error code was documented as `retry/circuit-open` but the runtime threw `CIRCUIT_OPEN`. The docstring is corrected to match the runtime; an alias rename will follow with a deprecation cycle in a future minor."
|
|
1667
|
+
}
|
|
1668
|
+
]
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
"heading": "Added",
|
|
1672
|
+
"items": [
|
|
1673
|
+
{
|
|
1674
|
+
"title": "`b.selfUpdate.standaloneVerifier` — zero-dep verifier for install-pipeline contexts",
|
|
1675
|
+
"body": "For contexts that run BEFORE the framework is installed (Dockerfile build stages, `install.sh`, `update.sh`, SEA-bundle verification at deploy time). Surface: `verify(assetPath, sigPath, pubkeyPem, opts?)` returns `{ ok, sha3_512, sha256, alg }`. Streams the asset in 64 KiB chunks through SHA-256 + SHA-3-512 + the signature verifier in parallel — multi-GB SEA bundles don't OOM the install runner. Supports ECDSA P-384 (both IEEE-P1363 96-byte and DER encodings), Ed25519, and ML-DSA-87. Detects signature format from length so `verifier.verify(...)` runs exactly once (calling it twice returns stale state and silently passes tampered assets). Module is hermetic: `node:crypto` + `node:fs` only, no framework imports. Operators copy the file via `cp \"$(node -p \"require('@blamejs/core').selfUpdate.standaloneVerifier.path\")\" install/standalone-verifier.js` into version control on their side."
|
|
1676
|
+
},
|
|
1677
|
+
{
|
|
1678
|
+
"title": "`b.metrics.snapshot` — out-of-process metrics export for long-running daemons",
|
|
1679
|
+
"body": "`startWriter({ path, intervalMs, fields })` atomically flushes a JSON snapshot (first flush synchronous so the file exists by return-time; subsequent flushes on the interval with `.unref()`; `stop()` clears + final-flushes). `read(path)` parses with shape validation (`writtenAt` + `fields`). `render(snap, { format, prefix })` produces operator-readable text or Prometheus 0.0.4 exposition (gauge metrics, prefixed; only finite numeric scalars with prom-compatible names emit; invalid-name / non-numeric / non-finite fields skipped silently). Lets a CLI process scrape a daemon's live metrics without opening an HTTP port."
|
|
1680
|
+
},
|
|
1681
|
+
{
|
|
1682
|
+
"title": "`b.retry.withBreaker(fn, { retry, breaker })` — composition primitive",
|
|
1683
|
+
"body": "Collapses the two-line wrapper every consumer rolls: `breaker.wrap(() => retry.withRetry(fn, opts.retry))`. One breaker call per retry loop (the retry budget is INSIDE the breaker's accounting, so a single transient burst doesn't open the breaker spuriously). Throws on non-function `fn` or breaker without `.wrap`."
|
|
1684
|
+
}
|
|
1685
|
+
]
|
|
1686
|
+
}
|
|
1687
|
+
]
|
|
1688
|
+
},
|
|
1689
|
+
{
|
|
1690
|
+
"version": "0.9.12",
|
|
1691
|
+
"date": "2026-05-13",
|
|
1692
|
+
"headline": "Republish of v0.9.10 / v0.9.11 — `npm audit signatures` grep widened for newer npm phrasing",
|
|
1693
|
+
"summary": "The publish workflow's `Verify npm registry signing chain` step treats an empty-tree result as success (the framework's zero-runtime-deps posture means `npm audit signatures --omit dev` finds nothing to audit). The exact phrasing has drifted across npm versions; the shell guard's grep only matched the older phrasing, so the previous publish failed at the audit-signatures gate.",
|
|
1694
|
+
"sections": [
|
|
1695
|
+
{
|
|
1696
|
+
"heading": "Fixed",
|
|
1697
|
+
"items": [
|
|
1698
|
+
{
|
|
1699
|
+
"title": "Audit-signatures empty-tree guard accepts both npm phrasings",
|
|
1700
|
+
"body": "Older npm prints `found no installed dependencies to audit`; newer npm prints `found no dependencies to audit that were installed from a supported registry`. The grep is now `no (installed )?dependencies to audit` — covers both known empty-tree variants. Functionally identical to the v0.9.10 intended surface. Operators stuck at v0.9.9 (because v0.9.10 + v0.9.11 never reached the npm registry) jump directly to v0.9.12."
|
|
1701
|
+
}
|
|
1702
|
+
]
|
|
1703
|
+
}
|
|
1704
|
+
]
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
"version": "0.9.11",
|
|
1708
|
+
"date": "2026-05-13",
|
|
1709
|
+
"headline": "Republish of v0.9.10 — `npm-publish.yml` installs devDependencies before smoke",
|
|
1710
|
+
"summary": "The previous release's tag was pushed and the GitHub release published, but `npm-publish.yml`'s Framework-smoke step ran `node test/smoke.js` without first running `npm install`. The new bundler-output gate requires `esbuild` (a devDependency); the publish workflow failed at the smoke step before reaching publish, so the npm tarball was never published.",
|
|
1711
|
+
"sections": [
|
|
1712
|
+
{
|
|
1713
|
+
"heading": "Fixed",
|
|
1714
|
+
"items": [
|
|
1715
|
+
{
|
|
1716
|
+
"title": "`npm install --no-audit --no-fund` runs before smoke in `npm-publish.yml`",
|
|
1717
|
+
"body": "Mirrors the same fix already present in `.github/workflows/ci.yml`. Operators consuming via `npm install @blamejs/core` should pull v0.9.11; functionally identical to the intended v0.9.10 surface. Zero runtime deps invariant preserved."
|
|
1718
|
+
}
|
|
1719
|
+
]
|
|
1720
|
+
}
|
|
1721
|
+
]
|
|
1722
|
+
},
|
|
1723
|
+
{
|
|
1724
|
+
"version": "0.9.10",
|
|
1725
|
+
"date": "2026-05-13",
|
|
1726
|
+
"headline": "Bundler-output integration gate — bundles + SEA produced and exercised in smoke",
|
|
1727
|
+
"summary": "Adds `test/layer-5-integration/bundler-output.test.js`. Bundles the framework via `esbuild --bundle --platform=node` (also `--minify`), runs the bundled consumer, and asserts the four-layer vendor-data integrity surface (dual-hash + SLH-DSA signature + canary) survives bundling.",
|
|
1728
|
+
"sections": [
|
|
1729
|
+
{
|
|
1730
|
+
"heading": "Added",
|
|
1731
|
+
"items": [
|
|
1732
|
+
{
|
|
1733
|
+
"title": "esbuild bundle gate + byte-search sentinel",
|
|
1734
|
+
"body": "The PSL canary roundtrips through `b.publicSuffix.isPublicSuffix(...)` after bundle exec — proves the `.data.js` payloads physically reached the bundle bytes, not just the runtime require shape. Plus a byte-search sentinel that greps the produced bundle for the canary tokens directly (defense-in-depth, independent failure mode from the runtime path)."
|
|
1735
|
+
},
|
|
1736
|
+
{
|
|
1737
|
+
"title": "SEA gate (Linux + Node >= 22)",
|
|
1738
|
+
"body": "Runs `--experimental-sea-config` + `postject` to produce an actual single-executable binary and runs it. The whole class of bugs — dynamic-require breaks bundling, SEA `assets` map missing, esbuild static-trace failures — is now smoke-gated. The previous release's defect produced bundles that exited with `vendor-data/module-missing` on first vendor-data access; this gate's `BUNDLE-OK psl=co.uk entries=3` stdout-check would have refused that exit at smoke time."
|
|
1739
|
+
}
|
|
1740
|
+
]
|
|
1741
|
+
}
|
|
1742
|
+
]
|
|
1743
|
+
},
|
|
1744
|
+
{
|
|
1745
|
+
"version": "0.9.9",
|
|
1746
|
+
"date": "2026-05-13",
|
|
1747
|
+
"headline": "`b.vendorData` — replace dynamic `require(variable)` with static literal-string requires so bundlers work",
|
|
1748
|
+
"summary": "The previous release looked up each `.data.js` module via `require(entry.module)` where `entry.module` was read from a frozen lookup table — a dynamic require, opaque to every bundler's static-analysis pass. esbuild, webpack, ncc, rollup, pkg, nexe, Bun's bundler, and Deno's bundler trace `require(\"./literal\")` calls only. The three payload modules never made it into SEA / pkg / esbuild bundles; consumers saw `vendor-data/module-missing` at boot.",
|
|
1749
|
+
"sections": [
|
|
1750
|
+
{
|
|
1751
|
+
"heading": "Fixed",
|
|
1752
|
+
"items": [
|
|
1753
|
+
{
|
|
1754
|
+
"title": "Static literal-string `require` for every vendor-data payload module",
|
|
1755
|
+
"body": "Replaces the lookup table with a `_MODULES` map whose three values are each a top-level `var X = require(\"./vendor/<name>.data\")` — literal string, statically traceable. Net surface change: zero (the public `b.vendorData.get` / `getAsString` / `verifyAll` / `inventory` shape is identical); the fix is internal-only. Operators upgrade if they bundle the framework via SEA / esbuild / pkg / Bun-compile — direct `node` consumers were unaffected (Node's runtime require always resolves dynamic strings correctly)."
|
|
1756
|
+
}
|
|
1757
|
+
]
|
|
1758
|
+
},
|
|
1759
|
+
{
|
|
1760
|
+
"heading": "Detectors",
|
|
1761
|
+
"items": [
|
|
1762
|
+
{
|
|
1763
|
+
"title": "`no-dynamic-requires` — refuses `require(variable)` in `lib/`",
|
|
1764
|
+
"body": "Any future site that reaches for a dynamic require trips the gate at n=1. Legitimate operator-extensibility points (`b.cli`, migrations, seeders) carry an explicit `allow:dynamic-require` marker with rationale."
|
|
1765
|
+
}
|
|
1766
|
+
]
|
|
1767
|
+
}
|
|
1768
|
+
]
|
|
1769
|
+
},
|
|
1770
|
+
{
|
|
1771
|
+
"version": "0.9.8",
|
|
1772
|
+
"date": "2026-05-13",
|
|
1773
|
+
"headline": "`b.vendorData` — packaging-mode-invariant signed loader for vendored data files",
|
|
1774
|
+
"summary": "Plaintext vendor data files (Public Suffix List, common-passwords list, BIMI trust anchors) are now loaded via inline base64 modules with four orthogonal integrity checks. Eliminates the `__dirname`-relative `fs.readFileSync` paths that broke under SEA, pkg, nexe, esbuild, Bun compile, Deno compile, and AWS Lambda layer bundling.",
|
|
1775
|
+
"sections": [
|
|
1776
|
+
{
|
|
1777
|
+
"heading": "Added",
|
|
1778
|
+
"items": [
|
|
1779
|
+
{
|
|
1780
|
+
"title": "`b.vendorData.get(name)` / `getAsString(name)` / `verifyAll()` / `inventory()`",
|
|
1781
|
+
"body": "`get` returns the verified Buffer; `getAsString` returns UTF-8 string; `verifyAll` runs all four integrity layers across every registered vendor data file and is invoked at framework boot; `inventory` returns per-file metadata (name, source, fetchedAt, sha256, sha3_512, signedBy, canary, byteLength, description) for compliance reporting + SBOM emission."
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
"title": "Four orthogonal integrity checks on every load",
|
|
1785
|
+
"body": "SHA-256 + SHA3-512 + SLH-DSA-SHAKE-256f signature against the maintainer's pinned public key (`lib/vendor/.vendor-data-pubkey`) + in-payload canary entry that the parsed structure must surface. Tamper at any layer throws `VendorDataError` at module-load — fail-fast rather than first-request-touches-PSL surprise. Adds a fourth orthogonal trust root alongside SSH-signed release tags, SLSA L3 npm provenance, and Sigstore-keyless SBOM signatures."
|
|
1786
|
+
},
|
|
1787
|
+
{
|
|
1788
|
+
"title": "Migrated call sites — PSL, common-passwords, BIMI trust anchors",
|
|
1789
|
+
"body": "`b.publicSuffix` (PSL load), `b.auth.password._loadBundledCommon` (common-passwords), `b.mail.bimi` (trust anchors) now route through `b.vendorData` — removes any downstream consumer's need to patch the loader for SEA / bundler builds."
|
|
1790
|
+
},
|
|
1791
|
+
{
|
|
1792
|
+
"title": "Maintainer signing infrastructure + new scripts",
|
|
1793
|
+
"body": "Vendor data files are signed at refresh time by a maintainer-held SLH-DSA-SHAKE-256f keypair (private key stays in `.keys/` and is never committed; public key ships in `lib/vendor/.vendor-data-pubkey` in every npm tarball). `scripts/vendor-data-keygen.js` (one-time keypair generation) + `scripts/vendor-data-gen.js` (generator invoked by `scripts/vendor-update.sh --refresh-data`). MANIFEST.json gains per-vendor-data `runtime_artifact` + `integrity_layers` + dual-file `hashes`."
|
|
1794
|
+
}
|
|
1795
|
+
]
|
|
1796
|
+
}
|
|
1797
|
+
]
|
|
1798
|
+
},
|
|
1799
|
+
{
|
|
1800
|
+
"version": "0.9.7",
|
|
1801
|
+
"date": "2026-05-13",
|
|
1802
|
+
"headline": "SECURITY.md release-tag verification recipe + signed-tag invariant from v0.9.7",
|
|
1803
|
+
"summary": "Documentation-only release. SECURITY.md gains a section on verifying release authenticity independently of GitHub's UI, and every release tag from this point forward is an annotated SSH-signed tag enforced by repository ruleset.",
|
|
1804
|
+
"sections": [
|
|
1805
|
+
{
|
|
1806
|
+
"heading": "Added",
|
|
1807
|
+
"items": [
|
|
1808
|
+
{
|
|
1809
|
+
"title": "SECURITY.md — `Verifying release authenticity` section",
|
|
1810
|
+
"body": "Documents how operators verify a release tag's authenticity independently of GitHub's UI. The maintainer Ed25519 SSH signing key fingerprint (`SHA256:5oF/XWhFpMde9TRfEX2GAHiApAq/MXOS4vti5zQbD7g`) is published alongside the public-key retrieval URL (`https://github.com/dotCooCoo.keys`) and a `git tag -v` recipe that bypasses the `Verified` badge."
|
|
1811
|
+
},
|
|
1812
|
+
{
|
|
1813
|
+
"title": "Signed-tag invariant from v0.9.7 onward",
|
|
1814
|
+
"body": "Every release tag is an annotated SSH-signed tag; the repository's `release-tags` ruleset's `required_signatures` rule refuses any unsigned or lightweight tag push at the server side. Earlier tags (v0.9.6 and prior) remain as lightweight commits and don't verify via `git tag -v`; they continue to verify via the SLSA L3 npm provenance + Sigstore-keyless SBOM signatures already attached to those releases (the `cosign verify-blob` recipe is in the same SECURITY.md section)."
|
|
1815
|
+
}
|
|
1816
|
+
]
|
|
1817
|
+
}
|
|
1818
|
+
]
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
"version": "0.9.6",
|
|
1822
|
+
"date": "2026-05-12",
|
|
1823
|
+
"headline": "`b.vex` OASIS CSAF 2.1 VEX statements + 25 new compliance postures",
|
|
1824
|
+
"summary": "Adds a vendor-side VEX emitter for the OASIS Common Security Advisory Framework profile, plus 25 framework / sectoral / supply-chain compliance postures wired through `b.compliance.KNOWN_POSTURES` with matching cascade defaults.",
|
|
1825
|
+
"sections": [
|
|
1826
|
+
{
|
|
1827
|
+
"heading": "Added",
|
|
1828
|
+
"items": [
|
|
1829
|
+
{
|
|
1830
|
+
"title": "`b.vex.statement({ cveId, status, productIds, justification?, impactStatement?, references?, firstReleased?, lastUpdated? })`",
|
|
1831
|
+
"body": "Builds an OASIS CSAF 2.1 §3.2.3 vulnerability statement with `product_status` keyed by status enum (`known_not_affected` / `affected` / `fixed` / `under_investigation`), `flags[].label` for §3.2.2.7 justifications (`component_not_present` / `vulnerable_code_not_present` / `vulnerable_code_not_in_execute_path` / `vulnerable_code_cannot_be_controlled_by_adversary` / `inline_mitigations_already_exist`), and `notes[].text` for impact narrative. Refuses missing CVE/CWE id, malformed CVE shape, unknown status, missing productIds, and `known_not_affected` without justification."
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
"title": "`b.vex.document({ documentId, title, publisher, trackingId, trackingVersion, currentReleaseDate, initialReleaseDate, statements, tlp? })`",
|
|
1835
|
+
"body": "Assembles the §3.2 CSAF document envelope with category `csaf_vex`, csaf_version `2.1`, publisher category `vendor`, tracking status `final`, and `distribution.tlp.label` from the TLP 2.0 vocabulary (`CLEAR` / `GREEN` / `AMBER` / `AMBER+STRICT` / `RED`; default `CLEAR`; refuses non-TLP labels). `cwes` is a list per §3.2.3.4; CWE alone is no longer accepted as a vulnerability identity per §3.2.3.2 (operator supplies `cveId` or `ids[]: [{ systemName, text }]` per §3.2.3.5). Public opt name is `cweId` to mirror `cveId`."
|
|
1836
|
+
},
|
|
1837
|
+
{
|
|
1838
|
+
"title": "`b.vex.serialize(doc)`",
|
|
1839
|
+
"body": "Routes through `b.canonicalJson.stringify` for byte-stable sorted-key output then re-indents at 2 spaces for human-diffable artifacts. Exports `STATUS_VALUES` / `JUSTIFICATION_VALUES` / `TLP_LABELS` / `CSAF_VERSION` / `VexError`."
|
|
1840
|
+
},
|
|
1841
|
+
{
|
|
1842
|
+
"title": "Compliance posture catalog gains 25 entries in `b.compliance.KNOWN_POSTURES`",
|
|
1843
|
+
"body": "With matching `POSTURE_DEFAULTS` cascade entries: `nist-800-53` (NIST SP 800-53 Rev 5 control catalog), `nist-ai-rmf-1.0` (NIST AI Risk Management Framework 1.0), `iso-42001-2023` (AI management systems), `iso-23894-2023` (AI risk management guidance), `owasp-llm-top-10-2025` (LLM application risk catalog), `owasp-asvs-v5.0` (Application Security Verification Standard v5.0), `nist-800-218-ssdf` (Secure Software Development Framework), `nist-800-82-r3` (industrial control systems), `nist-800-63b-rev4` (digital identity authenticator guidance), `iec-62443-3-3` (industrial security), `fedramp-rev5-moderate` (federal cloud baseline), `hipaa-security-rule` (45 CFR §164.302-318 administrative + technical safeguards), `hitrust-csf-v11.4`, `nerc-cip-007-6` (bulk electric system cyber asset security), `psd2-rts-sca`, `swift-cscf-v2026`, `slsa-v1.0-build-l3`, `vex-csaf-2.1`, `cyclonedx-v1.6`, `spdx-v3.0`, `owasp-wstg-v5`, `ptes`, `nist-800-115`, `cwe-top-25-2024`, `cis-controls-v8`, `cmmc-2.0-level-2`. Each cascade entry encodes the regime's data-tier mandate (encrypted backups + signed audit chain + TLS 1.3 minimum + vacuum-after-erase where applicable)."
|
|
1844
|
+
}
|
|
1845
|
+
]
|
|
1846
|
+
}
|
|
1847
|
+
],
|
|
1848
|
+
"references": [
|
|
1849
|
+
{
|
|
1850
|
+
"label": "OASIS CSAF 2.1 (Common Security Advisory Framework)",
|
|
1851
|
+
"url": "https://docs.oasis-open.org/csaf/csaf/v2.1/"
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
"label": "FIRST TLP 2.0 (Traffic Light Protocol)",
|
|
1855
|
+
"url": "https://www.first.org/tlp/"
|
|
1856
|
+
},
|
|
1857
|
+
{
|
|
1858
|
+
"label": "NIST SP 800-53 Rev 5",
|
|
1859
|
+
"url": "https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final"
|
|
1860
|
+
}
|
|
1861
|
+
]
|
|
1862
|
+
},
|
|
1863
|
+
{
|
|
1864
|
+
"version": "0.9.5",
|
|
1865
|
+
"date": "2026-05-12",
|
|
1866
|
+
"headline": "Fix-up for v0.9.3 + v0.9.4 — reachable opts, correct check-and-insert contract, CIBA interval-state leak",
|
|
1867
|
+
"summary": "Five reachability and contract bugs reported on the previous two releases.",
|
|
1868
|
+
"sections": [
|
|
1869
|
+
{
|
|
1870
|
+
"heading": "Fixed",
|
|
1871
|
+
"items": [
|
|
1872
|
+
{
|
|
1873
|
+
"title": "`b.middleware.dpop` `trustForwardedHeaders` reachable in validateOpts whitelist",
|
|
1874
|
+
"body": "The v0.9.4 X-Forwarded-* trust gate added the option to `_reconstructHtu` but the `create()` validateOpts whitelist still rejected unknown keys. Operators behind a trusted reverse proxy got `unknown-option` instead of the documented opt-in, leaving valid DPoP proofs failing htu matching. The whitelist now includes `trustForwardedHeaders`."
|
|
1875
|
+
},
|
|
1876
|
+
{
|
|
1877
|
+
"title": "`b.auth.jwt.verifyExternal` `allowKidlessJwks` reachable in validateOpts whitelist",
|
|
1878
|
+
"body": "Same shape as the dpop fix, fixed the same way."
|
|
1879
|
+
},
|
|
1880
|
+
{
|
|
1881
|
+
"title": "OAuth `allowKidlessJwks` now threads through token-exchange flows",
|
|
1882
|
+
"body": "Previously the opt was per-`verifyIdToken`-call, but `_normalizeTokens()` (called from `exchangeCode` / `pollDeviceCode` / `exchangeToken` / `refreshAccessToken`) passed a reduced `{ nonce, skipNonceCheck }` shape that dropped the operator opt. Surface promoted to client-level: pass `b.auth.oauth.create({ allowKidlessJwks: true })` once and it threads through every code path that lands on the verifier. The per-call `vopts.allowKidlessJwks` continues to work for direct `verifyIdToken` callers."
|
|
1883
|
+
},
|
|
1884
|
+
{
|
|
1885
|
+
"title": "`b.auth.oauth.refreshAccessToken` `checkAndInsert` return-value contract aligned",
|
|
1886
|
+
"body": "Previously interpreted `true` as already-seen but the framework-wide `checkAndInsert` contract (`b.nonceStore`, `b.auth.jwt`) is the opposite: `true` = unseen-and-now-inserted (first sighting), `false` = already-present (replay). Operators reusing an existing `b.nonceStore`-style backend got every first refresh attempt rejected as token theft, breaking normal refresh flows. The handler now normalizes `inserted === false` to `alreadySeen = true`, consistent with the rest of the framework."
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
"title": "`b.auth.ciba` `_intervalState` memory leak on error paths",
|
|
1890
|
+
"body": "Previously entries were only deleted on successful token issuance; denied / expired auth requests, and ping/push delivery modes that never call `pollToken` successfully, left permanent entries causing unbounded growth in long-running processes. Now entries carry an `expireAtMs` derived from the IdP-supplied `expires_in` of the auth_req_id, and an opportunistic sweep runs on every `_registerInitialInterval` call (no separate timer needed). Terminal CIBA errors (`expired_token` / `access_denied` / `invalid_grant` / `transaction_failed`) also delete the entry immediately on the error path."
|
|
1891
|
+
}
|
|
1892
|
+
]
|
|
1893
|
+
}
|
|
1894
|
+
]
|
|
1895
|
+
},
|
|
1896
|
+
{
|
|
1897
|
+
"version": "0.9.4",
|
|
1898
|
+
"date": "2026-05-12",
|
|
1899
|
+
"headline": "Auth hardening — kid-less JWKS refusal, OCSP nonce CT compare, OAuth scope strict-split, DPoP `X-Forwarded-Proto` trust gate",
|
|
1900
|
+
"summary": "Closes the remaining medium-tier findings from the federation-auth audit follow-through.",
|
|
1901
|
+
"sections": [
|
|
1902
|
+
{
|
|
1903
|
+
"heading": "Fixed",
|
|
1904
|
+
"items": [
|
|
1905
|
+
{
|
|
1906
|
+
"title": "`b.auth.oauth.verifyIdToken` + `b.auth.jwt.verifyExternal` refuse kid-less JWKS lookup",
|
|
1907
|
+
"body": "Previously both verifiers fell back to `keys[0]` when the token carried NO `kid` and the JWKS had exactly one key. This is a latent vector during JWKS rotation: an attacker shipping a kid-less token gets the lone-key path during the window the rotated-out key is still cached at the IdP but the rotated-in key is already published. Every modern IdP includes `kid`; the framework now refuses kid-less tokens unconditionally. Operators with non-conforming IdPs that genuinely emit kid-less tokens opt out via `vopts.allowKidlessJwks: true`."
|
|
1908
|
+
},
|
|
1909
|
+
{
|
|
1910
|
+
"title": "`b.network.tls` OCSP nonce constant-time compare",
|
|
1911
|
+
"body": "`evaluateOcspResponse`'s `expectedNonce` match migrated from `Buffer.equals` to `b.crypto.timingSafeEqual` for module-wide consistency with the Merkle-root / NTS-cookie / cert-fingerprint paths that already use `timingSafeEqual`."
|
|
1912
|
+
},
|
|
1913
|
+
{
|
|
1914
|
+
"title": "`b.auth.oauth` scope strict whitespace split",
|
|
1915
|
+
"body": "RFC 6749 §3.3 says `scope` is space-separated, ONLY `U+0020`. Previously `raw.scope.split(/\\s+/)` matched U+0085 NEL, U+00A0 NBSP, etc., so a hostile AS returning `scope: \"admin<NEL>read\"` would surface as `[\"admin\", \"read\"]` and the operator's scope allowlist saw two distinct scopes. Now splits on single-space only; empty pieces filtered out."
|
|
1916
|
+
},
|
|
1917
|
+
{
|
|
1918
|
+
"title": "`b.middleware.dpop` `X-Forwarded-*` trust gate",
|
|
1919
|
+
"body": "`_reconstructHtu` previously read `X-Forwarded-Proto` / `X-Forwarded-Host` unconditionally; an attacker who can hit the origin directly while spoofing `X-Forwarded-Proto: https` could trick the middleware into building an `https` htu that the DPoP proof was signed for, when the origin is actually serving HTTP (RFC 9449 §4.3 says the htu MUST be the absolute URL the request was sent to). The default now derives proto/host from the socket; operators with a confirmed-trusted front proxy opt in via `opts.trustForwardedHeaders: true`."
|
|
1920
|
+
}
|
|
1921
|
+
]
|
|
1922
|
+
}
|
|
1923
|
+
],
|
|
1924
|
+
"references": [
|
|
1925
|
+
{
|
|
1926
|
+
"label": "RFC 6749 §3.3 (OAuth scope)",
|
|
1927
|
+
"url": "https://www.rfc-editor.org/rfc/rfc6749#section-3.3"
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
"label": "RFC 9449 §4.3 (DPoP htu)",
|
|
1931
|
+
"url": "https://www.rfc-editor.org/rfc/rfc9449#section-4.3"
|
|
1932
|
+
}
|
|
1933
|
+
]
|
|
1934
|
+
},
|
|
1935
|
+
{
|
|
1936
|
+
"version": "0.9.3",
|
|
1937
|
+
"date": "2026-05-11",
|
|
1938
|
+
"headline": "Auth hardening — OAuth refresh atomic check-and-insert + OID4VCI/OID4VP/CIBA constant-time + slow_down honoring",
|
|
1939
|
+
"summary": "Continues the federation-auth audit follow-through with OAuth one-time-use refresh tokens, verifiable-credential constant-time compares, and CIBA back-off bookkeeping.",
|
|
1940
|
+
"sections": [
|
|
1941
|
+
{
|
|
1942
|
+
"heading": "Added",
|
|
1943
|
+
"items": [
|
|
1944
|
+
{
|
|
1945
|
+
"title": "`b.auth.oauth.refreshAccessToken` atomic check-and-insert",
|
|
1946
|
+
"body": "New `ropts.checkAndInsert(token, expireAtMs)` callback contract replaces the previous `ropts.seen(token)` check-then-act race. Two concurrent refresh requests on the same event-loop tick could both see `seen === false` and both POST to the token endpoint, neither flagging the replay; the new contract requires an atomic test-and-set (Redis SETNX, DB INSERT ON CONFLICT) and is the OAuth 2.1 §6.1 / RFC 9700 §4.13 one-time-use defense surfacing the actual race window. Legacy `seen` callback continues to work for backwards-compat with operator code; the docstring documents the race + recommends migration to `checkAndInsert`."
|
|
1947
|
+
}
|
|
1948
|
+
]
|
|
1949
|
+
},
|
|
1950
|
+
{
|
|
1951
|
+
"heading": "Fixed",
|
|
1952
|
+
"items": [
|
|
1953
|
+
{
|
|
1954
|
+
"title": "`b.auth.oid4vci` constant-time compares",
|
|
1955
|
+
"body": "Pre-auth `tx_code` hash compare (was `!==` on sha3 hex) and proof-JWT `c_nonce` compare (was `!==` on attacker-supplied wallet payload) both route through `b.crypto.timingSafeEqual`."
|
|
1956
|
+
},
|
|
1957
|
+
{
|
|
1958
|
+
"title": "`b.auth.oid4vp` per-presentation `vct` enforcement",
|
|
1959
|
+
"body": "DCQL filters with 2+ `vct_values` entries previously bypassed vct validation entirely (the framework only set `expectedVct` when the filter pinned to a single value). Verifier now validates the presented vct against the DCQL filter list manually when length > 1; refuses with `vp_token['<id>'][<n>] vct '<presented>' is not in DCQL vct_values [...]` on over-disclosure."
|
|
1960
|
+
},
|
|
1961
|
+
{
|
|
1962
|
+
"title": "`b.auth.ciba` slow_down honoring + back-off bookkeeping",
|
|
1963
|
+
"body": "CIBA §11.3 requires the client to increase its polling interval by at least 5s on every `slow_down` response. Previously the framework client never bumped, leaving operators to do their own interval bookkeeping. Now `pollToken()` tracks per-`authReqId` interval state internally (Map keyed by authReqId, seeded from `startAuthentication`'s response, cleared on token issuance), bumps by `max(5s, IdP-suggested interval) <= MAX_INTERVAL_SEC` on every slow_down, and attaches the next-suggested interval to the thrown `auth-ciba/slow_down` error as `err.nextIntervalSec` so operators read a spec-correct back-off without manual bookkeeping."
|
|
1964
|
+
},
|
|
1965
|
+
{
|
|
1966
|
+
"title": "`b.auth.ciba` notification-token entropy",
|
|
1967
|
+
"body": "`clientNotificationToken` now refuses < 32 chars per CIBA §7.1.2's opaque-hard-to-guess requirement. Previously a 4-char token passed."
|
|
1968
|
+
},
|
|
1969
|
+
{
|
|
1970
|
+
"title": "`b.auth.ciba.parseNotification` constant-time compare",
|
|
1971
|
+
"body": "Bearer-token hash compare migrated from `!==` to `b.crypto.timingSafeEqual` (both sides are fixed-width sha3-512 hex strings; defense-in-depth even though equal-length JS string compare is already widely understood as constant-time on V8)."
|
|
1972
|
+
}
|
|
1973
|
+
]
|
|
1974
|
+
}
|
|
1975
|
+
],
|
|
1976
|
+
"references": [
|
|
1977
|
+
{
|
|
1978
|
+
"label": "RFC 9700 §4.13 (OAuth security BCP — refresh token rotation)",
|
|
1979
|
+
"url": "https://www.rfc-editor.org/rfc/rfc9700.html"
|
|
1980
|
+
},
|
|
1981
|
+
{
|
|
1982
|
+
"label": "OpenID CIBA Core 1.0 §11.3 (token polling)",
|
|
1983
|
+
"url": "https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html"
|
|
1984
|
+
}
|
|
1985
|
+
]
|
|
1986
|
+
},
|
|
1987
|
+
{
|
|
1988
|
+
"version": "0.9.2",
|
|
1989
|
+
"date": "2026-05-11",
|
|
1990
|
+
"headline": "Auth hardening — WebAuthn counter regression, multi-origin passkey, MDS3 fail-closed, AAL3 user-verification gate",
|
|
1991
|
+
"summary": "Continues the federation-auth audit follow-through with six targeted fixes across `b.auth.passkey`, `b.auth.aal`, and `b.auth.fidoMds3`.",
|
|
1992
|
+
"sections": [
|
|
1993
|
+
{
|
|
1994
|
+
"heading": "Fixed",
|
|
1995
|
+
"items": [
|
|
1996
|
+
{
|
|
1997
|
+
"title": "`b.auth.passkey.verifyAuthentication` counter-regression bypass",
|
|
1998
|
+
"body": "The wrapper previously coerced `opts.credential.counter || 0`, silently zeroing an `undefined` / `null` / `NaN` counter and defeating CTAP 2.1 clone-detection on credentials whose stored counter was > 0. An operator deserializing the credential from a column that lost the counter would unknowingly accept a cloned authenticator. The wrapper now refuses `undefined` / `null` (operators MUST persist whatever the vendor returned at registration; first-time-stored credentials carry counter:0 explicitly) and rejects any non-integer / non-finite / negative value with `auth-passkey/bad-counter`."
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
"title": "`b.auth.passkey` prototype-pollution in `ALLOWED_MEDIATION` lookup",
|
|
2002
|
+
"body": "Lookup changed from `{...}[opts.mediation]` to `hasOwnProperty.call(ALLOWED_MEDIATION, opts.mediation)` with a null-prototype map. Pre-fix a caller passing `mediation: \"__proto__\"` / `\"constructor\"` truthy-matched an inherited Object.prototype property and slipped past the allowlist into `generateAuthenticationOptions`."
|
|
2003
|
+
},
|
|
2004
|
+
{
|
|
2005
|
+
"title": "`b.auth.aal.fromMethods` user-verification requirement for AAL3",
|
|
2006
|
+
"body": "Per NIST SP 800-63-4 §5.1.7, WebAuthn / passkey satisfies AAL3 (MF-CRYPT) only when user verification was performed on the assertion. Previously `fromMethods({ webauthn: true })` returned `AAL3` unconditionally; operators using `userVerification: \"preferred\"` whose authenticator skipped UV landed in AAL3 despite not satisfying the spec's MF requirement. The caller now passes `uv: true` (sourced from the vendor's authData UV bit) to claim AAL3 with webauthn alone; without `uv`, webauthn alone caps at AAL2 (SF-CRYPT). Combination paths (`webauthn + password` / `webauthn + pin`) reach AAL3 regardless of UV (the memorized secret provides the second factor independently)."
|
|
2007
|
+
},
|
|
2008
|
+
{
|
|
2009
|
+
"title": "`b.auth.fidoMds3.verifyAuthenticator` fail-closed default for unknown AAGUIDs",
|
|
2010
|
+
"body": "Previously unknown AAGUIDs returned `{ ok: true, reason: \"aaguid-not-in-blob\" }`, silently trusting any authenticator the metadata service hadn't yet listed (rogue / pre-certification / fake hardware). Now fails closed by default; operators wanting the legacy fail-open behavior (test fixtures, pre-certification pilot rollouts) pass `opts.allowUnknownAaguid: true` explicitly."
|
|
2011
|
+
},
|
|
2012
|
+
{
|
|
2013
|
+
"title": "`b.auth.fidoMds3.parseBlob` stale-BLOB refusal",
|
|
2014
|
+
"body": "Refuses BLOB payloads whose `nextUpdate` is already in the past (FIDO MDS3 §3.1.7). Previously staleness was floored to `MIN_CACHE_TTL_MS` but the BLOB was still served, letting an attacker pin operators to a revoked-authenticator-list-frozen-at-X by serving an ancient signed-but-expired BLOB."
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
"title": "`b.auth.fidoMds3.REFUSE_STATUS` adds `ATTESTATION_KEY_COMPROMISE`",
|
|
2018
|
+
"body": "Per FIDO MDS3 §3.1.4. Previously this status was silently accepted; manufacturer batch-signing-key compromise affects every credential attested under that key."
|
|
2019
|
+
}
|
|
2020
|
+
]
|
|
2021
|
+
},
|
|
2022
|
+
{
|
|
2023
|
+
"heading": "Added",
|
|
2024
|
+
"items": [
|
|
2025
|
+
{
|
|
2026
|
+
"title": "`b.auth.passkey` multi-origin support",
|
|
2027
|
+
"body": "`expectedOrigin` now accepts `string` OR `string[]` on both `verifyRegistration` and `verifyAuthentication`. Previously the wrapper enforced a single string only, blocking multi-origin deployments (web + admin-subdomain) from sharing one verifier; SimpleWebAuthn natively supports arrays."
|
|
2028
|
+
}
|
|
2029
|
+
]
|
|
2030
|
+
}
|
|
2031
|
+
],
|
|
2032
|
+
"references": [
|
|
2033
|
+
{
|
|
2034
|
+
"label": "NIST SP 800-63-4 §5.1.7 (AAL definitions)",
|
|
2035
|
+
"url": "https://csrc.nist.gov/pubs/sp/800/63/4/2pd"
|
|
2036
|
+
},
|
|
2037
|
+
{
|
|
2038
|
+
"label": "FIDO Metadata Service v3.0",
|
|
2039
|
+
"url": "https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html"
|
|
2040
|
+
}
|
|
2041
|
+
]
|
|
2042
|
+
},
|
|
2043
|
+
{
|
|
2044
|
+
"version": "0.9.1",
|
|
2045
|
+
"date": "2026-05-11",
|
|
2046
|
+
"headline": "Federation auth hardening — SAML SP / OIDC federation / SD-JWT VC / OAuth ID-token verifier",
|
|
2047
|
+
"summary": "Closes the highest-severity SAML, OIDC federation, SD-JWT VC, and OAuth findings from the 2026-05-11 federation auth review. XML attribute / element escaping for the SAML SP, JWK alg/kty cross-check for OIDC federation entity statements, claim-shadowing and disclosure-replay defenses for SD-JWT VC, and crit-header refusal plus constant-time compares for the OAuth ID-token verifier.",
|
|
2048
|
+
"sections": [
|
|
2049
|
+
{
|
|
2050
|
+
"heading": "Added",
|
|
2051
|
+
"items": [
|
|
2052
|
+
{
|
|
2053
|
+
"title": "`b.xmlC14n.escapeAttrValue(s)` / `escapeText(s)`",
|
|
2054
|
+
"body": "Newly exported XML escapers (RFC 3741 §1.3.x compliant). Available for any operator emitting XML alongside the framework's own SAML / canonicalization paths."
|
|
2055
|
+
}
|
|
2056
|
+
]
|
|
2057
|
+
},
|
|
2058
|
+
{
|
|
2059
|
+
"heading": "Fixed",
|
|
2060
|
+
"items": [
|
|
2061
|
+
{
|
|
2062
|
+
"title": "SAML SP `buildAuthnRequest` + `.metadata` operator-input XML escaping",
|
|
2063
|
+
"body": "Every operator-supplied URL / ID interpolated into the emitted XML now routes through `b.xmlC14n.escapeAttrValue` / `escapeText`. A `\"` or `<` in `idpSsoUrl` / `assertionConsumerServiceUrl` / `entityId` / `nameIdFormat` previously broke out of attribute / element context and produced unsigned-XML breakout into the IdP redirect."
|
|
2064
|
+
},
|
|
2065
|
+
{
|
|
2066
|
+
"title": "SAML SP `verifyResponse` constant-time digest compares",
|
|
2067
|
+
"body": "Digest compare on `Reference DigestValue` and `SubjectConfirmation InResponseTo` migrated from `Buffer.compare` / `!==` to `b.crypto.timingSafeEqual`."
|
|
2068
|
+
},
|
|
2069
|
+
{
|
|
2070
|
+
"title": "SAML XSW defense — single-child cardinality assertion",
|
|
2071
|
+
"body": "Refuses Response payloads that carry duplicate `<Status>`, `<StatusCode>`, `<Assertion>`, `<Subject>`, or `<NameID>` children. XML signature wrapping attacks ferry an unsigned sibling next to a signed element and exploit first-match parsers; the verifier now asserts single-child cardinality on every security-critical element via `_findAllChildren(...).length === 1`."
|
|
2072
|
+
},
|
|
2073
|
+
{
|
|
2074
|
+
"title": "`b.auth.openidFederation.verifyEntityStatement` alg/kty cross-check",
|
|
2075
|
+
"body": "JWK key-type cross-checked against the JWS `alg` header BEFORE `createPublicKey` runs. An attacker-controlled entity-config declaring `alg: \"ES256\"` while supplying an RSA JWK previously loaded through Node's silent algorithm-vs-key coercion path. Now refuses with `auth-openid-federation/alg-kty-mismatch` for any `alg=ES*` not paired with `kty=EC`, `alg=PS*`/`RS*` not paired with `RSA`, or `alg=EdDSA` not paired with `OKP`."
|
|
2076
|
+
},
|
|
2077
|
+
{
|
|
2078
|
+
"title": "`b.auth.openidFederation.buildTrustChain` error-masking removed",
|
|
2079
|
+
"body": "Trust-chain ascent previously swallowed every per-authority failure via `catch (_e) {}` and continued to the next `authority_hint`. Signature-failure errors from one authority no longer mask; the chain now refuses on cryptographic refusal (`bad-jwk`, `alg-kty-mismatch`, `bad-signature`, `signature-failed`). Network / 404 / iss-sub-mismatch errors still continue to the next hint but are collected and surfaced in the `no-ascent` failure shape."
|
|
2080
|
+
},
|
|
2081
|
+
{
|
|
2082
|
+
"title": "SD-JWT VC `_sd_alg` default switched to `sha-256`",
|
|
2083
|
+
"body": "Per IETF SD-JWT VC draft §4.1.1, the default `_sd_alg` is `sha-256`. The prior default of `sha3-512` broke verification against spec-conformant issuers when the issuer omitted `_sd_alg`."
|
|
2084
|
+
},
|
|
2085
|
+
{
|
|
2086
|
+
"title": "SD-JWT VC disclosure-replay defense",
|
|
2087
|
+
"body": "Every disclosure digest tracked in a Set; second occurrence of the same digest refuses with `auth-sd-jwt-vc/disclosure-replay`."
|
|
2088
|
+
},
|
|
2089
|
+
{
|
|
2090
|
+
"title": "SD-JWT VC claim-shadowing defense",
|
|
2091
|
+
"body": "Holder-supplied disclosures whose name collides with an issuer-signed top-level claim (`iss`, `sub`, `aud`, `iat`, `nbf`, `exp`, `jti`, `vct`, `cnf`, `_sd`, `_sd_alg`, `status`) refuse with `auth-sd-jwt-vc/protected-claim-shadow` instead of silently overwriting the signed value."
|
|
2092
|
+
},
|
|
2093
|
+
{
|
|
2094
|
+
"title": "SD-JWT VC KB-JWT `sd_hash` uses declared `_sd_alg`",
|
|
2095
|
+
"body": "Previously hardcoded sha256, breaking against issuers using sha3-512. `sd_hash` compare also routed through `b.crypto.timingSafeEqual`."
|
|
2096
|
+
},
|
|
2097
|
+
{
|
|
2098
|
+
"title": "OAuth `verifyIdToken` crash hardening",
|
|
2099
|
+
"body": "`createPublicKey + verify` wrapped in try/catch — previously panicked on key/sig shape mismatch (e.g. ES256 sig against an RS256 key returned by a buggy IdP with duplicate kids), bubbling a raw `Error` to the operator's handler instead of an `OAuthError`."
|
|
2100
|
+
},
|
|
2101
|
+
{
|
|
2102
|
+
"title": "OAuth `verifyIdToken` `crit` header refusal",
|
|
2103
|
+
"body": "Per RFC 7515 §4.1.11, every sibling verifier (`b.auth.jwt`, `b.auth.jwt.verifyExternal`, `b.auth.dpop`) refuses unknown crit extensions. `verifyIdToken` previously silently ignored, letting an attacker-controlled OP ship critical extensions the verifier doesn't understand."
|
|
2104
|
+
},
|
|
2105
|
+
{
|
|
2106
|
+
"title": "OAuth `verifyIdToken` constant-time state / nonce compares",
|
|
2107
|
+
"body": "`state` and `nonce` claim compares routed through `b.crypto.timingSafeEqual`. These are CSRF / replay tokens compared against attacker-controlled callback / payload data."
|
|
2108
|
+
}
|
|
2109
|
+
]
|
|
2110
|
+
}
|
|
2111
|
+
],
|
|
2112
|
+
"references": [
|
|
2113
|
+
{
|
|
2114
|
+
"label": "RFC 7515 JSON Web Signature",
|
|
2115
|
+
"url": "https://www.rfc-editor.org/rfc/rfc7515.html"
|
|
2116
|
+
},
|
|
2117
|
+
{
|
|
2118
|
+
"label": "OASIS SAML 2.0 Core",
|
|
2119
|
+
"url": "https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf"
|
|
2120
|
+
},
|
|
2121
|
+
{
|
|
2122
|
+
"label": "OpenID Federation 1.0",
|
|
2123
|
+
"url": "https://openid.net/specs/openid-federation-1_0.html"
|
|
2124
|
+
},
|
|
2125
|
+
{
|
|
2126
|
+
"label": "IETF SD-JWT-based Verifiable Credentials",
|
|
2127
|
+
"url": "https://datatracker.ietf.org/doc/draft-ietf-oauth-sd-jwt-vc/"
|
|
2128
|
+
}
|
|
2129
|
+
]
|
|
2130
|
+
},
|
|
2131
|
+
{
|
|
2132
|
+
"version": "0.9.0",
|
|
2133
|
+
"date": "2026-05-11",
|
|
2134
|
+
"headline": "Three new RFC primitives + `b.structuredFields` shared substrate + structured-fields hardening",
|
|
2135
|
+
"summary": "Minor release. Consolidates the quote-aware top-level splitter, control-byte refusal scan, and sf-string unquote used by every RFC 8941 / RFC 9110 / RFC 9111 / RFC 9213 / RFC 9421 / RFC 6266 / RFC 6265 / RFC 6455 parser into a shared `b.structuredFields` module. Closes eight structured-fields bug-class sites, adds three new RFC primitives (CDN cache-control, UA client hints, DNSSEC algorithm classifier), and lands five codebase-patterns detectors so the same shapes can't drift back in. Operators upgrade `0.8.90` → `0.9.0`; v0.8.91 was never tagged.",
|
|
2136
|
+
"sections": [
|
|
2137
|
+
{
|
|
2138
|
+
"heading": "Added",
|
|
2139
|
+
"items": [
|
|
2140
|
+
{
|
|
2141
|
+
"title": "`b.structuredFields` shared substrate",
|
|
2142
|
+
"body": "Exports `splitTopLevel(s, sep)` (quote-aware top-level splitter), `refuseControlBytes` / `containsControlBytes` (raw-value control-byte refusal scan), and `unquoteSfString` (sf-string unquote). Replaces the per-file open-coded copies that were drifting site-by-site across the RFC 8941 / RFC 9110 / RFC 9111 / RFC 9213 / RFC 9421 / RFC 6266 / RFC 6265 / RFC 6455 parsers."
|
|
2143
|
+
},
|
|
2144
|
+
{
|
|
2145
|
+
"title": "`b.cdnCacheControl` (RFC 9213)",
|
|
2146
|
+
"body": "Directive list builder + parser shared across `Cache-Control`, `CDN-Cache-Control`, `Surrogate-Control`, and the operator-specific `Cloudflare-` / `Vercel-` / `Fastly-` / `Akamai-` / `Netlify-CDN-Cache-Control` variants. `build({...})` emits the directive list string (numeric directives non-negative-integer-only; refuses Infinity / NaN / floats / negatives; full RFC 9111 boolean directive set; `extensions` for non-standard directives with RFC 7234 §5.2 token-shape enforcement); refuses `public + private` conflict per RFC 9111 §5.2.2.5/§5.2.2.6. `parse(headerValue)` decodes any targeted header into `{ public, private, noStore, maxAge, sMaxAge, ..., directives, fields }` with qualified-form support (`private=\"Authorization\"` flag stays enabled, field-name list under `.fields[camel]` per RFC 9111 §5.2.2.4 / §5.2.2.6), bare `max-stale` parses as `Infinity` per RFC 9111 §5.2.1.2, and a quote-aware top-level `,` splitter. `isTargetedHeader(name)` + curated `TARGETED_HEADERS` allowlist."
|
|
2147
|
+
},
|
|
2148
|
+
{
|
|
2149
|
+
"title": "`b.clientHints` (W3C UA Client Hints)",
|
|
2150
|
+
"body": "Sec-CH-UA-* request-header family parser per W3C UA Client Hints + IETF draft-davidben-http-client-hint-reliability. `parse(req.headers)` returns `{ brands, mobile, platform, platformVersion, arch, bitness, model, fullVersionList, wow64, formFactors, raw }`; quote-aware splitters at brand-list and brand-member-parameter level (RFC 8941 §4.1.1.4 parameter values may be sf-string); refuses control characters in any Sec-CH-* value. `acceptList(hintNames)` builds `Accept-CH` with typo-defense (unknown hint name throws `client-hints/unknown-hint`); dedupes case-variant duplicates; canonicalizes to W3C mixed-case spelling. `KNOWN_HINTS` exports the well-known 22-name list."
|
|
2151
|
+
},
|
|
2152
|
+
{
|
|
2153
|
+
"title": "`b.network.dns.classifyDnskeyAlgorithm` / `classifyDsDigestType`",
|
|
2154
|
+
"body": "RFC 9905 DNSSEC SHA-1 deprecation classifier. Covers every IANA-assigned DNSKEY algorithm (including PRIVATEDNS/PRIVATEOID/INDIRECT/Reserved entries) and RFC 9558 §3 DS digest types 5 (GOST R 34.11-2012) + 6 (SM3). Operators auditing inbound DNSSEC chain-of-trust evidence refuse validations where `deprecated === true`."
|
|
2155
|
+
}
|
|
2156
|
+
]
|
|
2157
|
+
},
|
|
2158
|
+
{
|
|
2159
|
+
"heading": "Fixed",
|
|
2160
|
+
"items": [
|
|
2161
|
+
{
|
|
2162
|
+
"title": "`b.middleware.bodyParser` Content-Type / Content-Disposition quoted-parameter handling",
|
|
2163
|
+
"body": "RFC 9110 Content-Type / RFC 6266 Content-Disposition parameters can carry quoted-string values (`boundary=\"foo;bar\"` / `filename=\"weird;name.txt\"`); bare `.split(\";\")` previously sliced through quoted semicolons and corrupted multipart boundaries. `_contentType` and `_parseHeaderParams` now route through the quote-aware splitter."
|
|
2164
|
+
},
|
|
2165
|
+
{
|
|
2166
|
+
"title": "`b.requestHelpers.parseListHeader({ strictToken: true })` trim-before-validate",
|
|
2167
|
+
"body": "Control-byte scan now runs on the RAW value before `.trim()` so a leading `\\n<token>` no longer slips past `RFC_9110_TOKEN_RE`. Used by webhook signature parsing and WS subprotocol negotiation."
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
"title": "`b.middleware.tusUpload._parseChecksumHeader` trim-before-validate",
|
|
2171
|
+
"body": "Same shape as the request-helpers fix — control-byte refusal scan runs on the RAW value before trim strips leading/trailing C0/DEL bytes."
|
|
2172
|
+
},
|
|
2173
|
+
{
|
|
2174
|
+
"title": "`b.httpClient.cache._parseCacheControl` quote-aware comma split",
|
|
2175
|
+
"body": "RFC 9111 §5.2 + RFC 9110 §5.6.4 directive values may be quoted-string; the parser now uses the shared quote-aware top-level `,` splitter."
|
|
2176
|
+
},
|
|
2177
|
+
{
|
|
2178
|
+
"title": "`b.httpClient.cookieJar._parseSetCookie` quote-aware semicolon split",
|
|
2179
|
+
"body": "Defends RFC 7230 quoted-string attribute values (`SameSite=\"Strict\"` from interop-imperfect upstreams) against bare `.split(\";\")` corruption."
|
|
2180
|
+
},
|
|
2181
|
+
{
|
|
2182
|
+
"title": "`b.websocket._parseExtensionHeader` quote-aware splits",
|
|
2183
|
+
"body": "Quote-aware `;` and `,` splitters defend RFC 6455 §9.1 + RFC 7230 token-or-quoted-string parameter values against forward-compat extensions shipping quoted params."
|
|
2184
|
+
},
|
|
2185
|
+
{
|
|
2186
|
+
"title": "`b.aiPref.parseHeader` control-byte refusal on raw value",
|
|
2187
|
+
"body": "Refusal scan added on the RAW value before split + trim so leading/trailing control bytes can no longer slip past the validator."
|
|
2188
|
+
},
|
|
2189
|
+
{
|
|
2190
|
+
"title": "`b.auth.stepUp.parseChallenge` trim-before-validate",
|
|
2191
|
+
"body": "Same trim-before-validate fix as the rest of the structured-fields parsers; returns `null` per defensive-reader contract instead of throwing."
|
|
2192
|
+
},
|
|
2193
|
+
{
|
|
2194
|
+
"title": "`b.logStream.init({ minLevel })` boot-time vocabulary validation",
|
|
2195
|
+
"body": "Validates the level vocabulary at config time so a typo'd `\"infos\"` (which previously produced `LEVEL_PRIORITY[\"infos\"] === undefined` and silently dropped every log record) throws at boot."
|
|
2196
|
+
},
|
|
2197
|
+
{
|
|
2198
|
+
"title": "`b.crypto.httpSig` RFC 9421 Signature-Input parameter parser",
|
|
2199
|
+
"body": "Now uses the shared quote-aware `;` splitter so RFC 8941 §3.1.2 sf-string parameter values parse correctly."
|
|
2200
|
+
},
|
|
2201
|
+
{
|
|
2202
|
+
"title": "`b.security.assertProductionPosture({ minTlsVersion })` vocabulary validation",
|
|
2203
|
+
"body": "Validates `minTlsVersion` against the canonical TLS vocabulary BEFORE the rank comparison. A typo previously silently passed because `indexOf` returned `-1` — same bug shape as the v0.8.88 `b.auth.fal.meets` fix."
|
|
2204
|
+
}
|
|
2205
|
+
]
|
|
2206
|
+
},
|
|
2207
|
+
{
|
|
2208
|
+
"heading": "Detectors",
|
|
2209
|
+
"items": [
|
|
2210
|
+
{
|
|
2211
|
+
"title": "`trim-before-validate`",
|
|
2212
|
+
"body": "Refuses any RFC structured-fields parser that runs control-byte validation AFTER `.trim()` strips leading/trailing C0/DEL bytes. Catches both the `charCodeAt` codepoint-loop shape AND the `<NAME>_RE.test(<trimmed>)` grammar-regex shape."
|
|
2213
|
+
},
|
|
2214
|
+
{
|
|
2215
|
+
"title": "`enum-rank-without-validation`",
|
|
2216
|
+
"body": "Refuses `_rankFn(X) >= _rankFn(Y)` arithmetic comparisons without a preceding `isValid*` / `KNOWN_*` membership check on both inputs. Catches the `b.auth.fal.meets` bug shape where `indexOf` returning `-1` for an unknown value silently passed rank checks."
|
|
2217
|
+
},
|
|
2218
|
+
{
|
|
2219
|
+
"title": "`bool-string-coerce-shape`",
|
|
2220
|
+
"body": "Refuses boolean directive parsing that uses `val === \"\" || val === \"true\"` coercion. Catches the `b.cdnCacheControl.parse` qualified-form bug shape."
|
|
2221
|
+
},
|
|
2222
|
+
{
|
|
2223
|
+
"title": "`bare-split-on-quoted-header`",
|
|
2224
|
+
"body": "RFC structured-fields parsers in files that also handle sf-string unquote regex must use `b.structuredFields.splitTopLevel`, not bare `.split(\",\")` / `.split(\";\")`. Inline `allow:bare-split-on-quoted-header` markers added across `mail-auth.js` (DKIM / DMARC / ARC tag-list grammar — token-only), `network-smtp-policy.js` (TLS-RPT — token-only), `middleware/scim-server.js` (RFC 7644 §3.9 SCIM attribute paths), `http-client-cache.js` (RFC 9110 §12.5.5 Vary field-names), `http-message-signature.js` (RFC 9421 component-id covered list), `middleware/body-parser.js` (RFC 9112 §6.1 Transfer-Encoding token-only), each citing the controlling RFC clause showing why quoted-string is not a legal value in that grammar."
|
|
2225
|
+
},
|
|
2226
|
+
{
|
|
2227
|
+
"title": "`scoped-context-binding-unused`",
|
|
2228
|
+
"body": "Scope-named factory bindings (`forwarderDomain` / `realm` / `origin` / `audience` / `issuer`) captured in the factory must be compared against the inbound value's embedded scope in the `verify` / `reverse` / `decode` path. Catches the v0.8.89 SRS forwarder-domain bug shape."
|
|
2229
|
+
}
|
|
2230
|
+
]
|
|
2231
|
+
}
|
|
2232
|
+
],
|
|
2233
|
+
"references": [
|
|
2234
|
+
{
|
|
2235
|
+
"label": "RFC 8941 Structured Field Values",
|
|
2236
|
+
"url": "https://www.rfc-editor.org/rfc/rfc8941.html"
|
|
2237
|
+
},
|
|
2238
|
+
{
|
|
2239
|
+
"label": "RFC 9111 HTTP Caching",
|
|
2240
|
+
"url": "https://www.rfc-editor.org/rfc/rfc9111.html"
|
|
2241
|
+
},
|
|
2242
|
+
{
|
|
2243
|
+
"label": "RFC 9213 Targeted HTTP Cache Control",
|
|
2244
|
+
"url": "https://www.rfc-editor.org/rfc/rfc9213.html"
|
|
2245
|
+
},
|
|
2246
|
+
{
|
|
2247
|
+
"label": "RFC 9905 DNSSEC SHA-1 deprecation",
|
|
2248
|
+
"url": "https://www.rfc-editor.org/rfc/rfc9905.html"
|
|
2249
|
+
},
|
|
2250
|
+
{
|
|
2251
|
+
"label": "W3C UA Client Hints",
|
|
2252
|
+
"url": "https://wicg.github.io/ua-client-hints/"
|
|
2253
|
+
}
|
|
2254
|
+
]
|
|
2255
|
+
}
|
|
2256
|
+
]
|
|
2257
|
+
}
|