@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
|
@@ -448,6 +448,48 @@ function testGuardCsvCellTooLargeCap() {
|
|
|
448
448
|
threw && /maxCellBytes|too-large/.test(threw.message));
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
+
function testGuardCsvCellByteCapMultibyte() {
|
|
452
|
+
// The cap is named in BYTES; a multibyte char must count its UTF-8 byte
|
|
453
|
+
// width, not its UTF-16 code-unit count. "é" (U+00E9) is 1 code unit but
|
|
454
|
+
// 2 UTF-8 bytes. Five of them: .length === 5, byteLength === 10.
|
|
455
|
+
var multibyte = "é".repeat(5);
|
|
456
|
+
check("multibyte fixture: .length 5 < byteLength 10",
|
|
457
|
+
multibyte.length === 5 && Buffer.byteLength(multibyte, "utf8") === 10);
|
|
458
|
+
|
|
459
|
+
// Cap at 6 bytes. byteLength(10) > 6 → refuse. The old char-length check
|
|
460
|
+
// (5 <= 6) would have let this 10-byte cell through under-enforced.
|
|
461
|
+
var threwCell = null;
|
|
462
|
+
try {
|
|
463
|
+
b.guardCsv.escapeCell(multibyte, { maxCellBytes: 6 });
|
|
464
|
+
} catch (e) { threwCell = e; }
|
|
465
|
+
check("escapeCell: multibyte cell over byte cap refused",
|
|
466
|
+
threwCell && threwCell.code === "csv.cell-too-large");
|
|
467
|
+
check("escapeCell: refusal reports the byte count, not the code-unit count",
|
|
468
|
+
threwCell && /\b10 bytes\b/.test(threwCell.message));
|
|
469
|
+
|
|
470
|
+
// The same cell through the shipped serialize consumer path.
|
|
471
|
+
var threwSer = null;
|
|
472
|
+
try {
|
|
473
|
+
b.guardCsv.serialize([[multibyte]], { maxCellBytes: 6 });
|
|
474
|
+
} catch (e) { threwSer = e; }
|
|
475
|
+
check("serialize: multibyte cell over byte cap refused",
|
|
476
|
+
threwSer && /maxCellBytes|too-large/.test(threwSer.message));
|
|
477
|
+
|
|
478
|
+
// A multibyte cell UNDER the byte cap is not falsely flagged: two "é" =
|
|
479
|
+
// 4 bytes < 6. And ASCII at the boundary is unchanged (5 bytes <= 6).
|
|
480
|
+
var underThrew = false;
|
|
481
|
+
try {
|
|
482
|
+
b.guardCsv.escapeCell("éé", { maxCellBytes: 6 });
|
|
483
|
+
} catch (_e) { underThrew = true; }
|
|
484
|
+
check("escapeCell: multibyte cell under byte cap accepted", underThrew === false);
|
|
485
|
+
|
|
486
|
+
var asciiThrew = false;
|
|
487
|
+
try {
|
|
488
|
+
b.guardCsv.escapeCell("alice", { maxCellBytes: 6 });
|
|
489
|
+
} catch (_e) { asciiThrew = true; }
|
|
490
|
+
check("escapeCell: ASCII under byte cap unchanged", asciiThrew === false);
|
|
491
|
+
}
|
|
492
|
+
|
|
451
493
|
function testGuardCsvTooManyRows() {
|
|
452
494
|
var threw = null;
|
|
453
495
|
try {
|
|
@@ -560,6 +602,46 @@ function testGuardCsvCompliancePosture() {
|
|
|
560
602
|
check("compliancePosture: unknown name throws", threw && /unknown/.test(threw.message));
|
|
561
603
|
}
|
|
562
604
|
|
|
605
|
+
function testGdprPostureMatchesBalancedTier() {
|
|
606
|
+
// csv is a `content` guard. Its four-posture COMPLIANCE_POSTURES map must be
|
|
607
|
+
// built by gateContract.compliancePostures(PROFILES, { base, overlays }) like
|
|
608
|
+
// every other content guard: hipaa/pci/soc2 -> strict tier, gdpr -> balanced
|
|
609
|
+
// tier, with piiPolicy:"redact" overlaid on hipaa/pci/gdpr only.
|
|
610
|
+
//
|
|
611
|
+
// The drift this pins: a hand-written PARTIAL gdpr literal omitted keys
|
|
612
|
+
// (nullByteHandling / trailingWhitespacePolicy / bomPrefix / ...) that
|
|
613
|
+
// backfilled from the strict-leaning DEFAULTS at resolve time, making gdpr an
|
|
614
|
+
// incoherent strict/balanced hybrid. Once routed, gdpr is the balanced tier
|
|
615
|
+
// plus the forensic-snippet budget (base/2 = 128) plus the piiPolicy overlay.
|
|
616
|
+
var P = require("../../lib/guard-csv.js");
|
|
617
|
+
var POSTURES = P.COMPLIANCE_POSTURES;
|
|
618
|
+
|
|
619
|
+
// PRIMARY (deep-equal): gdpr resolves to the balanced tier + 128-byte snippet
|
|
620
|
+
// budget + the piiPolicy overlay — no DEFAULTS backfill.
|
|
621
|
+
var wantGdpr = Object.assign({}, P.PROFILES.balanced, {
|
|
622
|
+
forensicSnippetBytes: 128,
|
|
623
|
+
piiPolicy: "redact",
|
|
624
|
+
});
|
|
625
|
+
check("gdpr posture deep-equals balanced tier + forensic(128) + piiPolicy overlay",
|
|
626
|
+
JSON.stringify(POSTURES.gdpr) === JSON.stringify(wantGdpr));
|
|
627
|
+
|
|
628
|
+
// Overlay lands on the right postures only.
|
|
629
|
+
check("piiPolicy overlay survives on hipaa", POSTURES.hipaa.piiPolicy === "redact");
|
|
630
|
+
check("piiPolicy overlay survives on pci-dss", POSTURES["pci-dss"].piiPolicy === "redact");
|
|
631
|
+
check("piiPolicy overlay survives on gdpr", POSTURES.gdpr.piiPolicy === "redact");
|
|
632
|
+
check("soc2 carries no piiPolicy overlay", POSTURES.soc2.piiPolicy === undefined);
|
|
633
|
+
|
|
634
|
+
// REGRESSION (deep-equal): hipaa is the strict tier + 256-byte snippet budget
|
|
635
|
+
// + the piiPolicy overlay — confirms the strict-tier postures are coherent
|
|
636
|
+
// (no partial-literal backfill) after routing too.
|
|
637
|
+
var wantHipaa = Object.assign({}, P.PROFILES.strict, {
|
|
638
|
+
forensicSnippetBytes: 256,
|
|
639
|
+
piiPolicy: "redact",
|
|
640
|
+
});
|
|
641
|
+
check("hipaa posture deep-equals strict tier + forensic(256) + piiPolicy overlay",
|
|
642
|
+
JSON.stringify(POSTURES.hipaa) === JSON.stringify(wantHipaa));
|
|
643
|
+
}
|
|
644
|
+
|
|
563
645
|
async function testGuardCsvGateClean() {
|
|
564
646
|
var g = b.guardCsv.gate({ profile: "strict" });
|
|
565
647
|
var d = await g.check({ bytes: Buffer.from("a,b\r\n1,2\r\n"), filename: "ok.csv" });
|
|
@@ -754,6 +836,7 @@ async function run() {
|
|
|
754
836
|
testGuardCsvNullByteReject();
|
|
755
837
|
testGuardCsvNumericPrecisionDecimalString();
|
|
756
838
|
testGuardCsvCellTooLargeCap();
|
|
839
|
+
testGuardCsvCellByteCapMultibyte();
|
|
757
840
|
testGuardCsvTooManyRows();
|
|
758
841
|
testGuardCsvValidateBidi();
|
|
759
842
|
testGuardCsvValidateClean();
|
|
@@ -766,6 +849,7 @@ async function run() {
|
|
|
766
849
|
testGuardCsvSchemaNonNullable();
|
|
767
850
|
testGuardCsvSchemaRegex();
|
|
768
851
|
testGuardCsvCompliancePosture();
|
|
852
|
+
testGdprPostureMatchesBalancedTier();
|
|
769
853
|
await testGuardCsvGateClean();
|
|
770
854
|
await testGuardCsvGateRefuseBidi();
|
|
771
855
|
await testGuardCsvGateSanitize();
|
|
@@ -65,6 +65,57 @@ function testGuardEmailLengthCaps() {
|
|
|
65
65
|
}));
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
function testGuardEmailMultibyteByteCaps() {
|
|
69
|
+
// A cap named in *Bytes must measure UTF-8 bytes, not UTF-16 .length code
|
|
70
|
+
// units. A 2-byte codepoint (U+00E9, "e acute") counts as 1 code unit, so a
|
|
71
|
+
// length-based comparison under-enforces: input over the byte cap slips
|
|
72
|
+
// through. Build inputs whose byteLength EXCEEDS the cap while .length does
|
|
73
|
+
// NOT, and assert each cap fires. Codepoint built programmatically so the
|
|
74
|
+
// source stays pure ASCII.
|
|
75
|
+
var acute = String.fromCodePoint(0x00e9); // U+00E9 = 2 UTF-8 bytes
|
|
76
|
+
var mb40 = new Array(41).join(acute); // .length 40, 80 bytes
|
|
77
|
+
var mb50 = new Array(51).join(acute); // .length 50, 100 bytes
|
|
78
|
+
|
|
79
|
+
// Message total-byte cap (maxBytes).
|
|
80
|
+
var rvMsg = b.guardEmail.validateMessage(mb50, { profile: "strict", maxBytes: 60 });
|
|
81
|
+
check("message maxBytes cap measures bytes (multibyte over byte cap → too-large)",
|
|
82
|
+
rvMsg.issues.some(function (i) { return i.kind === "too-large"; }));
|
|
83
|
+
|
|
84
|
+
// Total-address byte cap (maxAddressBytes).
|
|
85
|
+
var rvAddr = b.guardEmail.validateAddress(mb40 + "@example.com",
|
|
86
|
+
{ profile: "strict", maxAddressBytes: 60, maxLocalPartBytes: 100000, maxDomainBytes: 100000 });
|
|
87
|
+
check("address maxAddressBytes cap measures bytes (multibyte over byte cap → address-cap)",
|
|
88
|
+
rvAddr.issues.some(function (i) { return i.kind === "address-cap"; }));
|
|
89
|
+
|
|
90
|
+
// Local-part byte cap (maxLocalPartBytes).
|
|
91
|
+
var rvLp = b.guardEmail.validateAddress(mb40 + "@example.com",
|
|
92
|
+
{ profile: "strict", maxLocalPartBytes: 60, maxAddressBytes: 100000, maxDomainBytes: 100000 });
|
|
93
|
+
check("local-part maxLocalPartBytes cap measures bytes (multibyte over byte cap → local-part-cap)",
|
|
94
|
+
rvLp.issues.some(function (i) { return i.kind === "local-part-cap"; }));
|
|
95
|
+
|
|
96
|
+
// Domain byte cap (maxDomainBytes).
|
|
97
|
+
var rvDom = b.guardEmail.validateAddress("a@" + mb40 + ".com",
|
|
98
|
+
{ profile: "strict", maxDomainBytes: 60, maxAddressBytes: 100000, maxLocalPartBytes: 100000 });
|
|
99
|
+
check("domain maxDomainBytes cap measures bytes (multibyte over byte cap → domain-cap)",
|
|
100
|
+
rvDom.issues.some(function (i) { return i.kind === "domain-cap"; }));
|
|
101
|
+
|
|
102
|
+
// Header-line byte cap (maxHeaderLineBytes).
|
|
103
|
+
var msg = "From: alice@example.com\r\nSubject: " + mb40 + "\r\n\r\nbody\r\n";
|
|
104
|
+
var rvHdr = b.guardEmail.validateMessage(msg, { profile: "strict", maxHeaderLineBytes: 60 });
|
|
105
|
+
check("header-line maxHeaderLineBytes cap measures bytes (multibyte over byte cap → header-line-cap)",
|
|
106
|
+
rvHdr.issues.some(function (i) { return i.kind === "header-line-cap"; }));
|
|
107
|
+
|
|
108
|
+
// ASCII regression: clean inputs are unchanged; a real over-cap ASCII
|
|
109
|
+
// local-part still fires (byte count == code-unit count for ASCII).
|
|
110
|
+
var cleanAddr = b.guardEmail.validateAddress("alice@example.com", { profile: "strict" });
|
|
111
|
+
check("ASCII clean address unchanged (byte-cap fix is ASCII-transparent)",
|
|
112
|
+
cleanAddr.ok === true && cleanAddr.issues.length === 0);
|
|
113
|
+
var asciiLp = b.guardEmail.validateAddress(new Array(67).join("a") + "@example.com",
|
|
114
|
+
{ profile: "strict" });
|
|
115
|
+
check("ASCII local-part > 64 still caps after byte-measure fix",
|
|
116
|
+
asciiLp.issues.some(function (i) { return i.kind === "local-part-cap"; }));
|
|
117
|
+
}
|
|
118
|
+
|
|
68
119
|
function testGuardEmailIpLiteral() {
|
|
69
120
|
var rv = b.guardEmail.validateAddress("user@[1.2.3.4]", { profile: "strict" });
|
|
70
121
|
check("IP literal address detected (DMARC alignment bypass)",
|
|
@@ -138,6 +189,31 @@ function testGuardEmailCrlfHeaderInjection() {
|
|
|
138
189
|
}));
|
|
139
190
|
}
|
|
140
191
|
|
|
192
|
+
function testGuardEmailAutoRouterHeaderInjection() {
|
|
193
|
+
// The auto-router footgun: a value an operator intends as a single address
|
|
194
|
+
// but that carries CRLF + an injected header. `validate` sees the newline,
|
|
195
|
+
// treats it as a message, and (before the fix) the bare `a@b.com` line was
|
|
196
|
+
// silently dropped while the injected `Bcc` passed — ok:true. The shared
|
|
197
|
+
// mimeParse.classifyHeaderBlock now surfaces the colon-less header-section
|
|
198
|
+
// line as malformed → ok:false.
|
|
199
|
+
var rv = b.guardEmail.validate("a@b.com\r\nBcc: evil@x.com", { profile: "strict" });
|
|
200
|
+
check("auto-router CRLF+injected-header → ok:false",
|
|
201
|
+
rv.ok === false &&
|
|
202
|
+
rv.issues.some(function (i) { return i.kind === "malformed-header-line"; }));
|
|
203
|
+
|
|
204
|
+
// A bare address with a trailing CRLF is the same footgun shape.
|
|
205
|
+
var rv2 = b.guardEmail.validate("alice@example.com\r\n", { profile: "strict" });
|
|
206
|
+
check("auto-router address+trailing-CRLF → ok:false", rv2.ok === false);
|
|
207
|
+
|
|
208
|
+
// A well-formed RFC 5322 message must still pass — header fields, folding, and
|
|
209
|
+
// bare BODY lines after the blank boundary are all legitimate.
|
|
210
|
+
var ok = b.guardEmail.validate(
|
|
211
|
+
"From: a@example.com\r\nTo: b@example.com\r\nSubject: long\r\n folded\r\n" +
|
|
212
|
+
"Date: Mon, 5 May 2026 10:00:00 +0000\r\n\r\nbare body line\r\nanother\r\n",
|
|
213
|
+
{ profile: "strict" });
|
|
214
|
+
check("well-formed message (folding + body lines) still passes", ok.ok === true);
|
|
215
|
+
}
|
|
216
|
+
|
|
141
217
|
function testGuardEmailDisplayNameSpoof() {
|
|
142
218
|
var msg = 'From: "support@apple.com" <attacker@evil.com>\r\n' +
|
|
143
219
|
"To: bob@example.com\r\nSubject: x\r\n\r\nbody\r\n";
|
|
@@ -227,6 +303,7 @@ async function run() {
|
|
|
227
303
|
testGuardEmailCleanAddress();
|
|
228
304
|
testGuardEmailMultiAt();
|
|
229
305
|
testGuardEmailLengthCaps();
|
|
306
|
+
testGuardEmailMultibyteByteCaps();
|
|
230
307
|
testGuardEmailIpLiteral();
|
|
231
308
|
testGuardEmailAddressComment();
|
|
232
309
|
testGuardEmailPunycode();
|
|
@@ -235,6 +312,7 @@ async function run() {
|
|
|
235
312
|
testGuardEmailSyntaxReject();
|
|
236
313
|
testGuardEmailBareLfSmuggling();
|
|
237
314
|
testGuardEmailCrlfHeaderInjection();
|
|
315
|
+
testGuardEmailAutoRouterHeaderInjection();
|
|
238
316
|
testGuardEmailDisplayNameSpoof();
|
|
239
317
|
testGuardEmailBom();
|
|
240
318
|
testGuardEmailHeaderLineCap();
|
|
@@ -344,6 +344,28 @@ function testGuardFilenameSanitizeStripMode() {
|
|
|
344
344
|
captured[0].outcome === "success");
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
+
function testGdprPostureMatchesBalancedTier() {
|
|
348
|
+
// gdpr is the balanced tier for a filename guard: it allows non-ASCII
|
|
349
|
+
// (data-minimization keeps the value usable) rather than inheriting the
|
|
350
|
+
// strict tier's requireAscii:true. The drift was a partial gdpr posture
|
|
351
|
+
// object that omitted requireAscii, so resolved opts silently backfilled
|
|
352
|
+
// it from the strict-derived defaults — making gdpr a strict/balanced
|
|
353
|
+
// hybrid. Drive the real consumer path: a non-ASCII leaf must NOT raise
|
|
354
|
+
// a non-ascii issue under gdpr.
|
|
355
|
+
var rv = b.guardFilename.validate("café.txt", { compliancePosture: "gdpr" });
|
|
356
|
+
check("gdpr (balanced tier) does not flag non-ascii on a filename leaf",
|
|
357
|
+
!rv.issues.some(function (issue) { return issue.kind === "non-ascii"; }));
|
|
358
|
+
|
|
359
|
+
// The deliberate per-posture overlay survives the routing: gdpr strips
|
|
360
|
+
// bidi / control on leaf names (data-minimization) where the balanced
|
|
361
|
+
// profile would reject them. Both together prove balanced-tier base +
|
|
362
|
+
// intended overlay.
|
|
363
|
+
check("gdpr posture overlay keeps bidiPolicy=strip",
|
|
364
|
+
b.guardFilename.COMPLIANCE_POSTURES.gdpr.bidiPolicy === "strip");
|
|
365
|
+
check("gdpr posture overlay keeps controlPolicy=strip",
|
|
366
|
+
b.guardFilename.COMPLIANCE_POSTURES.gdpr.controlPolicy === "strip");
|
|
367
|
+
}
|
|
368
|
+
|
|
347
369
|
function testGuardFilenameCompliancePosture() {
|
|
348
370
|
var hipaa = b.guardFilename.compliancePosture("hipaa");
|
|
349
371
|
check("compliancePosture('hipaa') sets reject policies",
|
|
@@ -386,6 +408,7 @@ async function run() {
|
|
|
386
408
|
testGuardFilenameDoubleExtension();
|
|
387
409
|
testGuardFilenameOverlongUtf8();
|
|
388
410
|
testGuardFilenameAsciiOnlyStrict();
|
|
411
|
+
testGdprPostureMatchesBalancedTier();
|
|
389
412
|
testGuardFilenameClean();
|
|
390
413
|
testGuardFilenameSanitize();
|
|
391
414
|
testGuardFilenameSanitizeStripMode();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gate-disposition coverage — every finding kind a content guard can emit MUST
|
|
4
|
+
* be classified by that guard's dispositionFor (→ refuse / sanitize / audit), so
|
|
5
|
+
* buildContentGate never falls back to severity for a guard's own finding. The
|
|
6
|
+
* conservative severity fallback exists only for operator-injected extraIssues
|
|
7
|
+
* and as a secure backstop; this test proves it is dead code for guard findings.
|
|
8
|
+
*
|
|
9
|
+
* The kind set is SCANNED from each guard's source (every `kind:` literal,
|
|
10
|
+
* including ternary branches) plus the shared character-threat kinds emitted by
|
|
11
|
+
* codepointClass.detectCharThreats — so a NEW finding kind added to a guard
|
|
12
|
+
* without a dispositionFor mapping fails this test (RED) rather than silently
|
|
13
|
+
* inheriting the conservative refuse.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
var fs = require("fs");
|
|
17
|
+
var path = require("path");
|
|
18
|
+
var helpers = require("../helpers");
|
|
19
|
+
var check = helpers.check;
|
|
20
|
+
|
|
21
|
+
var LIB = path.join(__dirname, "..", "..", "lib");
|
|
22
|
+
|
|
23
|
+
// Shared char-threat kinds come from codepointClass.detectCharThreats, not the
|
|
24
|
+
// guard source, so they are added to every content guard's expected set
|
|
25
|
+
// (zero-width is opt-in via the detector's 4th arg for the markup guards).
|
|
26
|
+
var SHARED_CHAR_THREATS = ["bidi-override", "null-byte", "control-char", "zero-width"];
|
|
27
|
+
|
|
28
|
+
var GUARDS = [
|
|
29
|
+
{ name: "csv", file: "guard-csv.js", profiles: ["strict", "balanced", "permissive", "email-attachment"] },
|
|
30
|
+
{ name: "html", file: "guard-html.js", profiles: ["strict", "balanced", "permissive"] },
|
|
31
|
+
{ name: "svg", file: "guard-svg.js", profiles: ["strict", "balanced", "permissive"] },
|
|
32
|
+
{ name: "xml", file: "guard-xml.js", profiles: ["strict", "balanced", "permissive"] },
|
|
33
|
+
{ name: "markdown", file: "guard-markdown.js", profiles: ["strict", "balanced", "permissive"] },
|
|
34
|
+
{ name: "json", file: "guard-json.js", profiles: ["strict", "balanced", "permissive"] },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// The guard-KIND taxonomy (INTEGRATION_FIXTURES.kind / the guard's KIND export)
|
|
38
|
+
// is "content" | "entries" | "filename" — not a finding kind, so the scan skips
|
|
39
|
+
// these (they are never produced by detect and never reach a content gate).
|
|
40
|
+
var NON_FINDING_KINDS = { content: true, entries: true, filename: true };
|
|
41
|
+
|
|
42
|
+
// Extract every finding kind from a guard source: the token after `kind:` up to
|
|
43
|
+
// the first comma, then every quoted string inside it (covers both the literal
|
|
44
|
+
// `kind: "x"` and the ternary `kind: c ? "a" : "b"` forms).
|
|
45
|
+
function _scanKinds(source) {
|
|
46
|
+
var kinds = {};
|
|
47
|
+
var re = /kind:\s*([^,\n]+)/g;
|
|
48
|
+
var m;
|
|
49
|
+
while ((m = re.exec(source)) !== null) {
|
|
50
|
+
var seg = m[1];
|
|
51
|
+
var qre = /"([a-z][a-z0-9-]*)"/g;
|
|
52
|
+
var q;
|
|
53
|
+
while ((q = qre.exec(seg)) !== null) {
|
|
54
|
+
if (!NON_FINDING_KINDS[q[1]]) kinds[q[1]] = true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return Object.keys(kinds);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function run() {
|
|
61
|
+
var VALID = { refuse: true, sanitize: true, audit: true };
|
|
62
|
+
|
|
63
|
+
GUARDS.forEach(function (g) {
|
|
64
|
+
var mod = require(path.join(LIB, g.file));
|
|
65
|
+
check("[" + g.name + "] exposes dispositionFor test hook",
|
|
66
|
+
typeof mod._gateDispositionForTest === "function");
|
|
67
|
+
if (typeof mod._gateDispositionForTest !== "function") return;
|
|
68
|
+
|
|
69
|
+
var kinds = _scanKinds(fs.readFileSync(path.join(LIB, g.file), "utf8"));
|
|
70
|
+
SHARED_CHAR_THREATS.forEach(function (k) { if (kinds.indexOf(k) === -1) kinds.push(k); });
|
|
71
|
+
|
|
72
|
+
check("[" + g.name + "] scanned a non-empty finding-kind set", kinds.length > 0);
|
|
73
|
+
|
|
74
|
+
var unclassified = [];
|
|
75
|
+
g.profiles.forEach(function (profile) {
|
|
76
|
+
var opts = mod.resolveOpts({ profile: profile });
|
|
77
|
+
kinds.forEach(function (kind) {
|
|
78
|
+
var disp = mod._gateDispositionForTest({ kind: kind, severity: "high" }, opts);
|
|
79
|
+
if (!VALID[disp]) unclassified.push(profile + ":" + kind + "=" + disp);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
check("[" + g.name + "] every finding kind is classified across all profiles" +
|
|
83
|
+
(unclassified.length ? " (UNCLASSIFIED: " + unclassified.slice(0, 8).join(", ") + ")" : ""),
|
|
84
|
+
unclassified.length === 0);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { run: run };
|
|
89
|
+
|
|
90
|
+
if (require.main === module) {
|
|
91
|
+
try { run(); console.log("[guard-gate-disposition-coverage] OK — " + helpers.getChecks() + " checks passed"); }
|
|
92
|
+
catch (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
|
|
93
|
+
}
|
|
@@ -233,6 +233,52 @@ function testGuardHtmlSizeCaps() {
|
|
|
233
233
|
rv.issues.some(function (issue) { return issue.kind === "depth-cap"; }));
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
// Byte caps measure UTF-8 bytes, not UTF-16 code units — a multibyte
|
|
237
|
+
// payload under the .length cap but over the byte cap must still be
|
|
238
|
+
// refused. "é" is 1 code unit but 2 UTF-8 bytes.
|
|
239
|
+
function testGuardHtmlByteCaps() {
|
|
240
|
+
var multi = "é".repeat(8); // .length 8, 16 UTF-8 bytes
|
|
241
|
+
check("fixture: multibyte byteLength exceeds .length",
|
|
242
|
+
multi.length === 8 && Buffer.byteLength(multi, "utf8") === 16);
|
|
243
|
+
|
|
244
|
+
// Top-level maxBytes — validate path (tokenizer refusal surfaces as an issue).
|
|
245
|
+
var rvSize = b.guardHtml.validate(multi, { profile: "strict", maxBytes: 10 });
|
|
246
|
+
check("maxBytes caps multibyte input over the byte limit (validate)",
|
|
247
|
+
rvSize.issues.some(function (issue) {
|
|
248
|
+
return issue.kind === "tokenize-failed" ||
|
|
249
|
+
/exceeds maxBytes/.test(issue.snippet || "");
|
|
250
|
+
}));
|
|
251
|
+
|
|
252
|
+
// Top-level maxBytes — sanitize path throws.
|
|
253
|
+
var threwBytes = null;
|
|
254
|
+
try { b.guardHtml.sanitize(multi, { profile: "strict", maxBytes: 10 }); }
|
|
255
|
+
catch (e) { threwBytes = e; }
|
|
256
|
+
check("maxBytes caps multibyte input over the byte limit (sanitize)",
|
|
257
|
+
threwBytes && /exceeds maxBytes/.test(threwBytes.message));
|
|
258
|
+
|
|
259
|
+
// Multibyte under BOTH the byte cap and .length stays accepted.
|
|
260
|
+
var smallMulti = "é".repeat(3); // .length 3, 6 bytes
|
|
261
|
+
var rvOk = b.guardHtml.validate(smallMulti, { profile: "strict", maxBytes: 10 });
|
|
262
|
+
check("maxBytes accepts multibyte input under the byte limit",
|
|
263
|
+
!rvOk.issues.some(function (issue) {
|
|
264
|
+
return issue.kind === "tokenize-failed" ||
|
|
265
|
+
/exceeds maxBytes/.test(issue.snippet || "");
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
// Per-attribute maxAttrValueBytes measures bytes.
|
|
269
|
+
var attrVal = "é".repeat(8); // .length 8, 16 bytes
|
|
270
|
+
var rvAttr = b.guardHtml.validate('<a title="' + attrVal + '">x</a>',
|
|
271
|
+
{ profile: "strict", maxAttrValueBytes: 10 });
|
|
272
|
+
check("maxAttrValueBytes caps multibyte attribute value over the byte limit",
|
|
273
|
+
rvAttr.issues.some(function (issue) { return issue.kind === "attr-value-too-large"; }));
|
|
274
|
+
|
|
275
|
+
// ASCII attribute value under the byte cap stays accepted.
|
|
276
|
+
var rvAttrOk = b.guardHtml.validate('<a title="short">x</a>',
|
|
277
|
+
{ profile: "strict", maxAttrValueBytes: 10 });
|
|
278
|
+
check("maxAttrValueBytes accepts attribute value under the byte limit",
|
|
279
|
+
!rvAttrOk.issues.some(function (issue) { return issue.kind === "attr-value-too-large"; }));
|
|
280
|
+
}
|
|
281
|
+
|
|
236
282
|
// ---- Sanitize round-trip ----
|
|
237
283
|
|
|
238
284
|
function testGuardHtmlSanitizeBasic() {
|
|
@@ -328,6 +374,18 @@ function testGuardHtmlCompliancePosture() {
|
|
|
328
374
|
threw && /unknown/.test(threw.message));
|
|
329
375
|
}
|
|
330
376
|
|
|
377
|
+
// The gdpr posture is the balanced tier (data-minimization strips bytes
|
|
378
|
+
// rather than rejecting the whole value), so it must carry the balanced
|
|
379
|
+
// profile's attribute allowlist — which keeps `href`. The strict tier's
|
|
380
|
+
// 4-attribute allowlist (class/title/lang/dir) drops href; gdpr must not
|
|
381
|
+
// inherit it.
|
|
382
|
+
function testGdprPostureMatchesBalancedTier() {
|
|
383
|
+
var sanitized = b.guardHtml.sanitize(
|
|
384
|
+
'<a href="http://x.com/p">L</a>', { compliancePosture: "gdpr" });
|
|
385
|
+
check("gdpr posture keeps href on <a> (balanced tier, not strict backfill)",
|
|
386
|
+
sanitized === '<a href="http://x.com/p">L</a>');
|
|
387
|
+
}
|
|
388
|
+
|
|
331
389
|
// ---- Run all ----
|
|
332
390
|
|
|
333
391
|
async function run() {
|
|
@@ -345,10 +403,12 @@ async function run() {
|
|
|
345
403
|
testGuardHtmlNullByte();
|
|
346
404
|
testGuardHtmlControlChar();
|
|
347
405
|
testGuardHtmlSizeCaps();
|
|
406
|
+
testGuardHtmlByteCaps();
|
|
348
407
|
testGuardHtmlSanitizeBasic();
|
|
349
408
|
testGuardHtmlEscape();
|
|
350
409
|
testGuardHtmlBadProfile();
|
|
351
410
|
testGuardHtmlCompliancePosture();
|
|
411
|
+
testGdprPostureMatchesBalancedTier();
|
|
352
412
|
await testGuardHtmlGateClean();
|
|
353
413
|
await testGuardHtmlGateRefuse();
|
|
354
414
|
await testGuardHtmlGateSanitize();
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* guard-image — image content-safety primitive (b.guardImage).
|
|
4
|
+
*
|
|
5
|
+
* Covers: surface; registry parity; magic-byte / declared-MIME mismatch;
|
|
6
|
+
* polyglot rejection; unknown-magic; dimension + frame caps; inspectMagic;
|
|
7
|
+
* and the container-framing metadata strip (sanitize) — EXIF/XMP out of
|
|
8
|
+
* JPEG (APP1), text chunks out of PNG (tEXt), comment/app extensions out of
|
|
9
|
+
* GIF, EXIF/XMP RIFF chunks out of WebP with the VP8X flag bits cleared,
|
|
10
|
+
* the unsupported-format / malformed refusals, and refuse-before-strip for a
|
|
11
|
+
* polyglot / mismatch. The strip is byte-surgery over the linear container
|
|
12
|
+
* framing, not a pixel decoder.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
var helpers = require("../helpers");
|
|
16
|
+
var b = helpers.b;
|
|
17
|
+
var check = helpers.check;
|
|
18
|
+
|
|
19
|
+
function _has(buf, s) { return buf.toString("latin1").indexOf(s) !== -1; }
|
|
20
|
+
function _code(fn) { try { fn(); return null; } catch (e) { return e && e.code; } }
|
|
21
|
+
|
|
22
|
+
// ---- fixture builders (kept ASCII; metadata payloads carry a sentinel) ----
|
|
23
|
+
|
|
24
|
+
function _jpegWithExif() {
|
|
25
|
+
return Buffer.concat([
|
|
26
|
+
Buffer.from([0xFF, 0xD8]), // SOI
|
|
27
|
+
Buffer.from([0xFF, 0xE1, 0x00, 0x0F]), Buffer.from("Exif GPSDATA", "latin1"), // APP1 (EXIF) len=15
|
|
28
|
+
Buffer.from([0xFF, 0xE0, 0x00, 0x10]), Buffer.from("JFIF ABCDEFGHIJ".slice(0, 14), "latin1"), // APP0 (JFIF) len=16
|
|
29
|
+
Buffer.from([0xFF, 0xD9]), // EOI
|
|
30
|
+
]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function _pngWithText() {
|
|
34
|
+
var ihdr = Buffer.concat([Buffer.from([0, 0, 0, 13]), Buffer.from("IHDR", "latin1"),
|
|
35
|
+
Buffer.from([0, 0, 0, 1, 0, 0, 0, 1, 8, 0, 0, 0, 0]), Buffer.from([1, 2, 3, 4])]);
|
|
36
|
+
var text = Buffer.concat([Buffer.from([0, 0, 0, 18]), Buffer.from("tEXt", "latin1"),
|
|
37
|
+
Buffer.from("Comment secret-gps", "latin1"), Buffer.from([9, 9, 9, 9])]);
|
|
38
|
+
var iend = Buffer.concat([Buffer.from([0, 0, 0, 0]), Buffer.from("IEND", "latin1"),
|
|
39
|
+
Buffer.from([0xAE, 0x42, 0x60, 0x82])]);
|
|
40
|
+
return Buffer.concat([Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]), ihdr, text, iend]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function _gifWithComment() {
|
|
44
|
+
return Buffer.concat([
|
|
45
|
+
Buffer.from("GIF89a", "latin1"),
|
|
46
|
+
Buffer.from([1, 0, 1, 0, 0x00, 0, 0]), // LSD 1x1, no GCT
|
|
47
|
+
Buffer.from([0x21, 0xFE, 0x0A]), Buffer.from("secret-gps", "latin1"), Buffer.from([0x00]), // comment ext
|
|
48
|
+
Buffer.from([0x2C, 0, 0, 0, 0, 1, 0, 1, 0, 0x00]), // image descriptor
|
|
49
|
+
Buffer.from([0x02, 0x01, 0x44, 0x00]), // LZW min + sub-block + term
|
|
50
|
+
Buffer.from([0x3B]), // trailer
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function _webpWithExif() {
|
|
55
|
+
return Buffer.concat([
|
|
56
|
+
Buffer.from("RIFF", "latin1"), Buffer.from([34, 0, 0, 0]), Buffer.from("WEBP", "latin1"),
|
|
57
|
+
Buffer.from("VP8 ", "latin1"), Buffer.from([4, 0, 0, 0]), Buffer.from([1, 2, 3, 4]),
|
|
58
|
+
Buffer.from("EXIF", "latin1"), Buffer.from([10, 0, 0, 0]), Buffer.from("secret-gps", "latin1"),
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function testGuardImageSurface() {
|
|
63
|
+
check("guardImage is an object", typeof b.guardImage === "object");
|
|
64
|
+
check("guardImage.NAME === 'image'", b.guardImage.NAME === "image");
|
|
65
|
+
check("guardImage.PROFILES has strict", !!b.guardImage.PROFILES["strict"]);
|
|
66
|
+
check("guardImage.COMPLIANCE_POSTURES gdpr", !!b.guardImage.COMPLIANCE_POSTURES["gdpr"]);
|
|
67
|
+
check("guardImage.validate is a function", typeof b.guardImage.validate === "function");
|
|
68
|
+
check("guardImage.sanitize is a function", typeof b.guardImage.sanitize === "function");
|
|
69
|
+
check("guardImage.gate is a function", typeof b.guardImage.gate === "function");
|
|
70
|
+
check("guardImage.inspectMagic is a function", typeof b.guardImage.inspectMagic === "function");
|
|
71
|
+
check("frameworkError.GuardImageError exposed", typeof b.frameworkError.GuardImageError === "function");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function testGuardImageRegistryParity() {
|
|
75
|
+
check("guardImage registered in guardAll",
|
|
76
|
+
b.guardAll.allGuards().some(function (g) { return (g.name || g.NAME) === "image"; }));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function testInspectMagic() {
|
|
80
|
+
check("inspectMagic detects png",
|
|
81
|
+
b.guardImage.inspectMagic(Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])).indexOf("image/png") !== -1);
|
|
82
|
+
check("inspectMagic empty on garbage",
|
|
83
|
+
b.guardImage.inspectMagic(Buffer.from([0x00, 0x01, 0x02])).length === 0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function testValidateMismatchAndPolyglot() {
|
|
87
|
+
var rv = b.guardImage.validate({ bytes: Buffer.from([0xFF, 0xD8, 0xFF]), declaredMime: "image/png" }, { profile: "strict" });
|
|
88
|
+
check("mismatch: validate ok:false", rv.ok === false);
|
|
89
|
+
check("mismatch: kind reported", rv.issues.some(function (i) { return i.kind === "mime-mismatch"; }));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---- the real metadata strip ----
|
|
93
|
+
|
|
94
|
+
function testStripJpegExif() {
|
|
95
|
+
var src = _jpegWithExif();
|
|
96
|
+
var out = b.guardImage.sanitize({ bytes: src });
|
|
97
|
+
check("jpeg: EXIF (APP1) payload removed", !_has(out.bytes, "GPSDATA") && !_has(out.bytes, "Exif"));
|
|
98
|
+
check("jpeg: JFIF (APP0) preserved", _has(out.bytes, "JFIF"));
|
|
99
|
+
check("jpeg: output shrank", out.bytes.length < src.length);
|
|
100
|
+
check("jpeg: SOI + EOI intact",
|
|
101
|
+
out.bytes[0] === 0xFF && out.bytes[1] === 0xD8 &&
|
|
102
|
+
out.bytes[out.bytes.length - 2] === 0xFF && out.bytes[out.bytes.length - 1] === 0xD9);
|
|
103
|
+
check("jpeg: bag is a fresh object (bytes replaced)", typeof out === "object" && Buffer.isBuffer(out.bytes));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function testStripPngText() {
|
|
107
|
+
var out = b.guardImage.sanitize({ bytes: _pngWithText() });
|
|
108
|
+
check("png: tEXt payload removed", !_has(out.bytes, "secret-gps"));
|
|
109
|
+
check("png: IHDR preserved", _has(out.bytes, "IHDR"));
|
|
110
|
+
check("png: IEND preserved", _has(out.bytes, "IEND"));
|
|
111
|
+
check("png: signature intact", out.bytes[0] === 0x89 && out.bytes[1] === 0x50);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function testStripGifComment() {
|
|
115
|
+
var out = b.guardImage.sanitize({ bytes: _gifWithComment() });
|
|
116
|
+
check("gif: comment payload removed", !_has(out.bytes, "secret-gps"));
|
|
117
|
+
check("gif: header preserved", _has(out.bytes, "GIF89a"));
|
|
118
|
+
check("gif: trailer preserved", out.bytes[out.bytes.length - 1] === 0x3B);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function testStripWebpExif() {
|
|
122
|
+
var out = b.guardImage.sanitize({ bytes: _webpWithExif() });
|
|
123
|
+
check("webp: EXIF payload removed", !_has(out.bytes, "secret-gps"));
|
|
124
|
+
check("webp: VP8 chunk preserved", _has(out.bytes, "VP8 "));
|
|
125
|
+
check("webp: RIFF size rewritten", out.bytes.readUInt32LE(4) === 16);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function testStripWebpVp8xFlags() {
|
|
129
|
+
var webpx = Buffer.concat([
|
|
130
|
+
Buffer.from("RIFF", "latin1"), Buffer.from([0, 0, 0, 0]), Buffer.from("WEBP", "latin1"),
|
|
131
|
+
Buffer.from("VP8X", "latin1"), Buffer.from([10, 0, 0, 0]), Buffer.from([0x0C, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
|
|
132
|
+
Buffer.from("EXIF", "latin1"), Buffer.from([4, 0, 0, 0]), Buffer.from([9, 9, 9, 9]),
|
|
133
|
+
]);
|
|
134
|
+
var out = b.guardImage.sanitize({ bytes: webpx });
|
|
135
|
+
check("webp: VP8X EXIF+XMP flag bits cleared", (out.bytes[20] & 0x0C) === 0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function testRefuseBeforeStrip() {
|
|
139
|
+
// A MIME-mismatch / polyglot must refuse, never strip-and-serve.
|
|
140
|
+
check("mismatch refuses (not strips)",
|
|
141
|
+
_code(function () { b.guardImage.sanitize({ bytes: _jpegWithExif(), declaredMime: "image/png" }, { profile: "strict" }); }) === "image.mime-mismatch");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function testRefuseUnsupportedFormat() {
|
|
145
|
+
var tiff = Buffer.from([0x49, 0x49, 0x2A, 0x00, 8, 0, 0, 0]);
|
|
146
|
+
check("tiff: refuse unsupported-format",
|
|
147
|
+
_code(function () { b.guardImage.sanitize({ bytes: tiff }); }) === "image.sanitize-unsupported-format");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function testRefuseMalformed() {
|
|
151
|
+
var bad = Buffer.from([0xFF, 0xD8, 0xFF, 0xE1, 0x00, 0xFF]); // APP1 length overruns
|
|
152
|
+
check("malformed: refuse, not half-strip",
|
|
153
|
+
_code(function () { b.guardImage.sanitize({ bytes: bad }); }) === "image.sanitize-malformed");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function testBmpPassthrough() {
|
|
157
|
+
// BMP carries no text/EXIF metadata container — pass through unchanged.
|
|
158
|
+
var bmp = Buffer.from([0x42, 0x4D, 0x10, 0x00, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
159
|
+
var out = b.guardImage.sanitize({ bytes: bmp });
|
|
160
|
+
check("bmp: passes through (no metadata container)", out.bytes === bmp);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function run() {
|
|
164
|
+
testGuardImageSurface();
|
|
165
|
+
testGuardImageRegistryParity();
|
|
166
|
+
testInspectMagic();
|
|
167
|
+
testValidateMismatchAndPolyglot();
|
|
168
|
+
testStripJpegExif();
|
|
169
|
+
testStripPngText();
|
|
170
|
+
testStripGifComment();
|
|
171
|
+
testStripWebpExif();
|
|
172
|
+
testStripWebpVp8xFlags();
|
|
173
|
+
testRefuseBeforeStrip();
|
|
174
|
+
testRefuseUnsupportedFormat();
|
|
175
|
+
testRefuseMalformed();
|
|
176
|
+
testBmpPassthrough();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = { run: run };
|
|
180
|
+
|
|
181
|
+
if (require.main === module) {
|
|
182
|
+
try { run(); console.log("[guard-image] OK — " + helpers.getChecks() + " checks passed"); }
|
|
183
|
+
catch (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
|
|
184
|
+
}
|
|
Binary file
|