@blamejs/blamejs-shop 0.4.55 → 0.4.57
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/admin.js +385 -2
- package/lib/asset-manifest.json +1 -1
- 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/lib/winback-campaigns.js +202 -42
- package/package.json +1 -1
|
@@ -1887,6 +1887,45 @@ async function testCheckpointVerify() {
|
|
|
1887
1887
|
}
|
|
1888
1888
|
}
|
|
1889
1889
|
|
|
1890
|
+
// Regression: a fire-and-forget checkpoint launched by db.close() reads the
|
|
1891
|
+
// chain tip from the open database, then its deferred insert resolves the live
|
|
1892
|
+
// db handle at resume time. If a fresh database opened in between, the old
|
|
1893
|
+
// tip's checkpoint must NOT be anchored into that different/absent database
|
|
1894
|
+
// (it would forge a checkpoint signed under the prior keypair into another db).
|
|
1895
|
+
// Reproduce deterministically: tear the db down (bumping the db generation)
|
|
1896
|
+
// between the tip read and the insert — auditSign.sign() sits exactly there in
|
|
1897
|
+
// checkpoint(). The checkpoint must fail closed (return null, never throw, never
|
|
1898
|
+
// write) instead of inserting against the torn-down/replaced database.
|
|
1899
|
+
async function testCheckpointCrossGenerationRefused() {
|
|
1900
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-ckptgen-"));
|
|
1901
|
+
var realGen = b.db._dbGeneration;
|
|
1902
|
+
try {
|
|
1903
|
+
await setupTestDb(tmpDir);
|
|
1904
|
+
b.audit.registerNamespace("test");
|
|
1905
|
+
await b.audit.record({ action: "test.event", outcome: "success" });
|
|
1906
|
+
|
|
1907
|
+
// Simulate the db handle being closed/replaced between the tip read and
|
|
1908
|
+
// the insert: dbGeneration() returns one value when checkpoint() binds at
|
|
1909
|
+
// entry, and a changed value at the pre-insert re-check. The checkpoint
|
|
1910
|
+
// must fail closed — return null and write nothing.
|
|
1911
|
+
var n = 0;
|
|
1912
|
+
b.db._dbGeneration = function () { n += 1; return n <= 1 ? 1000 : 1001; };
|
|
1913
|
+
var result = await b.audit.checkpoint();
|
|
1914
|
+
b.db._dbGeneration = realGen;
|
|
1915
|
+
|
|
1916
|
+
check("cross-generation checkpoint returns null", result === null);
|
|
1917
|
+
var after = await b.audit.verifyCheckpoints();
|
|
1918
|
+
check("cross-generation checkpoint wrote nothing", after.ok === true && after.checkpointsVerified === 0);
|
|
1919
|
+
|
|
1920
|
+
// Control: a stable-generation checkpoint still anchors normally.
|
|
1921
|
+
var ok = await b.audit.checkpoint();
|
|
1922
|
+
check("stable-generation checkpoint still anchors", ok && typeof ok._id === "string");
|
|
1923
|
+
} finally {
|
|
1924
|
+
b.db._dbGeneration = realGen;
|
|
1925
|
+
await teardownTestDb(tmpDir);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1890
1929
|
async function testCheckpointTamperDetect() {
|
|
1891
1930
|
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-cdetect-"));
|
|
1892
1931
|
try {
|
|
@@ -2062,6 +2101,7 @@ async function run() {
|
|
|
2062
2101
|
// checkpoint sign / verify / tamper / rollback
|
|
2063
2102
|
await testCheckpointSign();
|
|
2064
2103
|
await testCheckpointVerify();
|
|
2104
|
+
await testCheckpointCrossGenerationRefused();
|
|
2065
2105
|
await testCheckpointTamperDetect();
|
|
2066
2106
|
await testRollbackDetection();
|
|
2067
2107
|
}
|
|
@@ -28,9 +28,27 @@ function addExternalChecks(n) {
|
|
|
28
28
|
if (typeof n === "number" && isFinite(n) && n >= 0) _checks += n;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// formatErr — render a thrown error as a single-line, bounded diagnostic for a
|
|
32
|
+
// test runner's failure catch. A thrown error's message/stack can carry a test
|
|
33
|
+
// fixture's bytes verbatim; replacing CR/LF (a recognized log-injection
|
|
34
|
+
// barrier) keeps the "FAIL:" line on one row so a fixture value can't forge
|
|
35
|
+
// extra log lines. The newline .replace() is what breaks the log-injection
|
|
36
|
+
// data flow; the tab/run-collapse + length bound are cosmetic.
|
|
37
|
+
function formatErr(e) {
|
|
38
|
+
var raw = (e && typeof e.stack === "string" && e.stack) ||
|
|
39
|
+
(e && typeof e.message === "string" && e.message) ||
|
|
40
|
+
String(e);
|
|
41
|
+
var oneLine = raw
|
|
42
|
+
.replace(/[\r\n]+/g, " ")
|
|
43
|
+
.replace(/\t+/g, " ")
|
|
44
|
+
.replace(/ {2,}/g, " ");
|
|
45
|
+
return oneLine.length > 2000 ? oneLine.slice(0, 2000) + "..." : oneLine;
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
module.exports = {
|
|
32
49
|
check: check,
|
|
33
50
|
getChecks: getChecks,
|
|
34
51
|
resetChecksForTest: resetChecksForTest,
|
|
35
52
|
addExternalChecks: addExternalChecks,
|
|
53
|
+
formatErr: formatErr,
|
|
36
54
|
};
|
|
@@ -68,6 +68,10 @@ async function teardownTestDb(tmpDir) {
|
|
|
68
68
|
// Drain audit handler buffered emissions BEFORE close so pending
|
|
69
69
|
// rows land in audit_log rather than leaking into the next test's DB.
|
|
70
70
|
try { await b.audit.flush(); } catch (_e) {}
|
|
71
|
+
// Anchor the final checkpoint synchronously while this db is still open, so
|
|
72
|
+
// close()'s own fire-and-forget checkpoint no-ops (skipIfUnchanged) and no
|
|
73
|
+
// detached checkpoint promise straddles the db boundary into the next test.
|
|
74
|
+
try { await b.audit.checkpoint({ skipIfUnchanged: true }); } catch (_e) {}
|
|
71
75
|
try { b.db.close(); } catch (_e) {}
|
|
72
76
|
b.audit._resetForTest();
|
|
73
77
|
b.db._resetForTest();
|
|
@@ -323,7 +323,8 @@ async function run() {
|
|
|
323
323
|
"_blamejs_audit_tip", "_blamejs_consent_tip", "_blamejs_audit_purge_anchor",
|
|
324
324
|
"_blamejs_scheduler_ticks", "_blamejs_rate_limit_counters",
|
|
325
325
|
"_blamejs_pubsub_messages", "_blamejs_api_encrypt_nonces", "_blamejs_api_keys",
|
|
326
|
-
"_blamejs_sessions", "
|
|
326
|
+
"_blamejs_sessions", "_blamejs_session_valid_from", "_blamejs_jobs",
|
|
327
|
+
"_blamejs_cache", "_blamejs_cache_tags",
|
|
327
328
|
"_blamejs_seeders", "_blamejs_seeders_lock", "_blamejs_break_glass_policies",
|
|
328
329
|
"_blamejs_break_glass_grants", "_blamejs_leader", "_blamejs_cluster_state",
|
|
329
330
|
].map(function (t) { return "DROP TABLE IF EXISTS " + t + " CASCADE;"; }).join("\n");
|
|
@@ -254,7 +254,8 @@ var FRAMEWORK_TABLES = [
|
|
|
254
254
|
"_blamejs_audit_tip", "_blamejs_consent_tip", "_blamejs_audit_purge_anchor",
|
|
255
255
|
"_blamejs_scheduler_ticks", "_blamejs_rate_limit_counters",
|
|
256
256
|
"_blamejs_pubsub_messages", "_blamejs_api_encrypt_nonces", "_blamejs_api_keys",
|
|
257
|
-
"_blamejs_sessions", "
|
|
257
|
+
"_blamejs_sessions", "_blamejs_session_valid_from", "_blamejs_jobs",
|
|
258
|
+
"_blamejs_cache", "_blamejs_cache_tags",
|
|
258
259
|
"_blamejs_seeders", "_blamejs_seeders_lock", "_blamejs_break_glass_policies",
|
|
259
260
|
"_blamejs_break_glass_grants", "_blamejs_leader", "_blamejs_cluster_state",
|
|
260
261
|
// app-side table for break-glass + K_row storage round-trips
|
|
@@ -209,6 +209,13 @@ var MYSQL_DDL = [
|
|
|
209
209
|
"`createdAt` BIGINT NOT NULL, " +
|
|
210
210
|
"`expiresAt` BIGINT NOT NULL, " +
|
|
211
211
|
"`lastActivity` BIGINT NOT NULL)",
|
|
212
|
+
// _blamejs_session_valid_from — per-subject not-before invalidation. Mirrors
|
|
213
|
+
// framework-schema._sessionValidFromDDL("mysql"): VARCHAR key, BIGINT epochs.
|
|
214
|
+
"DROP TABLE IF EXISTS `_blamejs_session_valid_from`",
|
|
215
|
+
"CREATE TABLE `_blamejs_session_valid_from` (" +
|
|
216
|
+
"`subjectHash` VARCHAR(191) PRIMARY KEY, " +
|
|
217
|
+
"`validFromEpoch` BIGINT NOT NULL, " +
|
|
218
|
+
"`updatedAt` BIGINT NOT NULL)",
|
|
212
219
|
// _blamejs_api_encrypt_nonces — replay-protection store. nonceHash is the
|
|
213
220
|
// PRIMARY KEY, so the ON DUPLICATE KEY UPDATE no-op fold makes the first
|
|
214
221
|
// insert win (affectedRows=1) and a replay no-op (affectedRows=0).
|
|
@@ -241,7 +248,7 @@ var MYSQL_DDL = [
|
|
|
241
248
|
var DROP_ALL = [
|
|
242
249
|
"_blamejs_cache", "_blamejs_cache_tags", "_blamejs_audit_tip",
|
|
243
250
|
"_blamejs_consent_tip", "_blamejs_audit_log", "_blamejs_consent_log",
|
|
244
|
-
"_blamejs_sessions", "_blamejs_api_encrypt_nonces",
|
|
251
|
+
"_blamejs_sessions", "_blamejs_session_valid_from", "_blamejs_api_encrypt_nonces",
|
|
245
252
|
"_blamejs_rate_limit_counters", "_blamejs_cluster_state",
|
|
246
253
|
].map(function (t) { return "DROP TABLE IF EXISTS `" + t + "`"; });
|
|
247
254
|
|
|
@@ -245,7 +245,7 @@ function _parseBlock(block) {
|
|
|
245
245
|
// cache_tags / nonces / rate-limit counters, but ensureSchema materializes
|
|
246
246
|
// the whole framework surface, so all of it is swept.
|
|
247
247
|
var FRAMEWORK_TABLES = [
|
|
248
|
-
"_blamejs_sessions", "_blamejs_cache", "_blamejs_cache_tags",
|
|
248
|
+
"_blamejs_sessions", "_blamejs_session_valid_from", "_blamejs_cache", "_blamejs_cache_tags",
|
|
249
249
|
"_blamejs_api_encrypt_nonces", "_blamejs_rate_limit_counters",
|
|
250
250
|
"_blamejs_audit_log", "_blamejs_consent_log", "_blamejs_audit_checkpoints",
|
|
251
251
|
"_blamejs_audit_tip", "_blamejs_consent_tip", "_blamejs_audit_purge_anchor",
|
|
@@ -138,7 +138,8 @@ var FRAMEWORK_TABLES = [
|
|
|
138
138
|
"_blamejs_audit_tip", "_blamejs_consent_tip", "_blamejs_audit_purge_anchor",
|
|
139
139
|
"_blamejs_scheduler_ticks", "_blamejs_rate_limit_counters",
|
|
140
140
|
"_blamejs_pubsub_messages", "_blamejs_api_encrypt_nonces", "_blamejs_api_keys",
|
|
141
|
-
"_blamejs_sessions", "
|
|
141
|
+
"_blamejs_sessions", "_blamejs_session_valid_from", "_blamejs_jobs",
|
|
142
|
+
"_blamejs_cache", "_blamejs_cache_tags",
|
|
142
143
|
"_blamejs_seeders", "_blamejs_seeders_lock", "_blamejs_break_glass_policies",
|
|
143
144
|
"_blamejs_break_glass_grants",
|
|
144
145
|
];
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Live webhook-dispatcher test against the docker Postgres container. The
|
|
4
|
+
* dispatcher's durable store composes b.sql -> b.externalDb, so smoke only
|
|
5
|
+
* ever exercises it on SQLite; this proves the real Postgres dialect path:
|
|
6
|
+
*
|
|
7
|
+
* - declareSchema renders valid Postgres DDL (serial -> BIGSERIAL, the
|
|
8
|
+
* TIMESTAMPTZ columns, the partial pending index) on a real server.
|
|
9
|
+
* - registerEndpoint INSERTs with the secret SEALED at rest (vault: prefix
|
|
10
|
+
* in the actual Postgres row, never the plaintext secret).
|
|
11
|
+
* - dispatch fans out one event into one delivery row per endpoint and the
|
|
12
|
+
* $1..$N-bound INSERT/UPDATE round-trips.
|
|
13
|
+
* - the retry claim TRANSACTION (mark-then-reselect) + the BIGINT `attempts`
|
|
14
|
+
* column coercing back through frameworkSchema for the count comparison.
|
|
15
|
+
* - maxAttempts -> dead-letter and dlq.replay on real Postgres.
|
|
16
|
+
*
|
|
17
|
+
* Delivery HTTP is a stubbed transport (no network); the storage path is real.
|
|
18
|
+
*
|
|
19
|
+
* RUN: node scripts/test-integration.js --skip-service-check webhook-dispatcher-pg
|
|
20
|
+
*/
|
|
21
|
+
var spawn = require("node:child_process").spawn;
|
|
22
|
+
var execFileSync = require("node:child_process").execFileSync;
|
|
23
|
+
var fs = require("node:fs");
|
|
24
|
+
var os = require("node:os");
|
|
25
|
+
var path = require("node:path");
|
|
26
|
+
var helpers = require("../helpers");
|
|
27
|
+
var check = helpers.check;
|
|
28
|
+
var services = require("../helpers/services");
|
|
29
|
+
var b = require("../../");
|
|
30
|
+
|
|
31
|
+
var CONTAINER = "blamejs-test-postgres";
|
|
32
|
+
var NULL_SENTINEL = "__BJNULL__";
|
|
33
|
+
var PSQL_ARGS = "psql -U blamejs -d blamejs_test -A -v ON_ERROR_STOP=0 -P null=__BJNULL__ 2>&1";
|
|
34
|
+
|
|
35
|
+
var ENDPOINTS_TABLE = b.frameworkSchema.tableName("webhook_endpoints");
|
|
36
|
+
var DELIVERIES_TABLE = b.frameworkSchema.tableName("webhook_deliveries");
|
|
37
|
+
|
|
38
|
+
// ---- one-shot psql (setup / teardown / out-of-band assertions) ----
|
|
39
|
+
function _psql(sql) {
|
|
40
|
+
var out = execFileSync(
|
|
41
|
+
"docker",
|
|
42
|
+
["exec", "-i", CONTAINER, "sh", "-c", "psql -U blamejs -d blamejs_test -qtA -P null=__BJNULL__ 2>&1"],
|
|
43
|
+
{ input: sql + "\n", stdio: ["pipe", "pipe", "pipe"] }
|
|
44
|
+
).toString("utf8");
|
|
45
|
+
if (/^ERROR:/m.test(out)) throw new Error("psql failed for [" + sql + "]:\n" + out);
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---- persistent-session docker-exec psql driver (faithful to node-postgres:
|
|
50
|
+
// BIGINT comes back as a string; quoted DDL preserves column case) ----
|
|
51
|
+
var _seq = 0;
|
|
52
|
+
function _makeDockerPgDriver() {
|
|
53
|
+
return {
|
|
54
|
+
connect: function () {
|
|
55
|
+
return new Promise(function (resolve, reject) {
|
|
56
|
+
var child = spawn("docker",
|
|
57
|
+
["exec", "-i", CONTAINER, "sh", "-c", PSQL_ARGS + " ; echo __BLAMEJS_PSQL_EXIT__"],
|
|
58
|
+
{ stdio: ["pipe", "pipe", "pipe"] });
|
|
59
|
+
var client = { child: child, buf: "", pending: null, closed: false };
|
|
60
|
+
child.on("error", function (e) { if (client.pending) { var p = client.pending; client.pending = null; p.reject(e); } });
|
|
61
|
+
child.on("close", function () {
|
|
62
|
+
client.closed = true;
|
|
63
|
+
if (client.pending) { var p = client.pending; client.pending = null; p.reject(new Error("psql session closed mid-statement")); }
|
|
64
|
+
});
|
|
65
|
+
child.stdout.on("data", function (chunk) { client.buf += chunk.toString("utf8"); _drain(client); });
|
|
66
|
+
var prime = "__BJ_PRIME__";
|
|
67
|
+
client.pending = { sentinel: prime, resolve: function () { resolve(client); }, reject: reject };
|
|
68
|
+
client.child.stdin.write("\\pset fieldsep '\\t'\n\\pset footer off\n\\echo " + prime + "\n");
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
query: function (client, sql, params) {
|
|
72
|
+
var bound = _bindParams(sql, params || []);
|
|
73
|
+
var sentinel = "__BJ_EOR_" + (++_seq) + "__";
|
|
74
|
+
return new Promise(function (resolve, reject) {
|
|
75
|
+
if (client.closed) { reject(new Error("psql session is closed")); return; }
|
|
76
|
+
client.pending = { sentinel: sentinel, resolve: resolve, reject: reject };
|
|
77
|
+
client.child.stdin.write(bound + "\n;\n\\echo " + sentinel + "\n");
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
close: function (client) {
|
|
81
|
+
return new Promise(function (resolve) {
|
|
82
|
+
if (client.closed) { resolve(); return; }
|
|
83
|
+
try { client.child.stdin.end("\\q\n"); } catch (_e) {}
|
|
84
|
+
var done = false;
|
|
85
|
+
client.child.on("close", function () { if (!done) { done = true; resolve(); } });
|
|
86
|
+
setTimeout(function () { if (done) return; done = true; try { client.child.kill("SIGKILL"); } catch (_e) {} resolve(); }, 2000);
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function _drain(client) {
|
|
93
|
+
if (!client.pending) return;
|
|
94
|
+
var sentinel = client.pending.sentinel;
|
|
95
|
+
var marker = "\n" + sentinel + "\n";
|
|
96
|
+
var idx = client.buf.indexOf(marker);
|
|
97
|
+
var startAtZero = client.buf.indexOf(sentinel + "\n") === 0;
|
|
98
|
+
var block;
|
|
99
|
+
if (idx !== -1) { block = client.buf.slice(0, idx); client.buf = client.buf.slice(idx + marker.length); }
|
|
100
|
+
else if (startAtZero) { block = ""; client.buf = client.buf.slice((sentinel + "\n").length); }
|
|
101
|
+
else return;
|
|
102
|
+
var p = client.pending; client.pending = null;
|
|
103
|
+
var parsed;
|
|
104
|
+
try { parsed = _parseBlock(block); } catch (e) { return p.reject(e); }
|
|
105
|
+
if (parsed.error) return p.reject(parsed.error);
|
|
106
|
+
p.resolve({ rows: parsed.rows, rowCount: parsed.rowCount });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function _bindOne(v) {
|
|
110
|
+
if (v === null || v === undefined) return "NULL";
|
|
111
|
+
if (typeof v === "number") return String(v);
|
|
112
|
+
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
|
113
|
+
return "'" + String(v).replace(/'/g, "''") + "'";
|
|
114
|
+
}
|
|
115
|
+
function _bindParams(sql, params) {
|
|
116
|
+
return sql.replace(/\$(\d+)/g, function (_m, n) {
|
|
117
|
+
var i = Number(n) - 1;
|
|
118
|
+
if (i < 0 || i >= params.length) throw new Error("placeholder $" + n + " has no matching param");
|
|
119
|
+
var v = params[i];
|
|
120
|
+
// whereInArray emits `col = ANY($k)` for postgres with a JS array param;
|
|
121
|
+
// a real node-postgres driver binds it as a postgres array. Replicate
|
|
122
|
+
// that as an ARRAY[...] literal so the text-shim is faithful.
|
|
123
|
+
if (Array.isArray(v)) return "ARRAY[" + v.map(_bindOne).join(",") + "]";
|
|
124
|
+
return _bindOne(v);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
var _CMD_TAG_RE = /^(INSERT|UPDATE|DELETE|MERGE|SELECT|COPY|MOVE)\b(?:\s+\d+)*\s*$/;
|
|
129
|
+
var _CTRL_TAG_RE = /^(BEGIN|COMMIT|ROLLBACK|SET|RESET|SAVEPOINT|RELEASE|START|CREATE|DROP|ALTER|GRANT|REVOKE|TRUNCATE|COMMENT|DO|CALL|VACUUM|ANALYZE|EXPLAIN|TABLE|SHOW|DISCARD)\b/;
|
|
130
|
+
|
|
131
|
+
function _parseBlock(block) {
|
|
132
|
+
var lines = block.split(/\r?\n/);
|
|
133
|
+
while (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
134
|
+
for (var i = 0; i < lines.length; i++) {
|
|
135
|
+
var em = /^ERROR:\s+([0-9A-Za-z]{5}):\s*(.*)$/.exec(lines[i]);
|
|
136
|
+
if (em) { var err = new Error("Postgres " + em[1] + ": " + em[2]); err.code = em[1]; return { error: err }; }
|
|
137
|
+
}
|
|
138
|
+
var affected = null, dataLines = [];
|
|
139
|
+
for (var j = 0; j < lines.length; j++) {
|
|
140
|
+
var ln = lines[j];
|
|
141
|
+
if (/^(NOTICE|WARNING|DETAIL|HINT|LINE|LOCATION|CONTEXT|STATEMENT):/.test(ln)) continue;
|
|
142
|
+
var tm = _CMD_TAG_RE.exec(ln);
|
|
143
|
+
if (tm) { var nums = ln.trim().split(/\s+/).slice(1).map(Number); if (nums.length) affected = nums[nums.length - 1]; continue; }
|
|
144
|
+
if (_CTRL_TAG_RE.test(ln) && ln.indexOf("\t") === -1) continue;
|
|
145
|
+
dataLines.push(ln);
|
|
146
|
+
}
|
|
147
|
+
var rows = [];
|
|
148
|
+
if (dataLines.length >= 1) {
|
|
149
|
+
var headers = dataLines[0].split("\t");
|
|
150
|
+
for (var k = 1; k < dataLines.length; k++) {
|
|
151
|
+
var cells = dataLines[k].split("\t"), row = {};
|
|
152
|
+
for (var c = 0; c < headers.length; c++) {
|
|
153
|
+
var cell = cells[c];
|
|
154
|
+
row[headers[c]] = (cell === NULL_SENTINEL || cell === undefined) ? null : cell;
|
|
155
|
+
}
|
|
156
|
+
rows.push(row);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { rows: rows, rowCount: (affected !== null) ? affected : rows.length, error: null };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Thin externalDb over the persistent psql session: exactly the query /
|
|
163
|
+
// transaction / dialect surface the dispatcher consumes. transaction runs
|
|
164
|
+
// BEGIN/COMMIT on the same session (the dispatcher's claim path is the only
|
|
165
|
+
// transactional one).
|
|
166
|
+
function _pgExternalDb(client, driver) {
|
|
167
|
+
var xdb = {
|
|
168
|
+
dialect: "postgres",
|
|
169
|
+
query: function (s, p) {
|
|
170
|
+
// The dispatcher binds JS Date params for the timestamp columns; the
|
|
171
|
+
// psql shim inlines params textually, so Dates must become ISO strings
|
|
172
|
+
// (a JS Date.toString() is not a portable TIMESTAMPTZ literal).
|
|
173
|
+
var bound = (p || []).map(function (v) { return v instanceof Date ? v.toISOString() : v; });
|
|
174
|
+
return driver.query(client, s, bound);
|
|
175
|
+
},
|
|
176
|
+
transaction: async function (fn) {
|
|
177
|
+
await driver.query(client, "BEGIN", []);
|
|
178
|
+
try { var r = await fn(xdb); await driver.query(client, "COMMIT", []); return r; }
|
|
179
|
+
catch (e) { try { await driver.query(client, "ROLLBACK", []); } catch (_e) {} throw e; }
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
return xdb;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function _stubTransport() {
|
|
186
|
+
var calls = []; var nextStatus = 200;
|
|
187
|
+
var fn = function (url, body, headers) { calls.push({ url: url, headers: headers }); return Promise.resolve({ status: nextStatus }); };
|
|
188
|
+
fn.calls = calls; fn.setStatus = function (s) { nextStatus = s; };
|
|
189
|
+
return fn;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function _dropTables() {
|
|
193
|
+
_psql("DROP TABLE IF EXISTS " + DELIVERIES_TABLE + " CASCADE; DROP TABLE IF EXISTS " + ENDPOINTS_TABLE + " CASCADE;");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function run() {
|
|
197
|
+
var pg = await services.requireService("postgres");
|
|
198
|
+
if (!pg.ok) throw new Error("postgres unreachable: " + pg.reason);
|
|
199
|
+
_dropTables();
|
|
200
|
+
|
|
201
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-wd-pg-"));
|
|
202
|
+
await helpers.setupVaultOnly(tmpDir); // for vault.seal of the per-endpoint secret
|
|
203
|
+
|
|
204
|
+
var driver = _makeDockerPgDriver();
|
|
205
|
+
var client = await driver.connect();
|
|
206
|
+
var xdb = _pgExternalDb(client, driver);
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
var now = 1700000000000;
|
|
210
|
+
var clock = function () { return now; };
|
|
211
|
+
var transport = _stubTransport();
|
|
212
|
+
var wd = b.webhook.dispatcher({
|
|
213
|
+
externalDb: xdb, httpRequest: transport, maxAttempts: 3,
|
|
214
|
+
retryBackoff: { initialMs: 1000, maxMs: 5000, factor: 2 }, now: clock,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
await wd.declareSchema();
|
|
218
|
+
check("declareSchema renders valid Postgres DDL", true);
|
|
219
|
+
|
|
220
|
+
var secret = "whsec_pg_secret_value";
|
|
221
|
+
await wd.registerEndpoint({ endpointId: "ep_pg", url: "https://1.1.1.1/hooks", eventTypes: ["invoice.paid"], secret: secret });
|
|
222
|
+
// Secret sealed at rest IN REAL POSTGRES.
|
|
223
|
+
var sealedRow = _psql("SELECT secret_sealed FROM " + ENDPOINTS_TABLE + " WHERE endpoint_id = 'ep_pg';").trim();
|
|
224
|
+
check("secret sealed at rest in Postgres (vault: prefix)", sealedRow.indexOf("vault:") === 0);
|
|
225
|
+
check("plaintext secret NOT in Postgres row", sealedRow.indexOf("whsec_pg_secret_value") === -1);
|
|
226
|
+
|
|
227
|
+
// dispatch → one delivery row, delivered (stub 200).
|
|
228
|
+
var res = await wd.dispatch("invoice.paid", { id: "inv_pg_1", amount: 4200 });
|
|
229
|
+
check("dispatch delivered on Postgres", res.delivered === 1 && res.failed === 0);
|
|
230
|
+
var delivered = await wd.deliveries.list({ status: "delivered" });
|
|
231
|
+
check("delivered row persisted in Postgres", delivered.length === 1);
|
|
232
|
+
check("BIGINT attempts coerced to number 1", delivered[0].attempts === 1);
|
|
233
|
+
check("responseStatus 200 round-trips", delivered[0].responseStatus === 200);
|
|
234
|
+
|
|
235
|
+
// transient failure → stays pending with attempts incremented.
|
|
236
|
+
transport.setStatus(503);
|
|
237
|
+
await wd.dispatch("invoice.paid", { id: "inv_pg_2" });
|
|
238
|
+
var pendingRows = _psql("SELECT count(*) FROM " + DELIVERIES_TABLE + " WHERE status = 'pending';").trim();
|
|
239
|
+
check("transient failure pending in Postgres", pendingRows === "1");
|
|
240
|
+
|
|
241
|
+
// processRetries claim transaction + dead-letter after maxAttempts.
|
|
242
|
+
transport.setStatus(500);
|
|
243
|
+
now += 10000; await wd.processRetries(); // attempt 2
|
|
244
|
+
now += 10000; var r3 = await wd.processRetries(); // attempt 3 → dead
|
|
245
|
+
check("processRetries dead-letters on Postgres", r3.dead === 1);
|
|
246
|
+
var dlq = await wd.dlq.list();
|
|
247
|
+
check("DLQ holds dead delivery on Postgres", dlq.length === 1 && dlq[0].attempts === 3);
|
|
248
|
+
|
|
249
|
+
// dlq.replay recovers.
|
|
250
|
+
transport.setStatus(200);
|
|
251
|
+
var replay = await wd.dlq.replay(dlq[0].deliveryId);
|
|
252
|
+
check("dlq.replay delivers on Postgres", replay.ok === true);
|
|
253
|
+
var dlqAfter = await wd.dlq.list();
|
|
254
|
+
check("DLQ empty after replay on Postgres", dlqAfter.length === 0);
|
|
255
|
+
} finally {
|
|
256
|
+
try { await driver.close(client); } catch (_e) {}
|
|
257
|
+
try { helpers.teardownVaultOnly(tmpDir); } catch (_e) {}
|
|
258
|
+
_dropTables();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = { run: run };
|
|
263
|
+
|
|
264
|
+
if (require.main === module) {
|
|
265
|
+
run().then(
|
|
266
|
+
function () { console.log("[webhook-dispatcher-pg] OK — " + helpers.getChecks() + " checks passed"); process.exit(0); },
|
|
267
|
+
function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
|
|
268
|
+
);
|
|
269
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.atomicFile.fdSafeReadSync — TOCTOU-safe fd read (CWE-367 /
|
|
4
|
+
* js/file-system-race). The single owner of the open-fd → fstat →
|
|
5
|
+
* read-fully loop that atomic-file / network-tls / vault-seal-pem / backup
|
|
6
|
+
* all route through, with the security guards exposed as opts:
|
|
7
|
+
*
|
|
8
|
+
* - maxBytes — refuse a file larger than the cap (post-fstat)
|
|
9
|
+
* - refuseSymlink — lstat + refuse a symlink source (no follow)
|
|
10
|
+
* - inodeCheck — refuse if the fd inode != the lstat inode (TOCTOU)
|
|
11
|
+
* - expectedHash — SHA3-512 the content must match
|
|
12
|
+
* - encoding — return a decoded string instead of a Buffer
|
|
13
|
+
* - allowShortRead — slice to the bytes read instead of throwing
|
|
14
|
+
* - errorFor — map a failure KIND to the caller's typed error
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
var fs = require("fs");
|
|
18
|
+
var path = require("path");
|
|
19
|
+
|
|
20
|
+
var helpers = require("../helpers");
|
|
21
|
+
var b = helpers.b;
|
|
22
|
+
var check = helpers.check;
|
|
23
|
+
|
|
24
|
+
function _dir() { return b.testing.tempDir("fdsaferead"); }
|
|
25
|
+
|
|
26
|
+
function _throws(fn) {
|
|
27
|
+
try { fn(); return null; } catch (e) { return e; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function testReadsBufferAndString() {
|
|
31
|
+
var dir = _dir();
|
|
32
|
+
try {
|
|
33
|
+
var p = path.join(dir.path, "data.bin");
|
|
34
|
+
var payload = Buffer.from("fd-safe payload ✓", "utf8");
|
|
35
|
+
fs.writeFileSync(p, payload, { mode: 0o600 });
|
|
36
|
+
var buf = b.atomicFile.fdSafeReadSync(p);
|
|
37
|
+
check("fdSafeReadSync: returns a Buffer by default", Buffer.isBuffer(buf) && buf.equals(payload));
|
|
38
|
+
var str = b.atomicFile.fdSafeReadSync(p, { encoding: "utf8" });
|
|
39
|
+
check("fdSafeReadSync: encoding returns a string", str === payload.toString("utf8"));
|
|
40
|
+
} finally { dir.cleanup(); }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function testMaxBytesCap() {
|
|
44
|
+
var dir = _dir();
|
|
45
|
+
try {
|
|
46
|
+
var p = path.join(dir.path, "big.bin");
|
|
47
|
+
fs.writeFileSync(p, Buffer.alloc(2048), { mode: 0o600 });
|
|
48
|
+
var e = _throws(function () { b.atomicFile.fdSafeReadSync(p, { maxBytes: 1024 }); });
|
|
49
|
+
check("fdSafeReadSync: maxBytes refuses an over-cap file",
|
|
50
|
+
e !== null && /too-large|maxBytes/i.test(e.code + " " + e.message));
|
|
51
|
+
// Under the cap reads fine.
|
|
52
|
+
var ok = b.atomicFile.fdSafeReadSync(p, { maxBytes: 4096 });
|
|
53
|
+
check("fdSafeReadSync: under the cap reads fine", ok.length === 2048);
|
|
54
|
+
} finally { dir.cleanup(); }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function testExpectedHash() {
|
|
58
|
+
var dir = _dir();
|
|
59
|
+
try {
|
|
60
|
+
var p = path.join(dir.path, "hashed.bin");
|
|
61
|
+
fs.writeFileSync(p, Buffer.from("integrity"), { mode: 0o600 });
|
|
62
|
+
var e = _throws(function () {
|
|
63
|
+
b.atomicFile.fdSafeReadSync(p, { expectedHash: "0".repeat(128) });
|
|
64
|
+
});
|
|
65
|
+
check("fdSafeReadSync: wrong expectedHash refuses (integrity)",
|
|
66
|
+
e !== null && /integrity/i.test(e.code + " " + e.message));
|
|
67
|
+
} finally { dir.cleanup(); }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function testEnoent() {
|
|
71
|
+
var dir = _dir();
|
|
72
|
+
try {
|
|
73
|
+
var missing = path.join(dir.path, "nope.bin");
|
|
74
|
+
// Default errorFor → AtomicFileError; an errorFor returning undefined
|
|
75
|
+
// rethrows the raw ENOENT (the network-tls / vault / backup posture).
|
|
76
|
+
var eDefault = _throws(function () { b.atomicFile.fdSafeReadSync(missing); });
|
|
77
|
+
check("fdSafeReadSync: missing file (default errorFor) throws a typed atomic-file error",
|
|
78
|
+
eDefault !== null && eDefault.code === "atomic-file/enoent");
|
|
79
|
+
var eRaw = _throws(function () {
|
|
80
|
+
b.atomicFile.fdSafeReadSync(missing, { errorFor: function () { return undefined; } });
|
|
81
|
+
});
|
|
82
|
+
check("fdSafeReadSync: errorFor undefined rethrows raw ENOENT",
|
|
83
|
+
eRaw !== null && eRaw.code === "ENOENT");
|
|
84
|
+
} finally { dir.cleanup(); }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function testErrorForCustomError() {
|
|
88
|
+
var dir = _dir();
|
|
89
|
+
try {
|
|
90
|
+
var p = path.join(dir.path, "big2.bin");
|
|
91
|
+
fs.writeFileSync(p, Buffer.alloc(4096), { mode: 0o600 });
|
|
92
|
+
function Tagged(msg) { this.message = msg; this.tag = "custom-too-large"; }
|
|
93
|
+
var e = _throws(function () {
|
|
94
|
+
b.atomicFile.fdSafeReadSync(p, {
|
|
95
|
+
maxBytes: 16,
|
|
96
|
+
errorFor: function (kind) { return kind === "too-large" ? new Tagged("over cap") : undefined; },
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
check("fdSafeReadSync: errorFor maps a kind to the caller's typed error",
|
|
100
|
+
e !== null && e.tag === "custom-too-large");
|
|
101
|
+
} finally { dir.cleanup(); }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function testRefuseSymlinkAndInodeHappyPath() {
|
|
105
|
+
var dir = _dir();
|
|
106
|
+
try {
|
|
107
|
+
var p = path.join(dir.path, "real.pem");
|
|
108
|
+
fs.writeFileSync(p, Buffer.from("PEM"), { mode: 0o600 });
|
|
109
|
+
// refuseSymlink + inodeCheck on a regular file reads fine (the vault posture).
|
|
110
|
+
var buf = b.atomicFile.fdSafeReadSync(p, { refuseSymlink: true, inodeCheck: true });
|
|
111
|
+
check("fdSafeReadSync: refuseSymlink+inodeCheck reads a regular file", buf.toString() === "PEM");
|
|
112
|
+
|
|
113
|
+
// A symlink source is refused (no follow). Platform-conditional: Windows
|
|
114
|
+
// without SeCreateSymbolicLink can't create symlinks — skip the assertion.
|
|
115
|
+
var victim = path.join(dir.path, "victim.pem");
|
|
116
|
+
fs.writeFileSync(victim, "SECRET", { mode: 0o600 });
|
|
117
|
+
var link = path.join(dir.path, "link.pem");
|
|
118
|
+
var symlinkOk = true;
|
|
119
|
+
try { fs.symlinkSync(victim, link); } catch (_e) { symlinkOk = false; }
|
|
120
|
+
if (symlinkOk) {
|
|
121
|
+
var e = _throws(function () {
|
|
122
|
+
b.atomicFile.fdSafeReadSync(link, {
|
|
123
|
+
refuseSymlink: true,
|
|
124
|
+
errorFor: function (kind) { return kind === "symlink" ? Object.assign(new Error("symlink"), { kind: "symlink" }) : undefined; },
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
check("fdSafeReadSync: refuseSymlink refuses a symlink source",
|
|
128
|
+
e !== null && e.kind === "symlink");
|
|
129
|
+
} else {
|
|
130
|
+
check("fdSafeReadSync: symlink case skipped (platform lacks symlink privilege)", true);
|
|
131
|
+
}
|
|
132
|
+
} finally { dir.cleanup(); }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function run() {
|
|
136
|
+
testReadsBufferAndString();
|
|
137
|
+
testMaxBytesCap();
|
|
138
|
+
testExpectedHash();
|
|
139
|
+
testEnoent();
|
|
140
|
+
testErrorForCustomError();
|
|
141
|
+
testRefuseSymlinkAndInodeHappyPath();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = { run: run };
|
|
145
|
+
|
|
146
|
+
if (require.main === module) {
|
|
147
|
+
run().then(function () { console.log("OK"); })
|
|
148
|
+
.catch(function (e) { console.error(e.stack || e); process.exit(1); });
|
|
149
|
+
}
|
|
@@ -35,6 +35,11 @@ var LIB_ROOT = path.resolve(__dirname, "..", "..", "lib");
|
|
|
35
35
|
// `[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+`.
|
|
36
36
|
var EMIT_PATTERNS = [
|
|
37
37
|
/(?:_emit|_emitAudit|_auditEmit|emitAudit)\(\s*["']([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+)["']/g,
|
|
38
|
+
// audit().namespaced("ns.scope"[, auditFlag]) — the audit namespace lives in
|
|
39
|
+
// the factory call now, not in each safeEmit/_emitAudit invocation. Scoped to
|
|
40
|
+
// `audit().namespaced` so `observability().namespaced` metric prefixes (a
|
|
41
|
+
// separate vocabulary, not FRAMEWORK_NAMESPACES) are NOT pulled in.
|
|
42
|
+
/audit\(\)\.namespaced\(\s*["']([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+)["']/g,
|
|
38
43
|
/action\s*:\s*["']([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+)["']/g,
|
|
39
44
|
];
|
|
40
45
|
|
|
@@ -126,9 +131,55 @@ function testFrameworkNamespacesShape() {
|
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
133
|
|
|
134
|
+
// auditEmit.gatedReasonEmitter — the gated, reason-hoisting emitter shared by
|
|
135
|
+
// backup / restore / scheduler / config-drift / legal-hold.
|
|
136
|
+
function testGatedReasonEmitter() {
|
|
137
|
+
var auditEmit = require("../../lib/audit-emit");
|
|
138
|
+
var audit = require("../../lib/audit");
|
|
139
|
+
var captured = [];
|
|
140
|
+
var realSafe = audit.safeEmit;
|
|
141
|
+
audit.safeEmit = function (e) { captured.push(e); }; // late-bound default sink
|
|
142
|
+
try {
|
|
143
|
+
var emit = auditEmit.gatedReasonEmitter({ audit: true });
|
|
144
|
+
emit("backup.started", { jobId: 7 }, "success");
|
|
145
|
+
emit("backup.failed", { jobId: 7, reason: "disk-full" }, "failure");
|
|
146
|
+
check("gatedReasonEmitter: reason null when info.reason absent",
|
|
147
|
+
captured[0] && captured[0].action === "backup.started" && captured[0].reason === null);
|
|
148
|
+
check("gatedReasonEmitter: reason hoisted from info.reason",
|
|
149
|
+
captured[1] && captured[1].reason === "disk-full" && captured[1].outcome === "failure");
|
|
150
|
+
check("gatedReasonEmitter: metadata is the info object",
|
|
151
|
+
captured[1] && captured[1].metadata && captured[1].metadata.jobId === 7);
|
|
152
|
+
|
|
153
|
+
// gate OFF — drop-silent, no emit
|
|
154
|
+
var n = captured.length;
|
|
155
|
+
auditEmit.gatedReasonEmitter({ audit: false })("x", { reason: "y" }, "success");
|
|
156
|
+
check("gatedReasonEmitter: gate off suppresses emit", captured.length === n);
|
|
157
|
+
|
|
158
|
+
// extra(info) adds top-level fields (legal-hold's resource)
|
|
159
|
+
auditEmit.gatedReasonEmitter({
|
|
160
|
+
audit: true,
|
|
161
|
+
extra: function (info) { return { resource: { kind: "legal-hold", id: info && info.subjectId } }; },
|
|
162
|
+
})("legalhold.placed", { subjectId: "abc", reason: "litigation" }, "success");
|
|
163
|
+
var last = captured[captured.length - 1];
|
|
164
|
+
check("gatedReasonEmitter: extra() merges top-level fields",
|
|
165
|
+
last && last.resource && last.resource.id === "abc" && last.reason === "litigation");
|
|
166
|
+
|
|
167
|
+
// operator sink routing — goes to the supplied sink, not the framework default
|
|
168
|
+
var sinkEvents = [];
|
|
169
|
+
var m = captured.length;
|
|
170
|
+
auditEmit.gatedReasonEmitter({ audit: true, sink: { safeEmit: function (e) { sinkEvents.push(e); } } })
|
|
171
|
+
("ns.act", { reason: "r" }, "success");
|
|
172
|
+
check("gatedReasonEmitter: routes to the operator sink",
|
|
173
|
+
sinkEvents.length === 1 && sinkEvents[0].reason === "r" && captured.length === m);
|
|
174
|
+
} finally {
|
|
175
|
+
audit.safeEmit = realSafe;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
129
179
|
async function run() {
|
|
130
180
|
testFrameworkNamespacesShape();
|
|
131
181
|
testEveryEmittedNamespaceIsRegistered();
|
|
182
|
+
testGatedReasonEmitter();
|
|
132
183
|
}
|
|
133
184
|
|
|
134
185
|
module.exports = { run: run };
|