@blamejs/blamejs-shop 0.4.56 → 0.4.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/lib/affiliates.js +35 -4
- package/lib/api-keys.js +17 -2
- package/lib/asset-manifest.json +1 -1
- package/lib/backorder.js +21 -4
- package/lib/click-and-collect.js +11 -2
- package/lib/credit-limits.js +132 -84
- package/lib/vendor/MANIFEST.json +426 -366
- package/lib/vendor/blamejs/.github/codeql/codeql-config.yml +25 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +1 -1
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +1 -1
- package/lib/vendor/blamejs/.github/workflows/ci.yml +13 -13
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +8 -7
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +2 -2
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +4 -4
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +1 -1
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +1 -1
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/api-snapshot.json +1381 -4
- package/lib/vendor/blamejs/eslint.config.mjs +1 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +4 -1
- package/lib/vendor/blamejs/examples/wiki/lib/symbol-index.js +5 -2
- package/lib/vendor/blamejs/examples/wiki/routes/pages.js +4 -1
- package/lib/vendor/blamejs/fuzz/guard-text.fuzz.js +20 -0
- package/lib/vendor/blamejs/index.js +2 -0
- package/lib/vendor/blamejs/lib/a2a-tasks.js +7 -23
- package/lib/vendor/blamejs/lib/acme.js +6 -5
- package/lib/vendor/blamejs/lib/agent-event-bus.js +5 -4
- package/lib/vendor/blamejs/lib/agent-idempotency.js +2 -5
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +2 -5
- package/lib/vendor/blamejs/lib/agent-saga.js +3 -5
- package/lib/vendor/blamejs/lib/agent-tenant.js +2 -5
- package/lib/vendor/blamejs/lib/ai-adverse-decision.js +2 -15
- package/lib/vendor/blamejs/lib/ai-capability.js +1 -6
- package/lib/vendor/blamejs/lib/ai-dp.js +1 -5
- package/lib/vendor/blamejs/lib/ai-input.js +2 -2
- package/lib/vendor/blamejs/lib/ai-pref.js +3 -8
- package/lib/vendor/blamejs/lib/ai-quota.js +3 -14
- package/lib/vendor/blamejs/lib/api-key.js +37 -28
- package/lib/vendor/blamejs/lib/archive-adapters.js +2 -4
- package/lib/vendor/blamejs/lib/archive-entry-policy.js +32 -0
- package/lib/vendor/blamejs/lib/archive-read.js +5 -17
- package/lib/vendor/blamejs/lib/archive-tar-read.js +5 -16
- package/lib/vendor/blamejs/lib/archive.js +2 -10
- package/lib/vendor/blamejs/lib/arg-parser.js +7 -6
- package/lib/vendor/blamejs/lib/asyncapi-traits.js +2 -6
- package/lib/vendor/blamejs/lib/atomic-file.js +108 -31
- package/lib/vendor/blamejs/lib/audit-chain.js +133 -53
- package/lib/vendor/blamejs/lib/audit-daily-review.js +24 -14
- package/lib/vendor/blamejs/lib/audit-emit.js +82 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +257 -0
- package/lib/vendor/blamejs/lib/audit.js +84 -0
- package/lib/vendor/blamejs/lib/auth/access-lock.js +5 -27
- package/lib/vendor/blamejs/lib/auth/dpop.js +23 -35
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +9 -15
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +26 -8
- package/lib/vendor/blamejs/lib/auth/jwt.js +13 -15
- package/lib/vendor/blamejs/lib/auth/lockout.js +6 -25
- package/lib/vendor/blamejs/lib/auth/oauth.js +67 -45
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +55 -32
- package/lib/vendor/blamejs/lib/auth/oid4vp.js +3 -2
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +6 -6
- package/lib/vendor/blamejs/lib/auth/passkey.js +3 -3
- package/lib/vendor/blamejs/lib/auth/password.js +7 -1
- package/lib/vendor/blamejs/lib/auth/saml.js +37 -27
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +1 -1
- package/lib/vendor/blamejs/lib/auth/status-list.js +7 -7
- package/lib/vendor/blamejs/lib/auth/step-up.js +6 -12
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +6 -37
- package/lib/vendor/blamejs/lib/backup/bundle.js +11 -18
- package/lib/vendor/blamejs/lib/backup/index.js +14 -47
- package/lib/vendor/blamejs/lib/bounded-map.js +112 -1
- package/lib/vendor/blamejs/lib/breach-deadline.js +1 -11
- package/lib/vendor/blamejs/lib/cache.js +7 -18
- package/lib/vendor/blamejs/lib/cbor.js +2 -1
- package/lib/vendor/blamejs/lib/cdn-cache-control.js +8 -9
- package/lib/vendor/blamejs/lib/cert.js +3 -10
- package/lib/vendor/blamejs/lib/chain-writer.js +162 -47
- package/lib/vendor/blamejs/lib/cli.js +5 -1
- package/lib/vendor/blamejs/lib/client-hints.js +7 -9
- package/lib/vendor/blamejs/lib/cloud-events.js +40 -31
- package/lib/vendor/blamejs/lib/cluster-storage.js +2 -38
- package/lib/vendor/blamejs/lib/cms-codec.js +2 -1
- package/lib/vendor/blamejs/lib/codepoint-class.js +67 -1
- package/lib/vendor/blamejs/lib/compliance-ai-act-logging.js +1 -6
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/vendor/blamejs/lib/compliance-eaa.js +1 -11
- package/lib/vendor/blamejs/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/vendor/blamejs/lib/compliance-sanctions.js +2 -14
- package/lib/vendor/blamejs/lib/compliance.js +2 -9
- package/lib/vendor/blamejs/lib/config-drift.js +2 -12
- package/lib/vendor/blamejs/lib/content-digest.js +10 -8
- package/lib/vendor/blamejs/lib/cookies.js +5 -11
- package/lib/vendor/blamejs/lib/cose.js +19 -11
- package/lib/vendor/blamejs/lib/cra-report.js +1 -11
- package/lib/vendor/blamejs/lib/crypto-field.js +5 -10
- package/lib/vendor/blamejs/lib/crypto.js +120 -3
- package/lib/vendor/blamejs/lib/csp.js +235 -3
- package/lib/vendor/blamejs/lib/daemon.js +42 -41
- package/lib/vendor/blamejs/lib/data-act.js +19 -9
- package/lib/vendor/blamejs/lib/db-query.js +6 -40
- package/lib/vendor/blamejs/lib/db.js +34 -12
- package/lib/vendor/blamejs/lib/dbsc.js +5 -6
- package/lib/vendor/blamejs/lib/ddl-change-control.js +18 -14
- package/lib/vendor/blamejs/lib/deprecate.js +4 -5
- package/lib/vendor/blamejs/lib/did.js +6 -2
- package/lib/vendor/blamejs/lib/dsr.js +8 -12
- package/lib/vendor/blamejs/lib/external-db-migrate.js +21 -22
- package/lib/vendor/blamejs/lib/external-db.js +14 -26
- package/lib/vendor/blamejs/lib/fda-21cfr11.js +12 -8
- package/lib/vendor/blamejs/lib/fdx.js +22 -18
- package/lib/vendor/blamejs/lib/file-upload.js +80 -66
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +5 -8
- package/lib/vendor/blamejs/lib/framework-error.js +12 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +19 -0
- package/lib/vendor/blamejs/lib/fsm.js +7 -12
- package/lib/vendor/blamejs/lib/gate-contract.js +869 -38
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +4 -13
- package/lib/vendor/blamejs/lib/graphql-federation.js +1 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +2 -1
- package/lib/vendor/blamejs/lib/guard-all.js +1 -0
- package/lib/vendor/blamejs/lib/guard-archive.js +9 -30
- package/lib/vendor/blamejs/lib/guard-auth.js +23 -80
- package/lib/vendor/blamejs/lib/guard-cidr.js +20 -96
- package/lib/vendor/blamejs/lib/guard-csv.js +135 -196
- package/lib/vendor/blamejs/lib/guard-domain.js +23 -106
- package/lib/vendor/blamejs/lib/guard-dsn.js +16 -13
- package/lib/vendor/blamejs/lib/guard-email.js +46 -53
- package/lib/vendor/blamejs/lib/guard-envelope.js +1 -1
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +2 -1
- package/lib/vendor/blamejs/lib/guard-filename.js +12 -60
- package/lib/vendor/blamejs/lib/guard-graphql.js +28 -75
- package/lib/vendor/blamejs/lib/guard-html-wcag.js +15 -2
- package/lib/vendor/blamejs/lib/guard-html.js +65 -117
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +2 -1
- package/lib/vendor/blamejs/lib/guard-image.js +280 -77
- package/lib/vendor/blamejs/lib/guard-imap-command.js +8 -9
- package/lib/vendor/blamejs/lib/guard-json.js +87 -103
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +20 -88
- package/lib/vendor/blamejs/lib/guard-jwt.js +32 -114
- package/lib/vendor/blamejs/lib/guard-list-id.js +2 -7
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +2 -7
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +5 -6
- package/lib/vendor/blamejs/lib/guard-mail-move.js +2 -1
- package/lib/vendor/blamejs/lib/guard-mail-query.js +5 -3
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +6 -7
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +5 -4
- package/lib/vendor/blamejs/lib/guard-markdown.js +76 -110
- package/lib/vendor/blamejs/lib/guard-message-id.js +5 -6
- package/lib/vendor/blamejs/lib/guard-mime.js +20 -99
- package/lib/vendor/blamejs/lib/guard-oauth.js +19 -73
- package/lib/vendor/blamejs/lib/guard-pdf.js +65 -72
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +12 -13
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +2 -1
- package/lib/vendor/blamejs/lib/guard-regex.js +24 -99
- package/lib/vendor/blamejs/lib/guard-saga-config.js +2 -1
- package/lib/vendor/blamejs/lib/guard-shell.js +22 -87
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +8 -11
- package/lib/vendor/blamejs/lib/guard-sql.js +15 -13
- package/lib/vendor/blamejs/lib/guard-stream-args.js +2 -1
- package/lib/vendor/blamejs/lib/guard-svg.js +95 -140
- package/lib/vendor/blamejs/lib/guard-template.js +23 -80
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +2 -1
- package/lib/vendor/blamejs/lib/guard-text.js +592 -0
- package/lib/vendor/blamejs/lib/guard-time.js +27 -95
- package/lib/vendor/blamejs/lib/guard-uuid.js +21 -93
- package/lib/vendor/blamejs/lib/guard-xml.js +76 -106
- package/lib/vendor/blamejs/lib/guard-yaml.js +24 -60
- package/lib/vendor/blamejs/lib/http-client-cache.js +5 -12
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +2 -4
- package/lib/vendor/blamejs/lib/http-client.js +8 -21
- package/lib/vendor/blamejs/lib/http-message-signature.js +3 -2
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +5 -7
- package/lib/vendor/blamejs/lib/i18n.js +83 -26
- package/lib/vendor/blamejs/lib/inbox.js +8 -8
- package/lib/vendor/blamejs/lib/incident-report.js +3 -21
- package/lib/vendor/blamejs/lib/ip-utils.js +49 -6
- package/lib/vendor/blamejs/lib/jobs.js +3 -2
- package/lib/vendor/blamejs/lib/keychain.js +6 -18
- package/lib/vendor/blamejs/lib/legal-hold.js +6 -15
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +44 -112
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +17 -14
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +16 -80
- package/lib/vendor/blamejs/lib/log-stream-webhook.js +20 -92
- package/lib/vendor/blamejs/lib/mail-arc-sign.js +8 -3
- package/lib/vendor/blamejs/lib/mail-arf.js +3 -2
- package/lib/vendor/blamejs/lib/mail-auth.js +40 -66
- package/lib/vendor/blamejs/lib/mail-bimi.js +19 -39
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +3 -2
- package/lib/vendor/blamejs/lib/mail-dav.js +8 -35
- package/lib/vendor/blamejs/lib/mail-deploy.js +6 -9
- package/lib/vendor/blamejs/lib/mail-dkim.js +19 -38
- package/lib/vendor/blamejs/lib/mail-greylist.js +15 -26
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -3
- package/lib/vendor/blamejs/lib/mail-journal.js +2 -1
- package/lib/vendor/blamejs/lib/mail-mdn.js +4 -3
- package/lib/vendor/blamejs/lib/mail-rbl.js +5 -8
- package/lib/vendor/blamejs/lib/mail-scan.js +2 -2
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +33 -20
- package/lib/vendor/blamejs/lib/mail-server-imap.js +32 -87
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +35 -51
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +29 -83
- package/lib/vendor/blamejs/lib/mail-server-mx.js +33 -74
- package/lib/vendor/blamejs/lib/mail-server-net.js +177 -0
- package/lib/vendor/blamejs/lib/mail-server-pop3.js +30 -83
- package/lib/vendor/blamejs/lib/mail-server-rate-limit.js +30 -6
- package/lib/vendor/blamejs/lib/mail-server-submission.js +34 -73
- package/lib/vendor/blamejs/lib/mail-server-tls.js +89 -0
- package/lib/vendor/blamejs/lib/mail-spam-score.js +7 -8
- package/lib/vendor/blamejs/lib/mail-store.js +3 -2
- package/lib/vendor/blamejs/lib/mail.js +6 -11
- package/lib/vendor/blamejs/lib/markup-escape.js +31 -0
- package/lib/vendor/blamejs/lib/markup-tokenizer.js +54 -0
- package/lib/vendor/blamejs/lib/mcp-tool-registry.js +26 -23
- package/lib/vendor/blamejs/lib/mcp.js +1 -1
- package/lib/vendor/blamejs/lib/mdoc.js +11 -12
- package/lib/vendor/blamejs/lib/metrics.js +32 -30
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +3 -23
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +11 -22
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +2 -7
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +31 -48
- package/lib/vendor/blamejs/lib/middleware/bot-disclose.js +7 -10
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +2 -10
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +13 -2
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +6 -26
- package/lib/vendor/blamejs/lib/middleware/deny-response.js +19 -1
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +7 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +4 -20
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +20 -28
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +9 -1
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +2 -6
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +12 -27
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +2 -7
- package/lib/vendor/blamejs/lib/migrations.js +4 -13
- package/lib/vendor/blamejs/lib/mime-parse.js +34 -17
- package/lib/vendor/blamejs/lib/module-loader.js +44 -0
- package/lib/vendor/blamejs/lib/money.js +105 -0
- package/lib/vendor/blamejs/lib/mtls-ca.js +116 -36
- package/lib/vendor/blamejs/lib/network-byte-quota.js +4 -18
- package/lib/vendor/blamejs/lib/network-dane.js +8 -6
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +97 -6
- package/lib/vendor/blamejs/lib/network-dnssec.js +16 -16
- package/lib/vendor/blamejs/lib/network-heartbeat.js +3 -2
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +29 -41
- package/lib/vendor/blamejs/lib/network-tls.js +40 -42
- package/lib/vendor/blamejs/lib/nis2-report.js +1 -11
- package/lib/vendor/blamejs/lib/nonce-store.js +81 -3
- package/lib/vendor/blamejs/lib/numeric-bounds.js +22 -8
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +5 -45
- package/lib/vendor/blamejs/lib/object-store/gcs.js +8 -71
- package/lib/vendor/blamejs/lib/object-store/http-put.js +1 -5
- package/lib/vendor/blamejs/lib/object-store/http-request.js +128 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +9 -77
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -25
- package/lib/vendor/blamejs/lib/observability.js +95 -0
- package/lib/vendor/blamejs/lib/openapi-paths-builder.js +7 -3
- package/lib/vendor/blamejs/lib/otel-export.js +7 -10
- package/lib/vendor/blamejs/lib/outbox.js +43 -37
- package/lib/vendor/blamejs/lib/pagination.js +11 -15
- package/lib/vendor/blamejs/lib/parsers/safe-env.js +5 -4
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +10 -4
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +11 -9
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +2 -2
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +7 -6
- package/lib/vendor/blamejs/lib/pick.js +63 -5
- package/lib/vendor/blamejs/lib/pipl-cn.js +69 -59
- package/lib/vendor/blamejs/lib/privacy-pass.js +8 -6
- package/lib/vendor/blamejs/lib/problem-details.js +2 -5
- package/lib/vendor/blamejs/lib/pubsub.js +4 -8
- package/lib/vendor/blamejs/lib/redact.js +2 -1
- package/lib/vendor/blamejs/lib/render.js +4 -2
- package/lib/vendor/blamejs/lib/request-helpers.js +133 -6
- package/lib/vendor/blamejs/lib/restore.js +5 -22
- package/lib/vendor/blamejs/lib/retention.js +3 -5
- package/lib/vendor/blamejs/lib/safe-async.js +457 -0
- package/lib/vendor/blamejs/lib/safe-buffer.js +137 -6
- package/lib/vendor/blamejs/lib/safe-decompress.js +2 -1
- package/lib/vendor/blamejs/lib/safe-dns.js +2 -1
- package/lib/vendor/blamejs/lib/safe-ical.js +12 -26
- package/lib/vendor/blamejs/lib/safe-json.js +7 -8
- package/lib/vendor/blamejs/lib/safe-jsonpath.js +6 -5
- package/lib/vendor/blamejs/lib/safe-mime.js +25 -29
- package/lib/vendor/blamejs/lib/safe-redirect.js +2 -5
- package/lib/vendor/blamejs/lib/safe-schema.js +7 -6
- package/lib/vendor/blamejs/lib/safe-sql.js +126 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +12 -26
- package/lib/vendor/blamejs/lib/sandbox-worker.js +9 -2
- package/lib/vendor/blamejs/lib/sandbox.js +3 -2
- package/lib/vendor/blamejs/lib/scheduler.js +5 -12
- package/lib/vendor/blamejs/lib/sec-cyber.js +82 -37
- package/lib/vendor/blamejs/lib/seeders.js +4 -11
- package/lib/vendor/blamejs/lib/self-update.js +92 -69
- package/lib/vendor/blamejs/lib/session-device-binding.js +112 -66
- package/lib/vendor/blamejs/lib/session.js +194 -6
- package/lib/vendor/blamejs/lib/sql.js +225 -78
- package/lib/vendor/blamejs/lib/sse.js +6 -10
- package/lib/vendor/blamejs/lib/static.js +136 -98
- package/lib/vendor/blamejs/lib/storage.js +4 -2
- package/lib/vendor/blamejs/lib/structured-fields.js +275 -16
- package/lib/vendor/blamejs/lib/tenant-quota.js +2 -20
- package/lib/vendor/blamejs/lib/tsa.js +11 -15
- package/lib/vendor/blamejs/lib/validate-opts.js +154 -8
- package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +30 -48
- package/lib/vendor/blamejs/lib/vault-aad.js +3 -2
- package/lib/vendor/blamejs/lib/vc.js +2 -7
- package/lib/vendor/blamejs/lib/vex.js +35 -13
- package/lib/vendor/blamejs/lib/web-push-vapid.js +23 -20
- package/lib/vendor/blamejs/lib/webhook-dispatcher.js +706 -0
- package/lib/vendor/blamejs/lib/webhook.js +43 -18
- package/lib/vendor/blamejs/lib/websocket-channels.js +9 -7
- package/lib/vendor/blamejs/lib/websocket.js +7 -3
- package/lib/vendor/blamejs/lib/worm.js +2 -3
- package/lib/vendor/blamejs/lib/ws-client.js +8 -10
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.15.13.json +81 -0
- package/lib/vendor/blamejs/scripts/check-changelog-extract.js +1 -1
- package/lib/vendor/blamejs/test/00-primitives.js +136 -7
- package/lib/vendor/blamejs/test/10-state.js +9 -3
- package/lib/vendor/blamejs/test/30-chain.js +40 -0
- package/lib/vendor/blamejs/test/helpers/check.js +18 -0
- package/lib/vendor/blamejs/test/helpers/db.js +4 -0
- package/lib/vendor/blamejs/test/helpers/index.js +1 -0
- package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +2 -1
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +2 -1
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +8 -1
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +1 -1
- package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +2 -1
- package/lib/vendor/blamejs/test/integration/webhook-dispatcher-pg.test.js +269 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-fd-read.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-framework-namespaces.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-anchor.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-use-store.test.js +55 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-object-store-adapter.test.js +2 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/bot-guard.test.js +1 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/bounded-map.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/chain-writer-multichain.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-api-key.test.js +12 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +2788 -3214
- package/lib/vendor/blamejs/test/layer-0-primitives/codepoint-class.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-base64url.test.js +63 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +2 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-builder.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-nonce.test.js +1 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +2 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-resolve-opts.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +9 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gate-contract-content-gate.test.js +290 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-email.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +23 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-gate-disposition-coverage.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +60 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-image.test.js +184 -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-json.test.js +54 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +10 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pdf.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-text.test.js +365 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-xml.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-yaml.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +20 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ip-utils.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-arf.test.js +10 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +39 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-greylist.test.js +17 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-tls.test.js +39 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +17 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mime-parse.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/money.test.js +17 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-revocation.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nonce-store-release.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-memory-getorinsert.test.js +100 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-loops.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer-linear-scans.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-jsonpath.test.js +14 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +19 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +1 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +2 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +1 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/session-valid-from.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +100 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/structured-fields.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/validate-opts-shape.test.js +235 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook-dispatcher.test.js +374 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +75 -8
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* webhook-dispatcher — durable signed-webhook delivery store
|
|
4
|
+
* (b.webhook.dispatcher).
|
|
5
|
+
*
|
|
6
|
+
* Driven against a REAL node:sqlite backend (a faithful externalDb) + a real
|
|
7
|
+
* vault (seal/unseal of the per-endpoint secret) + an injected transport, so
|
|
8
|
+
* the persistence, fan-out, retry/backoff, dead-letter, and replay paths run
|
|
9
|
+
* end-to-end without a network. SSRF refusal is exercised with IP-literal URLs
|
|
10
|
+
* (ssrfGuard classifies an IP literal without DNS, so it runs offline).
|
|
11
|
+
*
|
|
12
|
+
* Covers: declareSchema; registerEndpoint with secret SEALED at rest (never
|
|
13
|
+
* plaintext); SSRF refusal at registration; fan-out (one event → one delivery
|
|
14
|
+
* row per subscribed endpoint); event-type + wildcard subscription matching;
|
|
15
|
+
* a real signature the framework verifier accepts; first-attempt success;
|
|
16
|
+
* transient-failure backoff scheduling; maxAttempts → dead-letter; permanent
|
|
17
|
+
* (SSRF-rebind) → immediate dead-letter; deliveries.list/get/retry;
|
|
18
|
+
* dlq.list/replay.
|
|
19
|
+
*
|
|
20
|
+
* Run standalone: node test/layer-0-primitives/webhook-dispatcher.test.js
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
var fs = require("node:fs");
|
|
24
|
+
var os = require("node:os");
|
|
25
|
+
var path = require("node:path");
|
|
26
|
+
var { DatabaseSync } = require("node:sqlite");
|
|
27
|
+
var helpers = require("../helpers");
|
|
28
|
+
var b = helpers.b;
|
|
29
|
+
var check = helpers.check;
|
|
30
|
+
|
|
31
|
+
// Minimal faithful externalDb over an in-memory node:sqlite db (mirrors the
|
|
32
|
+
// framework's sqlite provider: Date params → ISO strings, SELECT → { rows }).
|
|
33
|
+
function _sqliteExternalDb() {
|
|
34
|
+
var db = new DatabaseSync(":memory:");
|
|
35
|
+
function _bind(params) {
|
|
36
|
+
return (params || []).map(function (v) {
|
|
37
|
+
if (v instanceof Date) return v.toISOString();
|
|
38
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
39
|
+
return v;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function _query(sqlText, params) {
|
|
43
|
+
var stmt = db.prepare(sqlText);
|
|
44
|
+
var args = _bind(params);
|
|
45
|
+
if (/^\s*select/i.test(sqlText)) return { rows: stmt.all.apply(stmt, args) };
|
|
46
|
+
var info = stmt.run.apply(stmt, args);
|
|
47
|
+
return { rows: [], changes: info.changes };
|
|
48
|
+
}
|
|
49
|
+
var xdb = { dialect: "sqlite", query: async function (s, p) { return _query(s, p); } };
|
|
50
|
+
return {
|
|
51
|
+
dialect: "sqlite",
|
|
52
|
+
query: async function (s, p) { return _query(s, p); },
|
|
53
|
+
transaction: async function (fn) {
|
|
54
|
+
db.exec("BEGIN");
|
|
55
|
+
try { var r = await fn(xdb); db.exec("COMMIT"); return r; }
|
|
56
|
+
catch (e) { try { db.exec("ROLLBACK"); } catch (_e) {} throw e; }
|
|
57
|
+
},
|
|
58
|
+
_raw: db,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// A controllable transport: records every POST, returns a programmable status.
|
|
63
|
+
function _stubTransport() {
|
|
64
|
+
var calls = [];
|
|
65
|
+
var nextStatus = 200;
|
|
66
|
+
var fn = function (url, body, headers) {
|
|
67
|
+
calls.push({ url: url, body: body, headers: headers });
|
|
68
|
+
return Promise.resolve({ status: nextStatus });
|
|
69
|
+
};
|
|
70
|
+
fn.calls = calls;
|
|
71
|
+
fn.setStatus = function (s) { nextStatus = s; };
|
|
72
|
+
return fn;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Public IP literals — ssrfGuard classifies these without DNS, so the SSRF
|
|
76
|
+
// gate runs offline. The stub transport means no real POST is attempted.
|
|
77
|
+
var PUBLIC_URL = "https://1.1.1.1/hooks";
|
|
78
|
+
var PUBLIC_URL2 = "https://8.8.8.8/hooks";
|
|
79
|
+
|
|
80
|
+
async function _readSealedSecret(xdb, endpointId) {
|
|
81
|
+
var row = xdb._raw.prepare(
|
|
82
|
+
"SELECT secret_sealed FROM " + tableName("webhook_endpoints") +
|
|
83
|
+
" WHERE endpoint_id = ?").get(endpointId);
|
|
84
|
+
return row && row.secret_sealed;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function tableName(local) { return b.frameworkSchema.tableName(local); }
|
|
88
|
+
|
|
89
|
+
async function run() {
|
|
90
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-wd-"));
|
|
91
|
+
await helpers.setupVaultOnly(tmpDir);
|
|
92
|
+
try {
|
|
93
|
+
await _runAll();
|
|
94
|
+
} finally {
|
|
95
|
+
helpers.teardownVaultOnly(tmpDir);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function _runAll() {
|
|
100
|
+
await testSurfaceAndSchema();
|
|
101
|
+
await testRegisterSealsSecret();
|
|
102
|
+
await testRegisterRefusesSsrf();
|
|
103
|
+
await testDispatchFanOutAndSignature();
|
|
104
|
+
await testEventTypeAndWildcardMatching();
|
|
105
|
+
await testTransientFailureBacksOff();
|
|
106
|
+
await testThrownTransportErrorBacksOff();
|
|
107
|
+
await testMysqlSchemaNoPartialIndex();
|
|
108
|
+
await testInlineDeliveryNotDoubleClaimed();
|
|
109
|
+
await testMaxAttemptsDeadLetters();
|
|
110
|
+
await testDlqReplay();
|
|
111
|
+
await testDeliveriesRetry();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function _newDispatcher(xdb, transport, extra) {
|
|
115
|
+
var opts = {
|
|
116
|
+
externalDb: xdb,
|
|
117
|
+
httpRequest: transport,
|
|
118
|
+
maxAttempts: 3,
|
|
119
|
+
retryBackoff: { initialMs: 1000, maxMs: 60000, factor: 2 },
|
|
120
|
+
};
|
|
121
|
+
if (extra) Object.keys(extra).forEach(function (k) { opts[k] = extra[k]; });
|
|
122
|
+
return b.webhook.dispatcher(opts);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function testSurfaceAndSchema() {
|
|
126
|
+
var xdb = _sqliteExternalDb();
|
|
127
|
+
var wd = _newDispatcher(xdb, _stubTransport());
|
|
128
|
+
check("dispatcher returns object", typeof wd === "object");
|
|
129
|
+
["declareSchema", "registerEndpoint", "removeEndpoint", "listEndpoints",
|
|
130
|
+
"dispatch", "processRetries"].forEach(function (m) {
|
|
131
|
+
check("dispatcher." + m + " is a function", typeof wd[m] === "function");
|
|
132
|
+
});
|
|
133
|
+
check("deliveries.list/get/retry present",
|
|
134
|
+
wd.deliveries && typeof wd.deliveries.list === "function" &&
|
|
135
|
+
typeof wd.deliveries.get === "function" && typeof wd.deliveries.retry === "function");
|
|
136
|
+
check("dlq.list/replay present",
|
|
137
|
+
wd.dlq && typeof wd.dlq.list === "function" && typeof wd.dlq.replay === "function");
|
|
138
|
+
await wd.declareSchema();
|
|
139
|
+
check("declareSchema is idempotent", true);
|
|
140
|
+
await wd.declareSchema(); // second call must not throw (IF NOT EXISTS)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function testRegisterSealsSecret() {
|
|
144
|
+
var xdb = _sqliteExternalDb();
|
|
145
|
+
var wd = _newDispatcher(xdb, _stubTransport());
|
|
146
|
+
await wd.declareSchema();
|
|
147
|
+
await wd.registerEndpoint({
|
|
148
|
+
endpointId: "ep1", url: PUBLIC_URL,
|
|
149
|
+
eventTypes: ["invoice.paid"], secret: "whsec_plaintext_secret",
|
|
150
|
+
});
|
|
151
|
+
var sealed = await _readSealedSecret(xdb, "ep1");
|
|
152
|
+
check("secret is sealed at rest (vault: prefix)",
|
|
153
|
+
typeof sealed === "string" && sealed.indexOf("vault:") === 0);
|
|
154
|
+
check("secret plaintext NOT stored",
|
|
155
|
+
sealed.indexOf("whsec_plaintext_secret") === -1);
|
|
156
|
+
var eps = await wd.listEndpoints();
|
|
157
|
+
check("listEndpoints returns the endpoint", eps.length === 1 && eps[0].endpointId === "ep1");
|
|
158
|
+
check("listEndpoints does not leak the secret",
|
|
159
|
+
JSON.stringify(eps).indexOf("whsec_plaintext_secret") === -1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function testRegisterRefusesSsrf() {
|
|
163
|
+
var xdb = _sqliteExternalDb();
|
|
164
|
+
var wd = _newDispatcher(xdb, _stubTransport());
|
|
165
|
+
await wd.declareSchema();
|
|
166
|
+
var privateThrew = false, metadataThrew = false, loopbackThrew = false, protoThrew = false;
|
|
167
|
+
try { await wd.registerEndpoint({ endpointId: "p", url: "https://10.0.0.5/h", eventTypes: ["x"], secret: "s" }); }
|
|
168
|
+
catch (e) { privateThrew = e.code === "webhook-dispatcher/ssrf-refused"; }
|
|
169
|
+
try { await wd.registerEndpoint({ endpointId: "m", url: "https://169.254.169.254/", eventTypes: ["x"], secret: "s" }); }
|
|
170
|
+
catch (e) { metadataThrew = e.code === "webhook-dispatcher/ssrf-refused"; }
|
|
171
|
+
try { await wd.registerEndpoint({ endpointId: "l", url: "https://127.0.0.1/", eventTypes: ["x"], secret: "s" }); }
|
|
172
|
+
catch (e) { loopbackThrew = e.code === "webhook-dispatcher/ssrf-refused"; }
|
|
173
|
+
// Non-TLS refused by safeUrl (protocol), before the IP check.
|
|
174
|
+
try { await wd.registerEndpoint({ endpointId: "h", url: "http://1.1.1.1/", eventTypes: ["x"], secret: "s" }); }
|
|
175
|
+
catch (e) { protoThrew = (e.code === "safe-url/protocol-disallowed" || /protocol/.test(e.message)); }
|
|
176
|
+
check("register refuses private IP (SSRF)", privateThrew);
|
|
177
|
+
check("register refuses metadata IP (SSRF)", metadataThrew);
|
|
178
|
+
check("register refuses loopback (SSRF)", loopbackThrew);
|
|
179
|
+
check("register refuses non-TLS", protoThrew);
|
|
180
|
+
// allowInternalDestinations opt-in lets an internal subscriber through.
|
|
181
|
+
var wdInternal = _newDispatcher(xdb, _stubTransport(), { allowInternalDestinations: true });
|
|
182
|
+
await wdInternal.declareSchema();
|
|
183
|
+
var ok = false;
|
|
184
|
+
try { await wdInternal.registerEndpoint({ endpointId: "int", url: "https://10.0.0.5/h", eventTypes: ["x"], secret: "s" }); ok = true; }
|
|
185
|
+
catch (_e) { ok = false; }
|
|
186
|
+
check("allowInternalDestinations opt-in permits private IP", ok);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function testDispatchFanOutAndSignature() {
|
|
190
|
+
var xdb = _sqliteExternalDb();
|
|
191
|
+
var transport = _stubTransport();
|
|
192
|
+
var wd = _newDispatcher(xdb, transport);
|
|
193
|
+
await wd.declareSchema();
|
|
194
|
+
var secret = "whsec_fanout_secret";
|
|
195
|
+
await wd.registerEndpoint({ endpointId: "a", url: PUBLIC_URL, eventTypes: ["order.created"], secret: secret });
|
|
196
|
+
await wd.registerEndpoint({ endpointId: "c", url: PUBLIC_URL2, eventTypes: ["order.created"], secret: secret });
|
|
197
|
+
var res = await wd.dispatch("order.created", { id: "ord_1", total: 99 });
|
|
198
|
+
check("fan-out delivered to both endpoints", res.delivered === 2 && res.failed === 0);
|
|
199
|
+
check("transport POSTed twice", transport.calls.length === 2);
|
|
200
|
+
|
|
201
|
+
// The signed request the framework produces verifies with the framework's
|
|
202
|
+
// own verifier under the same secret — proof the signature is real.
|
|
203
|
+
var c0 = transport.calls[0];
|
|
204
|
+
var sigHeader = c0.headers["Webhook-Signature"] || c0.headers["webhook-signature"];
|
|
205
|
+
check("signature header present", typeof sigHeader === "string" && sigHeader.length > 0);
|
|
206
|
+
check("X-Webhook-Delivery-Id header present", typeof c0.headers["X-Webhook-Delivery-Id"] === "string");
|
|
207
|
+
var verifier = b.webhook.verifier({ algo: "hmac-sha3-512", keys: { v1: secret } });
|
|
208
|
+
// Pass the signed headers verbatim — they carry the Webhook-Signature the
|
|
209
|
+
// verifier reads (plus the X-Webhook-* delivery headers it ignores).
|
|
210
|
+
var info = await verifier.verify({ body: c0.body, headers: c0.headers });
|
|
211
|
+
check("dispatcher signature verifies under endpoint secret", info && info.ok !== false);
|
|
212
|
+
|
|
213
|
+
// Persisted rows reflect delivered status.
|
|
214
|
+
var rows = await wd.deliveries.list({ status: "delivered" });
|
|
215
|
+
check("both deliveries persisted as delivered", rows.length === 2);
|
|
216
|
+
check("delivery carries responseStatus 200", rows[0].responseStatus === 200);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function testEventTypeAndWildcardMatching() {
|
|
220
|
+
var xdb = _sqliteExternalDb();
|
|
221
|
+
var transport = _stubTransport();
|
|
222
|
+
var wd = _newDispatcher(xdb, transport);
|
|
223
|
+
await wd.declareSchema();
|
|
224
|
+
await wd.registerEndpoint({ endpointId: "only-a", url: PUBLIC_URL, eventTypes: ["a"], secret: "s" });
|
|
225
|
+
await wd.registerEndpoint({ endpointId: "star", url: PUBLIC_URL2, eventTypes: ["*"], secret: "s" });
|
|
226
|
+
var resA = await wd.dispatch("a", { n: 1 });
|
|
227
|
+
check("event 'a' reaches only-a + wildcard (2)", resA.delivered === 2);
|
|
228
|
+
var resB = await wd.dispatch("b", { n: 2 });
|
|
229
|
+
check("event 'b' reaches only the wildcard (1)", resB.delivered === 1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function testTransientFailureBacksOff() {
|
|
233
|
+
var xdb = _sqliteExternalDb();
|
|
234
|
+
var transport = _stubTransport();
|
|
235
|
+
transport.setStatus(503); // receiver down
|
|
236
|
+
var wd = _newDispatcher(xdb, transport);
|
|
237
|
+
await wd.declareSchema();
|
|
238
|
+
await wd.registerEndpoint({ endpointId: "down", url: PUBLIC_URL, eventTypes: ["e"], secret: "s" });
|
|
239
|
+
var res = await wd.dispatch("e", { n: 1 });
|
|
240
|
+
check("transient failure not delivered", res.delivered === 0 && res.failed === 1);
|
|
241
|
+
var rows = await wd.deliveries.list({ endpointId: "down" });
|
|
242
|
+
check("failed delivery stays pending for retry", rows.length === 1 && rows[0].status === "pending");
|
|
243
|
+
check("attempts incremented to 1", rows[0].attempts === 1);
|
|
244
|
+
check("last_error recorded", /HTTP 503/.test(rows[0].lastError || ""));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function testThrownTransportErrorBacksOff() {
|
|
248
|
+
// A THROWN transport error (timeout / network / TLS) — httpClient throws it
|
|
249
|
+
// as an alwaysPermanent WebhookDispatcherError (err.permanent === true). It
|
|
250
|
+
// must still be treated as TRANSIENT (rescheduled), NOT dead-lettered on the
|
|
251
|
+
// first attempt. (Regression: reading err.permanent dead-lettered it.)
|
|
252
|
+
var xdb = _sqliteExternalDb();
|
|
253
|
+
var transport = function () { var e = new Error("ETIMEDOUT connect"); e.permanent = true; throw e; };
|
|
254
|
+
var wd = _newDispatcher(xdb, transport);
|
|
255
|
+
await wd.declareSchema();
|
|
256
|
+
await wd.registerEndpoint({ endpointId: "to", url: PUBLIC_URL, eventTypes: ["e"], secret: "s" });
|
|
257
|
+
var res = await wd.dispatch("e", { n: 1 });
|
|
258
|
+
check("thrown transport error not delivered", res.delivered === 0 && res.failed === 1);
|
|
259
|
+
var rows = await wd.deliveries.list({ endpointId: "to" });
|
|
260
|
+
check("thrown transport error stays pending (transient, not dead)",
|
|
261
|
+
rows.length === 1 && rows[0].status === "pending");
|
|
262
|
+
check("thrown transport error not dead-lettered", res.deliveries[0].dead !== true);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function testMysqlSchemaNoPartialIndex() {
|
|
266
|
+
// MySQL has no partial indexes; sql.createIndex refuses `where` on mysql, so
|
|
267
|
+
// declareSchema must emit a NON-partial index there or it throws on boot.
|
|
268
|
+
var sqls = [];
|
|
269
|
+
var mysqlXdb = {
|
|
270
|
+
dialect: "mysql",
|
|
271
|
+
query: async function (s) { sqls.push(s); return { rows: [] }; },
|
|
272
|
+
transaction: async function (fn) { return fn(this); },
|
|
273
|
+
};
|
|
274
|
+
var wd = _newDispatcher(mysqlXdb, _stubTransport());
|
|
275
|
+
var threw = null;
|
|
276
|
+
try { await wd.declareSchema(); } catch (e) { threw = e; }
|
|
277
|
+
check("declareSchema(mysql) does not throw on the pending index", threw === null);
|
|
278
|
+
var idxSql = sqls.filter(function (s) { return /_pending_idx/i.test(s); }).join(" || ");
|
|
279
|
+
check("mysql pending index is emitted", idxSql.length > 0);
|
|
280
|
+
check("mysql pending index is non-partial (no WHERE)", !/\bwhere\b/i.test(idxSql));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function testInlineDeliveryNotDoubleClaimed() {
|
|
284
|
+
// The inline first attempt must claim its row (status 'in-flight') BEFORE the
|
|
285
|
+
// POST, so a retry poller firing during a slow inline POST can't grab the
|
|
286
|
+
// same row and double-deliver. Simulate the poller from inside the transport.
|
|
287
|
+
var xdb = _sqliteExternalDb();
|
|
288
|
+
var calls = 0;
|
|
289
|
+
var wd;
|
|
290
|
+
var transport = function () {
|
|
291
|
+
calls += 1;
|
|
292
|
+
return wd.processRetries().then(function () { return { status: 200 }; });
|
|
293
|
+
};
|
|
294
|
+
wd = _newDispatcher(xdb, transport);
|
|
295
|
+
await wd.declareSchema();
|
|
296
|
+
await wd.registerEndpoint({ endpointId: "once", url: PUBLIC_URL, eventTypes: ["e"], secret: "s" });
|
|
297
|
+
await wd.dispatch("e", { n: 1 });
|
|
298
|
+
check("inline delivery not double-claimed by a concurrent poller", calls === 1);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function testMaxAttemptsDeadLetters() {
|
|
302
|
+
var now = 1700000000000;
|
|
303
|
+
var clock = function () { return now; };
|
|
304
|
+
var xdb = _sqliteExternalDb();
|
|
305
|
+
var transport = _stubTransport();
|
|
306
|
+
transport.setStatus(500);
|
|
307
|
+
var wd = b.webhook.dispatcher({
|
|
308
|
+
externalDb: xdb, httpRequest: transport, maxAttempts: 3,
|
|
309
|
+
retryBackoff: { initialMs: 1000, maxMs: 5000, factor: 2 }, now: clock,
|
|
310
|
+
});
|
|
311
|
+
await wd.declareSchema();
|
|
312
|
+
await wd.registerEndpoint({ endpointId: "dead", url: PUBLIC_URL, eventTypes: ["e"], secret: "s" });
|
|
313
|
+
await wd.dispatch("e", { n: 1 }); // attempt 1 → pending
|
|
314
|
+
// Advance the clock past each backoff so processRetries claims the row.
|
|
315
|
+
now += 10000; await wd.processRetries(); // attempt 2 → pending
|
|
316
|
+
now += 10000; var r3 = await wd.processRetries(); // attempt 3 → dead (maxAttempts)
|
|
317
|
+
check("third attempt dead-letters", r3.dead === 1);
|
|
318
|
+
var dlq = await wd.dlq.list();
|
|
319
|
+
check("DLQ holds the dead delivery", dlq.length === 1 && dlq[0].status === "dead");
|
|
320
|
+
check("dead delivery recorded 3 attempts", dlq[0].attempts === 3);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function testDlqReplay() {
|
|
324
|
+
var now = 1700000000000;
|
|
325
|
+
var clock = function () { return now; };
|
|
326
|
+
var xdb = _sqliteExternalDb();
|
|
327
|
+
var transport = _stubTransport();
|
|
328
|
+
transport.setStatus(500);
|
|
329
|
+
var wd = b.webhook.dispatcher({
|
|
330
|
+
externalDb: xdb, httpRequest: transport, maxAttempts: 2,
|
|
331
|
+
retryBackoff: { initialMs: 1000, maxMs: 5000, factor: 2 }, now: clock,
|
|
332
|
+
});
|
|
333
|
+
await wd.declareSchema();
|
|
334
|
+
await wd.registerEndpoint({ endpointId: "rep", url: PUBLIC_URL, eventTypes: ["e"], secret: "s" });
|
|
335
|
+
await wd.dispatch("e", { n: 1 }); // attempt 1 → pending
|
|
336
|
+
now += 10000; await wd.processRetries(); // attempt 2 → dead
|
|
337
|
+
var dlqBefore = await wd.dlq.list();
|
|
338
|
+
check("delivery is in DLQ before replay", dlqBefore.length === 1);
|
|
339
|
+
// Receiver recovers; replay from the DLQ delivers.
|
|
340
|
+
transport.setStatus(200);
|
|
341
|
+
var replayRes = await wd.dlq.replay(dlqBefore[0].deliveryId);
|
|
342
|
+
check("replay delivers", replayRes.ok === true);
|
|
343
|
+
var dlqAfter = await wd.dlq.list();
|
|
344
|
+
check("DLQ empty after successful replay", dlqAfter.length === 0);
|
|
345
|
+
var delivered = await wd.deliveries.list({ status: "delivered" });
|
|
346
|
+
check("replayed delivery now delivered", delivered.length === 1);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function testDeliveriesRetry() {
|
|
350
|
+
var xdb = _sqliteExternalDb();
|
|
351
|
+
var transport = _stubTransport();
|
|
352
|
+
transport.setStatus(500);
|
|
353
|
+
var wd = _newDispatcher(xdb, transport);
|
|
354
|
+
await wd.declareSchema();
|
|
355
|
+
await wd.registerEndpoint({ endpointId: "rt", url: PUBLIC_URL, eventTypes: ["e"], secret: "s" });
|
|
356
|
+
var res = await wd.dispatch("e", { n: 1 });
|
|
357
|
+
var deliveryId = res.deliveries[0].deliveryId;
|
|
358
|
+
var before = await wd.deliveries.get(deliveryId);
|
|
359
|
+
check("delivery get returns the row", before && before.deliveryId === deliveryId);
|
|
360
|
+
transport.setStatus(200);
|
|
361
|
+
var retryRes = await wd.deliveries.retry(deliveryId);
|
|
362
|
+
check("manual retry delivers", retryRes.ok === true);
|
|
363
|
+
var after = await wd.deliveries.get(deliveryId);
|
|
364
|
+
check("retried delivery now delivered", after.status === "delivered");
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
module.exports = { run: run };
|
|
368
|
+
|
|
369
|
+
if (require.main === module) {
|
|
370
|
+
run().then(
|
|
371
|
+
function () { console.log("[webhook-dispatcher] OK — " + helpers.getChecks() + " checks passed"); process.exit(0); },
|
|
372
|
+
function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
|
|
373
|
+
);
|
|
374
|
+
}
|
|
@@ -588,13 +588,21 @@ async function testStripeBadAlg() {
|
|
|
588
588
|
check("bad alg → throws", thrown);
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
+
// b.webhook.verify speaks the SAME atomic { checkAndInsert(nonce, expireAt)
|
|
592
|
+
// → bool } replay contract as b.webhook.verifier and b.nonceStore — one
|
|
593
|
+
// interface across the whole b.webhook surface, no racy check-then-set.
|
|
591
594
|
async function testStripeNonceReplay() {
|
|
592
595
|
var body = '{"x":1}';
|
|
593
596
|
var ts = Math.floor(Date.now() / 1000);
|
|
594
597
|
var secret = "whsec_replay_test";
|
|
595
598
|
var fx = _stripeFixture(body, ts, secret);
|
|
596
599
|
var seen = Object.create(null);
|
|
597
|
-
|
|
600
|
+
// Atomic check-and-insert: returns true only the first time a key is seen.
|
|
601
|
+
var store = { checkAndInsert: function (k) {
|
|
602
|
+
if (seen[k]) return false;
|
|
603
|
+
seen[k] = true;
|
|
604
|
+
return true;
|
|
605
|
+
} };
|
|
598
606
|
var r1 = await b.webhook.verify({ alg: "hmac-sha256-stripe", secret: secret, header: fx.header, body: body, nonceStore: store });
|
|
599
607
|
check("first call ok", r1.ok === true);
|
|
600
608
|
var thrown = false;
|
|
@@ -604,18 +612,20 @@ async function testStripeNonceReplay() {
|
|
|
604
612
|
}
|
|
605
613
|
|
|
606
614
|
async function testStripeAsyncNonceStore() {
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
// has() returns a Promise<boolean>.
|
|
615
|
+
// checkAndInsert MAY return a Promise (Redis / KV / cluster SQL backend);
|
|
616
|
+
// verify MUST await it before deciding fresh-vs-replay.
|
|
610
617
|
var body = '{"x":1}';
|
|
611
618
|
var ts = Math.floor(Date.now() / 1000);
|
|
612
619
|
var secret = "whsec_async_test";
|
|
613
620
|
var fx = _stripeFixture(body, ts, secret);
|
|
614
621
|
var seen = Object.create(null);
|
|
615
|
-
var asyncStore = {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
622
|
+
var asyncStore = { checkAndInsert: function (k) {
|
|
623
|
+
return new Promise(function (resolve) {
|
|
624
|
+
var fresh = !seen[k];
|
|
625
|
+
seen[k] = true;
|
|
626
|
+
resolve(fresh);
|
|
627
|
+
});
|
|
628
|
+
} };
|
|
619
629
|
var r1 = await b.webhook.verify({ alg: "hmac-sha256-stripe", secret: secret, header: fx.header, body: body, nonceStore: asyncStore });
|
|
620
630
|
check("async store first call ok", r1.ok === true);
|
|
621
631
|
var thrown = false;
|
|
@@ -624,6 +634,60 @@ async function testStripeAsyncNonceStore() {
|
|
|
624
634
|
check("async store replay throws", thrown);
|
|
625
635
|
}
|
|
626
636
|
|
|
637
|
+
// Headline: the framework's OWN replay store (b.nonceStore.create) drops
|
|
638
|
+
// straight into the framework's OWN Stripe verifier — no adapter object.
|
|
639
|
+
async function testStripeNonceStoreFromPrimitive() {
|
|
640
|
+
var body = '{"x":1}';
|
|
641
|
+
var ts = Math.floor(Date.now() / 1000);
|
|
642
|
+
var secret = "whsec_primitive_store";
|
|
643
|
+
var fx = _stripeFixture(body, ts, secret);
|
|
644
|
+
var ns = b.nonceStore.create({ backend: "memory" });
|
|
645
|
+
var r1 = await b.webhook.verify({ alg: "hmac-sha256-stripe", secret: secret, header: fx.header, body: body, nonceStore: ns });
|
|
646
|
+
check("b.nonceStore plugs into verify (1st ok)", r1.ok === true);
|
|
647
|
+
var thrown = false;
|
|
648
|
+
try { await b.webhook.verify({ alg: "hmac-sha256-stripe", secret: secret, header: fx.header, body: body, nonceStore: ns }); }
|
|
649
|
+
catch (e) { thrown = true; check("b.nonceStore replay → replay code", e.code === "webhook/replay"); }
|
|
650
|
+
check("b.nonceStore dedupes replay", thrown);
|
|
651
|
+
if (ns.close) await ns.close();
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// A store that exposes the legacy { has, set } shape but no checkAndInsert is
|
|
655
|
+
// refused config-time — the racy check-then-set contract is gone.
|
|
656
|
+
async function testStripeNonceStoreBadShapeRefused() {
|
|
657
|
+
var body = '{"x":1}';
|
|
658
|
+
var ts = Math.floor(Date.now() / 1000);
|
|
659
|
+
var secret = "whsec_bad_shape";
|
|
660
|
+
var fx = _stripeFixture(body, ts, secret);
|
|
661
|
+
var legacyStore = { has: function () { return false; }, set: function () {} };
|
|
662
|
+
var thrown = false;
|
|
663
|
+
try { await b.webhook.verify({ alg: "hmac-sha256-stripe", secret: secret, header: fx.header, body: body, nonceStore: legacyStore }); }
|
|
664
|
+
catch (e) { thrown = true; check("bad nonce-store → bad-nonce-store code", e.code === "webhook/bad-nonce-store"); }
|
|
665
|
+
check("nonceStore without checkAndInsert refused", thrown);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// checkAndInsert is the atomic gate: of two concurrent verifies of the same
|
|
669
|
+
// signature, exactly one is accepted (the other is a replay) — no TOCTOU
|
|
670
|
+
// window where both observe "unseen".
|
|
671
|
+
async function testStripeNonceStoreConcurrentAtomic() {
|
|
672
|
+
var body = '{"x":1}';
|
|
673
|
+
var ts = Math.floor(Date.now() / 1000);
|
|
674
|
+
var secret = "whsec_concurrent";
|
|
675
|
+
var fx = _stripeFixture(body, ts, secret);
|
|
676
|
+
var ns = b.nonceStore.create({ backend: "memory" });
|
|
677
|
+
var calls = [
|
|
678
|
+
b.webhook.verify({ alg: "hmac-sha256-stripe", secret: secret, header: fx.header, body: body, nonceStore: ns }),
|
|
679
|
+
b.webhook.verify({ alg: "hmac-sha256-stripe", secret: secret, header: fx.header, body: body, nonceStore: ns }),
|
|
680
|
+
];
|
|
681
|
+
var settled = await Promise.allSettled(calls);
|
|
682
|
+
var ok = settled.filter(function (s) { return s.status === "fulfilled"; }).length;
|
|
683
|
+
var replay = settled.filter(function (s) {
|
|
684
|
+
return s.status === "rejected" && s.reason && s.reason.code === "webhook/replay";
|
|
685
|
+
}).length;
|
|
686
|
+
check("concurrent: exactly one accepted", ok === 1);
|
|
687
|
+
check("concurrent: exactly one replay", replay === 1);
|
|
688
|
+
if (ns.close) await ns.close();
|
|
689
|
+
}
|
|
690
|
+
|
|
627
691
|
async function run() {
|
|
628
692
|
testWebhookSurface();
|
|
629
693
|
await testHmacRoundtrip();
|
|
@@ -662,6 +726,9 @@ async function run() {
|
|
|
662
726
|
await testStripeBadAlg();
|
|
663
727
|
await testStripeNonceReplay();
|
|
664
728
|
await testStripeAsyncNonceStore();
|
|
729
|
+
await testStripeNonceStoreFromPrimitive();
|
|
730
|
+
await testStripeNonceStoreBadShapeRefused();
|
|
731
|
+
await testStripeNonceStoreConcurrentAtomic();
|
|
665
732
|
}
|
|
666
733
|
|
|
667
734
|
module.exports = { run: run };
|
|
@@ -150,8 +150,8 @@ async function _runStaticServeRoundTrip(g, fx) {
|
|
|
150
150
|
var path = require("path");
|
|
151
151
|
var fs = require("fs");
|
|
152
152
|
|
|
153
|
-
var dir = path.join(os.tmpdir(),
|
|
154
|
-
"guard-host-static-" + g.NAME + "-"
|
|
153
|
+
var dir = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
154
|
+
"guard-host-static-" + g.NAME + "-"));
|
|
155
155
|
fs.mkdirSync(dir, { recursive: true });
|
|
156
156
|
var benignPath = path.join(dir, "benign" + fx.extension);
|
|
157
157
|
var hostilePath = path.join(dir, "hostile" + fx.extension);
|
|
@@ -545,7 +545,7 @@ async function testStaticServeDefaultOn() {
|
|
|
545
545
|
var fs = require("fs");
|
|
546
546
|
var os = require("os");
|
|
547
547
|
var path = require("path");
|
|
548
|
-
var dir = path.join(os.tmpdir(), "guard-default-on-ss-"
|
|
548
|
+
var dir = fs.mkdtempSync(path.join(os.tmpdir(), "guard-default-on-ss-"));
|
|
549
549
|
fs.mkdirSync(dir, { recursive: true });
|
|
550
550
|
// Hostile HTML — default-on contentSafety refuses.
|
|
551
551
|
fs.writeFileSync(path.join(dir, "hostile.html"),
|
package/package.json
CHANGED