@blamejs/blamejs-shop 0.5.12 → 0.5.14
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/asset-manifest.json +1 -1
- package/lib/sales-tax-filings.js +143 -0
- package/lib/vendor/MANIFEST.json +632 -416
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +21 -10
- package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +30 -9
- package/lib/vendor/blamejs/.github/dependabot.yml +8 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +7 -2
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +7 -2
- package/lib/vendor/blamejs/.github/workflows/ci.yml +21 -21
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +4 -4
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +7 -7
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +3 -3
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +1 -1
- package/lib/vendor/blamejs/.gitignore +2 -0
- package/lib/vendor/blamejs/CHANGELOG.md +122 -0
- package/lib/vendor/blamejs/NOTICE +26 -1
- package/lib/vendor/blamejs/SECURITY.md +1 -1
- package/lib/vendor/blamejs/api-snapshot.json +207 -5
- package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +29 -0
- package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +1 -0
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +31 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +109 -0
- package/lib/vendor/blamejs/examples/wiki/nginx.conf +106 -0
- package/lib/vendor/blamejs/examples/wiki/package-lock.json +1 -1
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +92 -0
- package/lib/vendor/blamejs/fuzz/asn1-der.fuzz.js +28 -0
- package/lib/vendor/blamejs/fuzz/cms-codec.fuzz.js +17 -0
- package/lib/vendor/blamejs/fuzz/link-header.fuzz.js +18 -0
- package/lib/vendor/blamejs/index.js +6 -0
- package/lib/vendor/blamejs/lib/a2a.js +4 -4
- package/lib/vendor/blamejs/lib/agent-idempotency.js +18 -2
- package/lib/vendor/blamejs/lib/agent-saga.js +36 -8
- package/lib/vendor/blamejs/lib/agent-snapshot.js +63 -6
- package/lib/vendor/blamejs/lib/app-shutdown.js +23 -20
- package/lib/vendor/blamejs/lib/archive-read.js +8 -0
- package/lib/vendor/blamejs/lib/archive-tar.js +12 -2
- package/lib/vendor/blamejs/lib/arg-parser.js +49 -5
- package/lib/vendor/blamejs/lib/asn1-der.js +12 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +47 -1
- package/lib/vendor/blamejs/lib/audit.js +71 -8
- package/lib/vendor/blamejs/lib/auth/ciba.js +14 -2
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +58 -47
- package/lib/vendor/blamejs/lib/auth/jwt.js +34 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +90 -13
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +20 -17
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +214 -23
- package/lib/vendor/blamejs/lib/auth/saml.js +38 -20
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +10 -4
- package/lib/vendor/blamejs/lib/backup/bundle.js +13 -3
- package/lib/vendor/blamejs/lib/backup/index.js +16 -5
- package/lib/vendor/blamejs/lib/backup/manifest.js +18 -5
- package/lib/vendor/blamejs/lib/base32.js +21 -0
- package/lib/vendor/blamejs/lib/break-glass.js +19 -10
- package/lib/vendor/blamejs/lib/bundler.js +5 -1
- package/lib/vendor/blamejs/lib/cache-status.js +3 -7
- package/lib/vendor/blamejs/lib/calendar.js +21 -1
- package/lib/vendor/blamejs/lib/cbor.js +12 -1
- package/lib/vendor/blamejs/lib/cert.js +29 -11
- package/lib/vendor/blamejs/lib/cli.js +94 -24
- package/lib/vendor/blamejs/lib/cloud-events.js +2 -2
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +13 -3
- package/lib/vendor/blamejs/lib/codepoint-class.js +85 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +8 -4
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +19 -4
- package/lib/vendor/blamejs/lib/compliance-ai-act.js +6 -8
- package/lib/vendor/blamejs/lib/content-credentials.js +1 -1
- package/lib/vendor/blamejs/lib/content-digest.js +2 -1
- package/lib/vendor/blamejs/lib/cookies.js +13 -3
- package/lib/vendor/blamejs/lib/cose.js +31 -16
- package/lib/vendor/blamejs/lib/crypto-field.js +32 -0
- package/lib/vendor/blamejs/lib/crypto-oprf.js +11 -2
- package/lib/vendor/blamejs/lib/crypto-xwing.js +22 -4
- package/lib/vendor/blamejs/lib/crypto.js +314 -19
- package/lib/vendor/blamejs/lib/csp.js +11 -3
- package/lib/vendor/blamejs/lib/daemon.js +270 -24
- package/lib/vendor/blamejs/lib/dark-patterns.js +5 -2
- package/lib/vendor/blamejs/lib/db-declare-view.js +2 -2
- package/lib/vendor/blamejs/lib/db-query.js +34 -7
- package/lib/vendor/blamejs/lib/db.js +18 -6
- package/lib/vendor/blamejs/lib/dbsc.js +17 -0
- package/lib/vendor/blamejs/lib/dev.js +21 -2
- package/lib/vendor/blamejs/lib/dsr.js +157 -102
- package/lib/vendor/blamejs/lib/external-db.js +41 -2
- package/lib/vendor/blamejs/lib/file-upload.js +25 -7
- package/lib/vendor/blamejs/lib/flag-providers.js +27 -15
- package/lib/vendor/blamejs/lib/forms.js +113 -27
- package/lib/vendor/blamejs/lib/gate-contract.js +23 -10
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +8 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +1 -1
- package/lib/vendor/blamejs/lib/guard-archive.js +2 -2
- package/lib/vendor/blamejs/lib/guard-cidr.js +27 -1
- package/lib/vendor/blamejs/lib/guard-graphql.js +30 -11
- package/lib/vendor/blamejs/lib/guard-html.js +26 -53
- package/lib/vendor/blamejs/lib/guard-image.js +24 -5
- package/lib/vendor/blamejs/lib/guard-imap-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-jmap.js +19 -3
- package/lib/vendor/blamejs/lib/guard-jwt.js +18 -2
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +2 -2
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-markdown.js +11 -24
- package/lib/vendor/blamejs/lib/guard-pdf.js +18 -2
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +10 -2
- package/lib/vendor/blamejs/lib/guard-regex.js +8 -5
- package/lib/vendor/blamejs/lib/guard-svg.js +31 -32
- package/lib/vendor/blamejs/lib/guard-time.js +1 -1
- package/lib/vendor/blamejs/lib/guard-uuid.js +2 -2
- package/lib/vendor/blamejs/lib/html-balance.js +3 -3
- package/lib/vendor/blamejs/lib/http-client-cache.js +82 -4
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +16 -1
- package/lib/vendor/blamejs/lib/http-client.js +39 -7
- package/lib/vendor/blamejs/lib/http-message-signature.js +35 -1
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +31 -6
- package/lib/vendor/blamejs/lib/i18n.js +35 -21
- package/lib/vendor/blamejs/lib/incident-report.js +8 -1
- package/lib/vendor/blamejs/lib/ip-utils.js +11 -1
- package/lib/vendor/blamejs/lib/json-path.js +23 -1
- package/lib/vendor/blamejs/lib/jwk.js +20 -3
- package/lib/vendor/blamejs/lib/link-header.js +2 -1
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +5 -1
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +5 -1
- package/lib/vendor/blamejs/lib/log-stream.js +8 -3
- package/lib/vendor/blamejs/lib/mail-auth.js +123 -22
- package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
- package/lib/vendor/blamejs/lib/mail-bounce.js +8 -0
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +46 -10
- package/lib/vendor/blamejs/lib/mail-dav.js +22 -3
- package/lib/vendor/blamejs/lib/mail-dkim.js +96 -30
- package/lib/vendor/blamejs/lib/mail-helo.js +6 -1
- package/lib/vendor/blamejs/lib/mail-scan.js +23 -16
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +34 -8
- package/lib/vendor/blamejs/lib/mail-server-imap.js +3 -4
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +7 -11
- package/lib/vendor/blamejs/lib/mail-server-mx.js +93 -7
- package/lib/vendor/blamejs/lib/mail-server-registry.js +8 -2
- package/lib/vendor/blamejs/lib/mail.js +15 -5
- package/lib/vendor/blamejs/lib/metrics.js +268 -63
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +7 -1
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +19 -4
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +5 -9
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +40 -8
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +24 -10
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -5
- package/lib/vendor/blamejs/lib/middleware/index.js +6 -1
- package/lib/vendor/blamejs/lib/middleware/nel.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +12 -1
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +6 -2
- package/lib/vendor/blamejs/lib/mtls-ca.js +18 -2
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +11 -1
- package/lib/vendor/blamejs/lib/network-dns.js +21 -2
- package/lib/vendor/blamejs/lib/network-tls.js +69 -7
- package/lib/vendor/blamejs/lib/nonce-store.js +9 -1
- package/lib/vendor/blamejs/lib/object-store/gcs.js +7 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +31 -0
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +1 -1
- package/lib/vendor/blamejs/lib/outbox.js +1 -1
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +9 -1
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +10 -1
- package/lib/vendor/blamejs/lib/permissions.js +6 -3
- package/lib/vendor/blamejs/lib/pid-probe.js +55 -0
- package/lib/vendor/blamejs/lib/pqc-agent.js +8 -1
- package/lib/vendor/blamejs/lib/pqc-software.js +3 -3
- package/lib/vendor/blamejs/lib/pubsub.js +20 -9
- package/lib/vendor/blamejs/lib/queue-local.js +8 -1
- package/lib/vendor/blamejs/lib/queue-redis.js +5 -0
- package/lib/vendor/blamejs/lib/redact.js +54 -0
- package/lib/vendor/blamejs/lib/restore-bundle.js +10 -2
- package/lib/vendor/blamejs/lib/restore.js +19 -0
- package/lib/vendor/blamejs/lib/retention.js +12 -4
- package/lib/vendor/blamejs/lib/router.js +60 -6
- package/lib/vendor/blamejs/lib/safe-buffer.js +69 -0
- package/lib/vendor/blamejs/lib/safe-decompress.js +8 -2
- package/lib/vendor/blamejs/lib/safe-dns.js +25 -20
- package/lib/vendor/blamejs/lib/safe-ical.js +7 -3
- package/lib/vendor/blamejs/lib/safe-mime.js +12 -1
- package/lib/vendor/blamejs/lib/safe-object.js +80 -0
- package/lib/vendor/blamejs/lib/safe-path.js +52 -4
- package/lib/vendor/blamejs/lib/safe-redirect.js +10 -2
- package/lib/vendor/blamejs/lib/safe-schema.js +4 -1
- package/lib/vendor/blamejs/lib/safe-sieve.js +33 -1
- package/lib/vendor/blamejs/lib/safe-sql.js +88 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +7 -3
- package/lib/vendor/blamejs/lib/scheduler.js +8 -1
- package/lib/vendor/blamejs/lib/security-assert.js +6 -6
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +74 -27
- package/lib/vendor/blamejs/lib/self-update.js +517 -87
- package/lib/vendor/blamejs/lib/server-timing.js +2 -4
- package/lib/vendor/blamejs/lib/session.js +34 -12
- package/lib/vendor/blamejs/lib/sql.js +15 -1
- package/lib/vendor/blamejs/lib/ssrf-guard.js +52 -0
- package/lib/vendor/blamejs/lib/static.js +55 -13
- package/lib/vendor/blamejs/lib/storage.js +8 -2
- package/lib/vendor/blamejs/lib/subject.js +6 -1
- package/lib/vendor/blamejs/lib/tenant-quota.js +52 -13
- package/lib/vendor/blamejs/lib/tls-exporter.js +3 -3
- package/lib/vendor/blamejs/lib/tsa.js +34 -13
- package/lib/vendor/blamejs/lib/uri-template.js +8 -1
- package/lib/vendor/blamejs/lib/vault/index.js +13 -0
- package/lib/vendor/blamejs/lib/vc.js +44 -5
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +26 -26
- package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +792 -1
- package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +3443 -1
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +2737 -1
- package/lib/vendor/blamejs/lib/vendor/pki.cjs +39550 -13
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +55 -53
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5551 -5554
- package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +22459 -32
- package/lib/vendor/blamejs/lib/watcher.js +89 -17
- package/lib/vendor/blamejs/lib/webhook-dispatcher.js +25 -3
- package/lib/vendor/blamejs/lib/webhook.js +12 -2
- package/lib/vendor/blamejs/lib/ws-client.js +32 -1
- package/lib/vendor/blamejs/lib/xml-c14n.js +29 -1
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +1 -1
- package/lib/vendor/blamejs/package-lock.json +2 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.16.x.json +1462 -0
- package/lib/vendor/blamejs/release-notes/v0.17.0.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.1.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.10.json +30 -0
- package/lib/vendor/blamejs/release-notes/v0.17.11.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.17.12.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.13.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.17.14.json +40 -0
- package/lib/vendor/blamejs/release-notes/v0.17.15.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.16.json +27 -0
- package/lib/vendor/blamejs/release-notes/v0.17.17.json +31 -0
- package/lib/vendor/blamejs/release-notes/v0.17.18.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.19.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.2.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.20.json +43 -0
- package/lib/vendor/blamejs/release-notes/v0.17.21.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.22.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.17.3.json +43 -0
- package/lib/vendor/blamejs/release-notes/v0.17.4.json +35 -0
- package/lib/vendor/blamejs/release-notes/v0.17.5.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.6.json +26 -0
- package/lib/vendor/blamejs/release-notes/v0.17.7.json +35 -0
- package/lib/vendor/blamejs/release-notes/v0.17.8.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.17.9.json +26 -0
- package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +4 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +293 -9
- package/lib/vendor/blamejs/socket.yml +94 -0
- package/lib/vendor/blamejs/test/00-primitives.js +605 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +12 -9
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +90 -1
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +122 -1
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +30 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +29 -0
- package/lib/vendor/blamejs/test/integration/dsr-cluster-mysql.test.js +479 -0
- package/lib/vendor/blamejs/test/integration/dsr-cluster-pg.test.js +487 -0
- package/lib/vendor/blamejs/test/integration/federation-auth.test.js +313 -5
- package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +80 -0
- package/lib/vendor/blamejs/test/integration/openid-federation-chain.test.js +275 -0
- package/lib/vendor/blamejs/test/integration/webhook-dispatcher-pg.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +1649 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +33 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +60 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +891 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +128 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-adapters.test.js +271 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-read.test.js +628 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-read.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-wrap.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +15 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file.test.js +114 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-anchor.test.js +141 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-tools.test.js +624 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit.test.js +769 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +436 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth.test.js +1697 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oid4vp.test.js +495 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-password.test.js +500 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml.test.js +1809 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +243 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-index.test.js +2226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +183 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/base32.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +693 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +603 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +557 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/case-insensitive-security-token-guard.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cbor.test.js +25 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +843 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/ciba-authreqid-binding.test.js +77 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/ciba.test.js +866 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +19 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli.test.js +2790 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cluster-lease-renewal-ttl.test.js +114 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cluster.test.js +1032 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +739 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/codepoint-class.test.js +8 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +194 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-lifecycle-proto-key.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +254 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +613 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/cookies.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +407 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-adversarial.test.js +352 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +10 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +24 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +17 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-spki-pin.test.js +183 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-xwing.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto.test.js +345 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +48 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +459 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +19 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query.test.js +1019 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db.test.js +1621 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +462 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-verify.test.js +562 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +901 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +249 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +561 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db.test.js +1214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +46 -20
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +147 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +907 -38
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload.test.js +1135 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/forms.test.js +747 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gate-contract.test.js +1855 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-auth.test.js +117 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-cidr.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-domain.test.js +422 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +424 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{guard-gate-disposition-coverage.test.js → guard-gate-disposition.test.js} +1 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-graphql.test.js +340 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-image.test.js +519 -45
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jsonpath.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jwt.test.js +436 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +25 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +16 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mime.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-oauth.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pdf.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-regex.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-shell.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{guard-sql-coverage.test.js → guard-sql.test.js} +3 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-template.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-time.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-uuid.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache-authorization.test.js +347 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +908 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client.test.js +2874 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +643 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +1153 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +494 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ip-utils.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jsdoc-example-execution.test.js +177 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-path.test.js +103 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-schema.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwk.test.js +41 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +276 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream.test.js +241 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +441 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +2136 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +759 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +811 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +401 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +751 -8
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +864 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +830 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +635 -86
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +818 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +686 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +778 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +1438 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +449 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +773 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +623 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +13 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +877 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +326 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +242 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +1596 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +440 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics.test.js +1230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-assetlinks.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-body-parser.test.js +1169 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-bot-disclose.test.js +123 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-cookies.test.js +108 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-gpc.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-headers.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-host-allowlist.test.js +154 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-require-content-type.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{middleware-tus-upload-coverage.test.js → middleware-tus-upload.test.js} +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-web-app-manifest.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-crl-fingerprint-only.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +1727 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/network-nts.test.js +699 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy.test.js +1181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +1876 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nonce-store-enforce-replay-nonboolean.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ntp-check.test.js +122 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +378 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oid4vci.test.js +1016 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/openid-federation.test.js +946 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox.test.js +818 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/own-property-membership-guards.test.js +109 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-safe-toml.test.js +543 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/{parsers-safe-yaml-coverage.test.js → parsers-safe-yaml.test.js} +2 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +649 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +524 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +22 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +295 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact.test.js +1031 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-bound-key.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/restore-empty-manifest-wipe.test.js +154 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention.test.js +600 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-host-path-injection.test.js +231 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router.test.js +1277 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer-linear-scans.test.js +14 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +28 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +245 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ini.test.js +376 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-json.test.js +175 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +518 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-object.test.js +62 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-schema.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-sql.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +26 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler.test.js +157 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +396 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +930 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +755 -82
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +1053 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +200 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-strict-binding-missing-failclosed.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +636 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql-offset-without-limit.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +1190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +850 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage.test.js +621 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/subject.test.js +47 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/template.test.js +582 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +587 -25
- package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +11 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tsa.test.js +524 -63
- package/lib/vendor/blamejs/test/layer-0-primitives/uri-template.test.js +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-rotate.test.js +866 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +215 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +550 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook-dispatcher.test.js +388 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +262 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket.test.js +1294 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +629 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/xml-c14n-attr-normalization.test.js +164 -0
- package/lib/vendor/blamejs/test/smoke.js +2 -0
- package/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.16.0.json +0 -44
- package/lib/vendor/blamejs/release-notes/v0.16.1.json +0 -36
- package/lib/vendor/blamejs/release-notes/v0.16.2.json +0 -71
- package/lib/vendor/blamejs/release-notes/v0.16.3.json +0 -71
- package/lib/vendor/blamejs/test/layer-0-primitives/acme-coverage.test.js +0 -441
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth-coverage.test.js +0 -482
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml-coverage.test.js +0 -671
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-index-coverage.test.js +0 -690
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-coverage.test.js +0 -238
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials-coverage.test.js +0 -319
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain-coverage.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth-coverage.test.js +0 -437
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-coverage.test.js +0 -443
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-coverage.test.js +0 -395
- package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy-coverage.test.js +0 -565
- package/lib/vendor/blamejs/test/layer-0-primitives/router-coverage.test.js +0 -592
- package/lib/vendor/blamejs/test/layer-0-primitives/sql-coverage.test.js +0 -422
- package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +0 -573
|
@@ -36,6 +36,10 @@ function _newSigningKeys() {
|
|
|
36
36
|
return nodeCrypto.generateKeyPairSync("ed25519");
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function _newEcP384Keys() {
|
|
40
|
+
return nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-384" });
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
function _detachedSign(privateKey, bytes) {
|
|
40
44
|
return nodeCrypto.sign(null, bytes, privateKey);
|
|
41
45
|
}
|
|
@@ -46,6 +50,9 @@ function testSurface() {
|
|
|
46
50
|
check("b.selfUpdate.verify is a function", typeof b.selfUpdate.verify === "function");
|
|
47
51
|
check("b.selfUpdate.swap is a function", typeof b.selfUpdate.swap === "function");
|
|
48
52
|
check("b.selfUpdate.rollback is a function", typeof b.selfUpdate.rollback === "function");
|
|
53
|
+
check("b.selfUpdate.beginProbation is a function", typeof b.selfUpdate.beginProbation === "function");
|
|
54
|
+
check("b.selfUpdate.confirmHealthy is a function", typeof b.selfUpdate.confirmHealthy === "function");
|
|
55
|
+
check("b.selfUpdate.evaluateOnBoot is a function", typeof b.selfUpdate.evaluateOnBoot === "function");
|
|
49
56
|
check("SelfUpdateError class exposed", typeof b.selfUpdate.SelfUpdateError === "function");
|
|
50
57
|
check("DEFAULT_HASH_ALG = sha3-512", b.selfUpdate.DEFAULT_HASH_ALG === "sha3-512");
|
|
51
58
|
}
|
|
@@ -360,6 +367,1018 @@ async function testRollbackMissingBackupRefused() {
|
|
|
360
367
|
try { fs.rmdirSync(dir); } catch (_e) { /* best-effort */ }
|
|
361
368
|
}
|
|
362
369
|
|
|
370
|
+
// ---- Additional coverage: poll / verify / swap / rollback error,
|
|
371
|
+
// adversarial, and option-default branches ----
|
|
372
|
+
|
|
373
|
+
function _serveStatus(status, body, headers, onReq) {
|
|
374
|
+
return http.createServer(function (req, res) {
|
|
375
|
+
if (onReq) onReq(req);
|
|
376
|
+
res.writeHead(status, headers || {});
|
|
377
|
+
res.end(body == null ? "" : body);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function _pollLocal(port, extra) {
|
|
382
|
+
return b.selfUpdate.poll(Object.assign({
|
|
383
|
+
releasesUrl: "http://127.0.0.1:" + port + "/releases",
|
|
384
|
+
currentVersion: "v1.0.0",
|
|
385
|
+
allowedProtocols: ["http:"],
|
|
386
|
+
allowInternal: true,
|
|
387
|
+
}, extra || {}));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function testPoll304FastPath() {
|
|
391
|
+
// RED before the fix: httpClient.request rejects EVERY non-2xx (304
|
|
392
|
+
// included) as HTTP_ERROR, so poll() never reached its own
|
|
393
|
+
// statusCode===304 branch — the documented If-None-Match "fast no-update"
|
|
394
|
+
// path was dead code and a conditional poll that correctly received a 304
|
|
395
|
+
// threw selfupdate/poll-failed instead of reporting "no update". poll() now
|
|
396
|
+
// passes responseMode:"always-resolve" so it owns status handling.
|
|
397
|
+
var etag = "W/\"cafe-f00d\"";
|
|
398
|
+
var seenHeader = null;
|
|
399
|
+
var s = _serveStatus(304, "", { ETag: etag }, function (req) {
|
|
400
|
+
seenHeader = req.headers["if-none-match"];
|
|
401
|
+
});
|
|
402
|
+
var port = await b.testing.listenOnRandomPort(s);
|
|
403
|
+
try {
|
|
404
|
+
var r = await _pollLocal(port, { etag: etag });
|
|
405
|
+
check("poll 304: If-None-Match header was sent", seenHeader === etag);
|
|
406
|
+
check("poll 304: fast-path returns (no throw)", r && r.available === false);
|
|
407
|
+
check("poll 304: statusCode surfaced", r.statusCode === 304);
|
|
408
|
+
check("poll 304: etag echoed back", r.etag === etag);
|
|
409
|
+
check("poll 304: latestTag null on 304", r.latestTag === null);
|
|
410
|
+
} finally { s.close(); }
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function testPollNon2xxSurfacesTypedCode() {
|
|
414
|
+
// With poll owning status handling, a real non-2xx surfaces the intended
|
|
415
|
+
// typed selfupdate/poll-non-2xx branch rather than the generic
|
|
416
|
+
// request-failed catch (also previously dead behind httpClient's throw).
|
|
417
|
+
var s = _serveStatus(503, "busy");
|
|
418
|
+
var port = await b.testing.listenOnRandomPort(s);
|
|
419
|
+
var threw = null;
|
|
420
|
+
try { await _pollLocal(port); } catch (e) { threw = e; }
|
|
421
|
+
s.close();
|
|
422
|
+
check("poll: 503 surfaces selfupdate/poll-non-2xx",
|
|
423
|
+
threw && /selfupdate\/poll-non-2xx/.test(threw.code || ""));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function testPollEmptyArrayFeed() {
|
|
427
|
+
var s = _serveJson([]);
|
|
428
|
+
var port = await b.testing.listenOnRandomPort(s);
|
|
429
|
+
try {
|
|
430
|
+
var r = await _pollLocal(port);
|
|
431
|
+
check("poll: empty-array feed available=false", r.available === false);
|
|
432
|
+
check("poll: empty-array latestTag null", r.latestTag === null);
|
|
433
|
+
} finally { s.close(); }
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async function testPollMalformedBodies() {
|
|
437
|
+
// Malformed JSON body.
|
|
438
|
+
var s1 = _serveStatus(200, "{ not json", { "Content-Type": "application/json" });
|
|
439
|
+
var p1 = await b.testing.listenOnRandomPort(s1);
|
|
440
|
+
var t1 = null;
|
|
441
|
+
try { await _pollLocal(p1); } catch (e) { t1 = e; }
|
|
442
|
+
s1.close();
|
|
443
|
+
check("poll: malformed JSON -> selfupdate/bad-json",
|
|
444
|
+
t1 && /selfupdate\/bad-json/.test(t1.code || ""));
|
|
445
|
+
|
|
446
|
+
// Valid JSON but a bare primitive — neither object nor array.
|
|
447
|
+
var s2 = _serveStatus(200, "42", { "Content-Type": "application/json" });
|
|
448
|
+
var p2 = await b.testing.listenOnRandomPort(s2);
|
|
449
|
+
var t2 = null;
|
|
450
|
+
try { await _pollLocal(p2); } catch (e) { t2 = e; }
|
|
451
|
+
s2.close();
|
|
452
|
+
check("poll: JSON primitive -> selfupdate/bad-shape",
|
|
453
|
+
t2 && /selfupdate\/bad-shape/.test(t2.code || ""));
|
|
454
|
+
|
|
455
|
+
// Object with no tag_name.
|
|
456
|
+
var s3 = _serveJson({ assets: [] });
|
|
457
|
+
var p3 = await b.testing.listenOnRandomPort(s3);
|
|
458
|
+
var t3 = null;
|
|
459
|
+
try { await _pollLocal(p3); } catch (e) { t3 = e; }
|
|
460
|
+
s3.close();
|
|
461
|
+
check("poll: object missing tag_name -> selfupdate/bad-shape",
|
|
462
|
+
t3 && /selfupdate\/bad-shape/.test(t3.code || ""));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async function testPollAvailableNoAssetMatch() {
|
|
466
|
+
// Newer tag but nothing matches the well-known asset/signature shapes.
|
|
467
|
+
var s = _serveJson({
|
|
468
|
+
tag_name: "v2.0.0",
|
|
469
|
+
assets: [{ name: "NOTES.md", browser_download_url: "https://example.invalid/notes" }],
|
|
470
|
+
});
|
|
471
|
+
var port = await b.testing.listenOnRandomPort(s);
|
|
472
|
+
try {
|
|
473
|
+
var r = await _pollLocal(port);
|
|
474
|
+
check("poll: newer tag with no matching asset -> available=true", r.available === true);
|
|
475
|
+
check("poll: no matching asset -> asset null", r.asset === null);
|
|
476
|
+
check("poll: no matching signature -> signature null", r.signature === null);
|
|
477
|
+
} finally { s.close(); }
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function testPollDigestPassthrough() {
|
|
481
|
+
var s = _serveJson({
|
|
482
|
+
tag_name: "v2.0.0",
|
|
483
|
+
assets: [
|
|
484
|
+
{ name: "blamejs.tar.gz", browser_download_url: "https://example.invalid/a.tgz", size: 100, digest: "sha256:abc123" },
|
|
485
|
+
{ name: "blamejs.tar.gz.sig", browser_download_url: "https://example.invalid/a.sig", size: 64, digest: "sha256:sigdig" },
|
|
486
|
+
],
|
|
487
|
+
});
|
|
488
|
+
var port = await b.testing.listenOnRandomPort(s);
|
|
489
|
+
try {
|
|
490
|
+
var r = await _pollLocal(port);
|
|
491
|
+
check("poll: asset digest passed through verbatim", r.asset && r.asset.digest === "sha256:abc123");
|
|
492
|
+
check("poll: signature digest passed through verbatim", r.signature && r.signature.digest === "sha256:sigdig");
|
|
493
|
+
check("poll: asset size surfaced", r.asset && r.asset.size === 100);
|
|
494
|
+
} finally { s.close(); }
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async function testPollStringPatterns() {
|
|
498
|
+
// assetPattern / signaturePattern as substrings — matched via indexOf,
|
|
499
|
+
// never compiled, so no ReDoS surface.
|
|
500
|
+
var s = _serveJson({
|
|
501
|
+
tag_name: "v2.0.0",
|
|
502
|
+
assets: [
|
|
503
|
+
{ name: "custom-runtime.pkg", browser_download_url: "https://example.invalid/rt" },
|
|
504
|
+
{ name: "custom-runtime.pkg.sig", browser_download_url: "https://example.invalid/rt.sig" },
|
|
505
|
+
],
|
|
506
|
+
});
|
|
507
|
+
var port = await b.testing.listenOnRandomPort(s);
|
|
508
|
+
try {
|
|
509
|
+
var r = await _pollLocal(port, { assetPattern: ".pkg", signaturePattern: ".pkg.sig" });
|
|
510
|
+
check("poll: string signaturePattern selects the sig", r.signature && r.signature.name === "custom-runtime.pkg.sig");
|
|
511
|
+
check("poll: string assetPattern selects the asset", r.asset && r.asset.name === "custom-runtime.pkg");
|
|
512
|
+
} finally { s.close(); }
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function testPollSignaturePairsWithAsset() {
|
|
516
|
+
// #497 — the returned signature MUST be the detached sig OF the returned asset,
|
|
517
|
+
// not a first-match-wins sig that may belong to a different sidecar. Order an
|
|
518
|
+
// ML-DSA sidecar BEFORE the real .sig so a first-match-wins selection returns
|
|
519
|
+
// the wrong one; the derived-name pairing (asset.name + ".sig") returns the
|
|
520
|
+
// sig that actually signs the selected asset.
|
|
521
|
+
var s = _serveJson({
|
|
522
|
+
tag_name: "v2.0.0",
|
|
523
|
+
assets: [
|
|
524
|
+
{ name: "blamejs-linux.tar.gz", browser_download_url: "https://example.invalid/a.tgz", size: 4096 },
|
|
525
|
+
{ name: "blamejs-linux.tar.gz.mldsa.sig", browser_download_url: "https://example.invalid/a.mldsa.sig", size: 4700 },
|
|
526
|
+
{ name: "blamejs-linux.tar.gz.sig", browser_download_url: "https://example.invalid/a.sig", size: 96 },
|
|
527
|
+
],
|
|
528
|
+
});
|
|
529
|
+
var port = await b.testing.listenOnRandomPort(s);
|
|
530
|
+
try {
|
|
531
|
+
var r = await _pollLocal(port);
|
|
532
|
+
check("poll pairing: asset selected", r.asset && r.asset.name === "blamejs-linux.tar.gz");
|
|
533
|
+
check("poll pairing: signature is the detached sig OF the asset",
|
|
534
|
+
r.signature && r.signature.name === r.asset.name + ".sig");
|
|
535
|
+
} finally { s.close(); }
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async function testPollSignaturePairingEdgeCases() {
|
|
539
|
+
// A lone signature-shaped asset (not asset+suffix) is accepted as THE sig
|
|
540
|
+
// (single-sig release) — keeps the common one-asset-one-sig case green.
|
|
541
|
+
var s1 = _serveJson({
|
|
542
|
+
tag_name: "v2.0.0",
|
|
543
|
+
assets: [
|
|
544
|
+
{ name: "runtime.tar.gz", browser_download_url: "https://example.invalid/rt.tgz" },
|
|
545
|
+
{ name: "runtime.detached.sig", browser_download_url: "https://example.invalid/rt.sig" },
|
|
546
|
+
],
|
|
547
|
+
});
|
|
548
|
+
var p1 = await b.testing.listenOnRandomPort(s1);
|
|
549
|
+
try {
|
|
550
|
+
var r1 = await _pollLocal(p1);
|
|
551
|
+
check("poll pairing: lone signature-shaped asset accepted",
|
|
552
|
+
r1.signature && r1.signature.name === "runtime.detached.sig");
|
|
553
|
+
} finally { s1.close(); }
|
|
554
|
+
|
|
555
|
+
// Two unrelated sigs, neither derived from the asset name — ambiguous, so the
|
|
556
|
+
// signature is null (fail closed) rather than guessing one that may not sign
|
|
557
|
+
// the asset.
|
|
558
|
+
var s2 = _serveJson({
|
|
559
|
+
tag_name: "v2.0.0",
|
|
560
|
+
assets: [
|
|
561
|
+
{ name: "runtime.tar.gz", browser_download_url: "https://example.invalid/rt.tgz" },
|
|
562
|
+
{ name: "other-a.sig", browser_download_url: "https://example.invalid/a.sig" },
|
|
563
|
+
{ name: "other-b.sig", browser_download_url: "https://example.invalid/b.sig" },
|
|
564
|
+
],
|
|
565
|
+
});
|
|
566
|
+
var p2 = await b.testing.listenOnRandomPort(s2);
|
|
567
|
+
try {
|
|
568
|
+
var r2 = await _pollLocal(p2);
|
|
569
|
+
check("poll pairing: ambiguous unrelated sigs → signature null (fail closed)",
|
|
570
|
+
r2.asset && r2.asset.name === "runtime.tar.gz" && r2.signature === null);
|
|
571
|
+
} finally { s2.close(); }
|
|
572
|
+
|
|
573
|
+
// An operator signaturePattern that matches a sig NOT belonging to the asset
|
|
574
|
+
// stem must not be paired to the asset (fail closed).
|
|
575
|
+
var s3 = _serveJson({
|
|
576
|
+
tag_name: "v2.0.0",
|
|
577
|
+
assets: [
|
|
578
|
+
{ name: "app.tar.gz", browser_download_url: "https://example.invalid/app.tgz" },
|
|
579
|
+
{ name: "unrelated.sig.bin", browser_download_url: "https://example.invalid/u.sig.bin" },
|
|
580
|
+
],
|
|
581
|
+
});
|
|
582
|
+
var p3 = await b.testing.listenOnRandomPort(s3);
|
|
583
|
+
try {
|
|
584
|
+
var r3 = await _pollLocal(p3, { signaturePattern: ".sig.bin" });
|
|
585
|
+
check("poll pairing: operator sig pattern not matching asset stem → null",
|
|
586
|
+
r3.asset && r3.asset.name === "app.tar.gz" && r3.signature === null);
|
|
587
|
+
} finally { s3.close(); }
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function testPollOptValidation() {
|
|
591
|
+
// Config-time refusals — each throws before any request is issued.
|
|
592
|
+
var cases = [
|
|
593
|
+
[{ currentVersion: "1.0.0" }, "bad-releases-url", "missing releasesUrl"],
|
|
594
|
+
[{ releasesUrl: "https://x/r" }, "bad-current-version", "missing currentVersion"],
|
|
595
|
+
[{ releasesUrl: "::::not a url", currentVersion: "1.0.0" }, "bad-releases-url", "unparseable releasesUrl"],
|
|
596
|
+
[{ releasesUrl: "https://x/r", currentVersion: "1.0.0", maxBytes: -1 }, "bad-max-bytes", "negative maxBytes"],
|
|
597
|
+
[{ releasesUrl: "https://x/r", currentVersion: "1.0.0", maxBytes: Infinity }, "bad-max-bytes", "Infinity maxBytes"],
|
|
598
|
+
[{ releasesUrl: "https://x/r", currentVersion: "1.0.0", timeoutMs: 0 }, "bad-timeout", "zero timeoutMs"],
|
|
599
|
+
[{ releasesUrl: "https://x/r", currentVersion: "1.0.0", headers: [] }, "bad-headers", "array headers"],
|
|
600
|
+
[{ releasesUrl: "https://x/r", currentVersion: "1.0.0", etag: 123 }, "bad-etag", "numeric etag"],
|
|
601
|
+
[{ releasesUrl: "http://x/r", currentVersion: "1.0.0", allowedProtocols: ["http:", 1] }, "bad-allowed-protocols", "non-string protocol element"],
|
|
602
|
+
[{ releasesUrl: "https://x/r", currentVersion: "1.0.0", assetPattern: 5 }, "bad-asset-pattern", "numeric assetPattern"],
|
|
603
|
+
[{ releasesUrl: "https://x/r", currentVersion: "1.0.0", signaturePattern: 5 }, "bad-sig-pattern", "numeric signaturePattern"],
|
|
604
|
+
[{ releasesUrl: "https://x/r", currentVersion: "1.0.0", signaturePattern: /((a)+)+$/ }, "unsafe-sig-pattern", "ReDoS signaturePattern"],
|
|
605
|
+
[{ releasesUrl: "https://x/r", currentVersion: "1.0.0", bogusOpt: 1 }, "bad-opts", "unknown opt"],
|
|
606
|
+
];
|
|
607
|
+
return cases.reduce(function (chain, c) {
|
|
608
|
+
return chain.then(function () {
|
|
609
|
+
return b.selfUpdate.poll(c[0]).then(
|
|
610
|
+
function () { check("poll validation (" + c[2] + ") should throw", false); },
|
|
611
|
+
function (e) {
|
|
612
|
+
check("poll validation: " + c[2] + " -> selfupdate/" + c[1],
|
|
613
|
+
e && new RegExp("selfupdate/" + c[1]).test(e.code || ""));
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
}, Promise.resolve());
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function testVerifyErrorPaths() {
|
|
620
|
+
// Missing asset file -> read-failed.
|
|
621
|
+
var t1 = null;
|
|
622
|
+
try {
|
|
623
|
+
await b.selfUpdate.verify({
|
|
624
|
+
assetPath: _tmp("absent-asset.bin"),
|
|
625
|
+
signaturePath: _tmp("absent.sig"),
|
|
626
|
+
pubkeyPem: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA\n-----END PUBLIC KEY-----\n",
|
|
627
|
+
});
|
|
628
|
+
} catch (e) { t1 = e; }
|
|
629
|
+
check("verify: missing asset file -> selfupdate/read-failed",
|
|
630
|
+
t1 && /selfupdate\/read-failed/.test(t1.code || ""));
|
|
631
|
+
|
|
632
|
+
var keys = _newSigningKeys();
|
|
633
|
+
var pubPem = keys.publicKey.export({ type: "spki", format: "pem" });
|
|
634
|
+
var asset = Buffer.from("verify-error-path payload");
|
|
635
|
+
var sig = _detachedSign(keys.privateKey, asset);
|
|
636
|
+
var aPath = _tmp("verr-asset.bin");
|
|
637
|
+
var sPath = _tmp("verr-asset.sig");
|
|
638
|
+
fs.writeFileSync(aPath, asset);
|
|
639
|
+
fs.writeFileSync(sPath, sig);
|
|
640
|
+
try {
|
|
641
|
+
// Garbage PEM makes crypto.verify throw -> verify-failed (typed).
|
|
642
|
+
var t2 = null;
|
|
643
|
+
try {
|
|
644
|
+
await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: "not a valid pem" });
|
|
645
|
+
} catch (e) { t2 = e; }
|
|
646
|
+
check("verify: garbage pubkey -> selfupdate/verify-failed",
|
|
647
|
+
t2 && /selfupdate\/verify-failed/.test(t2.code || ""));
|
|
648
|
+
|
|
649
|
+
// Unsupported digest algorithm -> config-time bad-hash-algo.
|
|
650
|
+
var t3 = null;
|
|
651
|
+
try {
|
|
652
|
+
await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: pubPem, hashAlgo: "md5" });
|
|
653
|
+
} catch (e) { t3 = e; }
|
|
654
|
+
check("verify: unsupported hashAlgo -> selfupdate/bad-hash-algo",
|
|
655
|
+
t3 && /selfupdate\/bad-hash-algo/.test(t3.code || ""));
|
|
656
|
+
|
|
657
|
+
// Missing required pubkeyPem -> bad-pubkey.
|
|
658
|
+
var t4 = null;
|
|
659
|
+
try {
|
|
660
|
+
await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath });
|
|
661
|
+
} catch (e) { t4 = e; }
|
|
662
|
+
check("verify: missing pubkeyPem -> selfupdate/bad-pubkey",
|
|
663
|
+
t4 && /selfupdate\/bad-pubkey/.test(t4.code || ""));
|
|
664
|
+
|
|
665
|
+
// A valid non-default digest algorithm is honored end-to-end.
|
|
666
|
+
var vv = await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: pubPem, hashAlgo: "sha-256" });
|
|
667
|
+
check("verify: custom hashAlgo verified", vv.verified === true);
|
|
668
|
+
check("verify: custom hashAlgo reported back", vv.alg === "sha-256");
|
|
669
|
+
check("verify: sha-256 digest is 64 lc hex", /^[0-9a-f]{64}$/.test(vv.hash));
|
|
670
|
+
} finally {
|
|
671
|
+
try { fs.unlinkSync(aPath); } catch (_e) { /* best-effort */ }
|
|
672
|
+
try { fs.unlinkSync(sPath); } catch (_e) { /* best-effort */ }
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async function testSwapFreshInstall() {
|
|
677
|
+
// No pre-existing `to` — hadOriginal=false, so no backup is written and the
|
|
678
|
+
// verified bytes are installed from memory.
|
|
679
|
+
var dir = _tmp("dir-fresh");
|
|
680
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
681
|
+
var to = path.join(dir, "app.bin");
|
|
682
|
+
var backupTo = path.join(dir, "app.bin.bak");
|
|
683
|
+
var from = path.join(dir, "app.bin.new");
|
|
684
|
+
var bytes = Buffer.from("FRESH-INSTALL-BYTES");
|
|
685
|
+
fs.writeFileSync(from, bytes);
|
|
686
|
+
var hash = nodeCrypto.createHash("sha3-512").update(bytes).digest("hex");
|
|
687
|
+
var rs = await b.selfUpdate.swap({ from: from, to: to, backupTo: backupTo, expectedHash: hash });
|
|
688
|
+
check("swap fresh: ok=true", rs.ok === true);
|
|
689
|
+
check("swap fresh: installed bytes", fs.readFileSync(to, "utf8") === "FRESH-INSTALL-BYTES");
|
|
690
|
+
check("swap fresh: no backup written", !fs.existsSync(backupTo));
|
|
691
|
+
check("swap fresh: from consumed", !fs.existsSync(from));
|
|
692
|
+
try { fs.unlinkSync(to); } catch (_e) { /* best-effort */ }
|
|
693
|
+
try { fs.rmdirSync(dir); } catch (_e) { /* best-effort */ }
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async function testSwapCustomHashAlgoRoundTrip() {
|
|
697
|
+
var dir = _tmp("dir-algo");
|
|
698
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
699
|
+
var to = path.join(dir, "bin");
|
|
700
|
+
var from = path.join(dir, "bin.new");
|
|
701
|
+
var bytes = Buffer.from("ALGO-ROUNDTRIP");
|
|
702
|
+
fs.writeFileSync(to, Buffer.from("OLD"));
|
|
703
|
+
fs.writeFileSync(from, bytes);
|
|
704
|
+
var h256 = nodeCrypto.createHash("sha-256").update(bytes).digest("hex");
|
|
705
|
+
var rs = await b.selfUpdate.swap({ from: from, to: to, backupTo: path.join(dir, "bin.bak"),
|
|
706
|
+
expectedHash: h256, hashAlgo: "sha-256" });
|
|
707
|
+
check("swap sha-256: ok=true", rs.ok === true);
|
|
708
|
+
check("swap sha-256: installed", fs.readFileSync(to, "utf8") === "ALGO-ROUNDTRIP");
|
|
709
|
+
|
|
710
|
+
// A sha-256 expectedHash checked against swap's default sha3-512 re-hash
|
|
711
|
+
// fails closed (algo divergence must never install).
|
|
712
|
+
fs.writeFileSync(to, Buffer.from("OLD2"));
|
|
713
|
+
fs.writeFileSync(from, bytes);
|
|
714
|
+
var t2 = null;
|
|
715
|
+
try {
|
|
716
|
+
await b.selfUpdate.swap({ from: from, to: to, backupTo: path.join(dir, "bin.bak"), expectedHash: h256 });
|
|
717
|
+
} catch (e) { t2 = e; }
|
|
718
|
+
check("swap: algo-divergent expectedHash refused",
|
|
719
|
+
t2 && /selfupdate\/swap-hash-mismatch/.test(t2.code || ""));
|
|
720
|
+
check("swap: algo-divergence left original intact", fs.readFileSync(to, "utf8") === "OLD2");
|
|
721
|
+
try { fs.unlinkSync(to); } catch (_e) { /* best-effort */ }
|
|
722
|
+
try { fs.unlinkSync(from); } catch (_e) { /* best-effort */ }
|
|
723
|
+
try { fs.rmdirSync(dir); } catch (_e) { /* best-effort */ }
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
async function testSwapRollbackOptValidation() {
|
|
727
|
+
var dir = _tmp("dir-optval");
|
|
728
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
729
|
+
var from = path.join(dir, "src.bin");
|
|
730
|
+
fs.writeFileSync(from, Buffer.from("X"));
|
|
731
|
+
|
|
732
|
+
// The verify->swap integrity binding is mandatory: omitting expectedHash
|
|
733
|
+
// is refused at config-time (never an opt-in security check).
|
|
734
|
+
var t1 = null;
|
|
735
|
+
try {
|
|
736
|
+
await b.selfUpdate.swap({ from: from, to: path.join(dir, "to.bin"), backupTo: path.join(dir, "to.bak") });
|
|
737
|
+
} catch (e) { t1 = e; }
|
|
738
|
+
check("swap: missing expectedHash -> selfupdate/bad-expected-hash",
|
|
739
|
+
t1 && /selfupdate\/bad-expected-hash/.test(t1.code || ""));
|
|
740
|
+
|
|
741
|
+
var t2 = null;
|
|
742
|
+
try {
|
|
743
|
+
await b.selfUpdate.swap({ from: from, to: path.join(dir, "to.bin"),
|
|
744
|
+
backupTo: path.join(dir, "to.bak"), expectedHash: "00", hashAlgo: "md5" });
|
|
745
|
+
} catch (e) { t2 = e; }
|
|
746
|
+
check("swap: unsupported hashAlgo -> selfupdate/bad-hash-algo",
|
|
747
|
+
t2 && /selfupdate\/bad-hash-algo/.test(t2.code || ""));
|
|
748
|
+
|
|
749
|
+
var t3 = null;
|
|
750
|
+
try {
|
|
751
|
+
await b.selfUpdate.swap({ from: from, to: path.join(dir, "to.bin"),
|
|
752
|
+
backupTo: path.join(dir, "to.bak"), expectedHash: "00", bogus: 1 });
|
|
753
|
+
} catch (e) { t3 = e; }
|
|
754
|
+
check("swap: unknown opt -> selfupdate/bad-opts",
|
|
755
|
+
t3 && /selfupdate\/bad-opts/.test(t3.code || ""));
|
|
756
|
+
|
|
757
|
+
// maxBytes is a DECLARED swap opt (the body re-reads the from-bytes under it,
|
|
758
|
+
// matching what selfUpdate.verify accepted): a bad value is refused with the
|
|
759
|
+
// specific bad-max-bytes code, not the generic unknown-opt bad-opts.
|
|
760
|
+
var t3b = null;
|
|
761
|
+
try {
|
|
762
|
+
await b.selfUpdate.swap({ from: from, to: path.join(dir, "to.bin"),
|
|
763
|
+
backupTo: path.join(dir, "to.bak"), expectedHash: "00", maxBytes: -1 });
|
|
764
|
+
} catch (e) { t3b = e; }
|
|
765
|
+
check("swap: bad maxBytes -> selfupdate/bad-max-bytes (declared opt, matches verify)",
|
|
766
|
+
t3b && /selfupdate\/bad-max-bytes/.test(t3b.code || ""));
|
|
767
|
+
|
|
768
|
+
// maxBytes is NOT a rollback opt (rollback re-reads nothing), so it stays
|
|
769
|
+
// unknown there — the declaration is swap-only.
|
|
770
|
+
var t3c = null;
|
|
771
|
+
try {
|
|
772
|
+
await b.selfUpdate.rollback({ to: path.join(dir, "to.bin"),
|
|
773
|
+
backupTo: path.join(dir, "to.bak"), maxBytes: 999 });
|
|
774
|
+
} catch (e) { t3c = e; }
|
|
775
|
+
check("rollback: maxBytes is not a rollback opt -> selfupdate/bad-opts",
|
|
776
|
+
t3c && /selfupdate\/bad-opts/.test(t3c.code || ""));
|
|
777
|
+
|
|
778
|
+
var t4 = null;
|
|
779
|
+
try {
|
|
780
|
+
await b.selfUpdate.rollback({ backupTo: path.join(dir, "to.bak") });
|
|
781
|
+
} catch (e) { t4 = e; }
|
|
782
|
+
check("rollback: missing to -> selfupdate/bad-to",
|
|
783
|
+
t4 && /selfupdate\/bad-to/.test(t4.code || ""));
|
|
784
|
+
|
|
785
|
+
try { fs.unlinkSync(from); } catch (_e) { /* best-effort */ }
|
|
786
|
+
try { fs.rmdirSync(dir); } catch (_e) { /* best-effort */ }
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// ---- compareTags SemVer §11 precedence — remaining branch coverage.
|
|
790
|
+
// Each pair pins the strict-§11 ordering the poll upgrade decision relies
|
|
791
|
+
// on; a lexicographic fallback would misorder several of these and offer a
|
|
792
|
+
// downgrade / skip an upgrade. ----
|
|
793
|
+
function testCompareTagsFullPrecedence() {
|
|
794
|
+
var cmp = b.selfUpdate.compareTags;
|
|
795
|
+
// Numeric core, a-side LONGER than b-side (b's missing component is "0").
|
|
796
|
+
check("compareTags: 1.0.0 == 1.0 (a longer core, missing→0)", cmp("1.0.0", "1.0") === 0);
|
|
797
|
+
// Non-numeric core component — deterministic ASCII fallback, a > b.
|
|
798
|
+
check("compareTags: non-numeric core a>b (1.z > 1.a)", cmp("1.z", "1.a") === 1);
|
|
799
|
+
// §11.4.4 — a's pre-release list is LONGER with a common prefix → a > b.
|
|
800
|
+
check("compareTags §11.4.4: alpha.1 > alpha (longer > shorter)", cmp("1.0.0-alpha.1", "1.0.0-alpha") === 1);
|
|
801
|
+
// §11.4.1 — equal leading numeric identifier then numeric compare (9 < 10).
|
|
802
|
+
check("compareTags §11.4.1: 1.9 < 1.10 (numeric, equal prefix)", cmp("1.0.0-1.9", "1.0.0-1.10") === -1);
|
|
803
|
+
// §11.4.3 — alphanumeric identifier OUTRANKS a numeric one (a=alpha, b=1).
|
|
804
|
+
check("compareTags §11.4.3: alpha > 1 (alphanum > numeric)", cmp("1.0.0-alpha", "1.0.0-1") === 1);
|
|
805
|
+
// §11.4.2 — both alphanumeric, ASCII compare, a > b.
|
|
806
|
+
check("compareTags §11.4.2: beta > alpha (lex)", cmp("1.0.0-beta", "1.0.0-alpha") === 1);
|
|
807
|
+
// Fully-equal pre-release lists → 0 (final fall-through).
|
|
808
|
+
check("compareTags: alpha.1 == alpha.1 (equal pre-release)", cmp("1.0.0-alpha.1", "1.0.0-alpha.1") === 0);
|
|
809
|
+
// An empty pre-release identifier (malformed input) is treated as
|
|
810
|
+
// alphanumeric (non-numeric) and stays deterministic rather than throwing.
|
|
811
|
+
check("compareTags: empty pre-release segment is deterministic", cmp("1.0.0-alpha.", "1.0.0-alpha.0") === 1);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// A body larger than `bodyBytes` streamed on a 200 — used to prove poll's
|
|
815
|
+
// maxBytes cap refuses an oversized releases feed (no unbounded buffering).
|
|
816
|
+
function _serveOversized(bodyBytes) {
|
|
817
|
+
return http.createServer(function (req, res) {
|
|
818
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
819
|
+
res.end("[" + '"' + "x".repeat(bodyBytes) + '"' + "]");
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
async function testPollDowngradeRefusedWithEtag() {
|
|
824
|
+
// A feed advertising an OLDER tag than currentVersion must never report an
|
|
825
|
+
// upgrade. Also exercises the maxBytes / timeoutMs numeric-opt branches and
|
|
826
|
+
// the available=false etag pass-through from the response headers.
|
|
827
|
+
var etag = 'W/"downgrade-guard"';
|
|
828
|
+
var s = _serveStatus(200, JSON.stringify({ tag_name: "v1.0.0", assets: [] }),
|
|
829
|
+
{ "Content-Type": "application/json", ETag: etag });
|
|
830
|
+
var port = await b.testing.listenOnRandomPort(s);
|
|
831
|
+
try {
|
|
832
|
+
var r = await _pollLocal(port, { currentVersion: "v2.0.0", maxBytes: 2000000, timeoutMs: 5000 });
|
|
833
|
+
check("poll: older-than-current tag → available=false (no downgrade)", r.available === false);
|
|
834
|
+
check("poll: downgrade path reports the older latestTag", r.latestTag === "v1.0.0");
|
|
835
|
+
check("poll: available=false surfaces the response etag", r.etag === etag);
|
|
836
|
+
} finally { s.close(); }
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
async function testPollOversizedFeedRefused() {
|
|
840
|
+
// The releases JSON is capped by opts.maxBytes; an oversized response is
|
|
841
|
+
// rejected by the framework downloader and surfaces as poll-failed rather
|
|
842
|
+
// than buffering unboundedly (DoS bound on a hostile feed).
|
|
843
|
+
var s = _serveOversized(4000);
|
|
844
|
+
var port = await b.testing.listenOnRandomPort(s);
|
|
845
|
+
var threw = null;
|
|
846
|
+
try { await _pollLocal(port, { maxBytes: 512 }); } catch (e) { threw = e; }
|
|
847
|
+
s.close();
|
|
848
|
+
check("poll: oversized feed over maxBytes → selfupdate/poll-failed",
|
|
849
|
+
threw && /selfupdate\/poll-failed/.test(threw.code || ""));
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
async function testPollNonArrayAssetsAndMalformedEntries() {
|
|
853
|
+
// Newer tag but `assets` is not an array — asset/signature resolve to null
|
|
854
|
+
// without throwing (defensive normalization).
|
|
855
|
+
var s1 = _serveJson({ tag_name: "v2.0.0", assets: "not-an-array" });
|
|
856
|
+
var p1 = await b.testing.listenOnRandomPort(s1);
|
|
857
|
+
try {
|
|
858
|
+
var r1 = await _pollLocal(p1);
|
|
859
|
+
check("poll: non-array assets → available=true", r1.available === true);
|
|
860
|
+
check("poll: non-array assets → asset null", r1.asset === null);
|
|
861
|
+
check("poll: non-array assets → signature null", r1.signature === null);
|
|
862
|
+
} finally { s1.close(); }
|
|
863
|
+
|
|
864
|
+
// A null asset entry and a name-less entry are both skipped; the well-formed
|
|
865
|
+
// entry is selected.
|
|
866
|
+
var s2 = _serveJson({
|
|
867
|
+
tag_name: "v2.0.0",
|
|
868
|
+
assets: [
|
|
869
|
+
null,
|
|
870
|
+
{ browser_download_url: "https://example.invalid/no-name" }, // missing name → skipped
|
|
871
|
+
{ name: "app-2.0.0.tar.gz", browser_download_url: "https://example.invalid/app.tgz" },
|
|
872
|
+
{ name: "no-url.tar.gz" }, // missing url → skipped
|
|
873
|
+
],
|
|
874
|
+
});
|
|
875
|
+
var p2 = await b.testing.listenOnRandomPort(s2);
|
|
876
|
+
try {
|
|
877
|
+
var r2 = await _pollLocal(p2);
|
|
878
|
+
check("poll: malformed entries skipped, valid asset selected",
|
|
879
|
+
r2.asset && r2.asset.name === "app-2.0.0.tar.gz");
|
|
880
|
+
} finally { s2.close(); }
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
async function testPollRegexpPatterns() {
|
|
884
|
+
// RegExp asset / signature patterns reach _matchAsset's RegExp branch
|
|
885
|
+
// (the string form is covered separately via indexOf). Both are ReDoS-safe
|
|
886
|
+
// anchored shapes so they pass the config-time guardRegex screen.
|
|
887
|
+
var s = _serveJson({
|
|
888
|
+
tag_name: "v2.0.0",
|
|
889
|
+
assets: [
|
|
890
|
+
{ name: "blamejs-runtime.pkg", browser_download_url: "https://example.invalid/rt" },
|
|
891
|
+
{ name: "blamejs-runtime.pkg.sig", browser_download_url: "https://example.invalid/rt.sig" },
|
|
892
|
+
],
|
|
893
|
+
});
|
|
894
|
+
var port = await b.testing.listenOnRandomPort(s);
|
|
895
|
+
try {
|
|
896
|
+
var r = await _pollLocal(port, { assetPattern: /-runtime\.pkg$/, signaturePattern: /\.pkg\.sig$/ });
|
|
897
|
+
check("poll: RegExp signaturePattern selects the sig", r.signature && r.signature.name === "blamejs-runtime.pkg.sig");
|
|
898
|
+
check("poll: RegExp assetPattern selects the asset", r.asset && r.asset.name === "blamejs-runtime.pkg");
|
|
899
|
+
} finally { s.close(); }
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
async function testVerifyWrongKeyAndMalformedSig() {
|
|
903
|
+
// A valid signature produced by a DIFFERENT keypair must NOT verify against
|
|
904
|
+
// the operator's pinned key — this is the wrong-key fail-closed guarantee.
|
|
905
|
+
var kSign = _newSigningKeys();
|
|
906
|
+
var kOther = _newSigningKeys();
|
|
907
|
+
var asset = Buffer.from("wrong-key adversarial payload");
|
|
908
|
+
var goodSig = _detachedSign(kSign.privateKey, asset);
|
|
909
|
+
var otherPub = kOther.publicKey.export({ type: "spki", format: "pem" });
|
|
910
|
+
|
|
911
|
+
var aPath = _tmp("wk-asset.bin");
|
|
912
|
+
var sPath = _tmp("wk-asset.sig");
|
|
913
|
+
fs.writeFileSync(aPath, asset);
|
|
914
|
+
fs.writeFileSync(sPath, goodSig);
|
|
915
|
+
try {
|
|
916
|
+
var t1 = null;
|
|
917
|
+
try {
|
|
918
|
+
await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: otherPub });
|
|
919
|
+
} catch (e) { t1 = e; }
|
|
920
|
+
check("verify: valid sig under WRONG key is refused",
|
|
921
|
+
t1 && /selfupdate\/signature-mismatch/.test(t1.code || ""));
|
|
922
|
+
|
|
923
|
+
// A truncated signature must be refused (never silently accepted).
|
|
924
|
+
fs.writeFileSync(sPath, goodSig.slice(0, 32));
|
|
925
|
+
var signerPub = kSign.publicKey.export({ type: "spki", format: "pem" });
|
|
926
|
+
var t2 = null;
|
|
927
|
+
try {
|
|
928
|
+
await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: signerPub });
|
|
929
|
+
} catch (e) { t2 = e; }
|
|
930
|
+
check("verify: truncated signature is refused",
|
|
931
|
+
t2 && /selfupdate\/(signature-mismatch|verify-failed)/.test(t2.code || ""));
|
|
932
|
+
|
|
933
|
+
// An empty signature file must be refused.
|
|
934
|
+
fs.writeFileSync(sPath, Buffer.alloc(0));
|
|
935
|
+
var t3 = null;
|
|
936
|
+
try {
|
|
937
|
+
await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: signerPub });
|
|
938
|
+
} catch (e) { t3 = e; }
|
|
939
|
+
check("verify: empty signature is refused",
|
|
940
|
+
t3 && /selfupdate\/(signature-mismatch|verify-failed)/.test(t3.code || ""));
|
|
941
|
+
} finally {
|
|
942
|
+
try { fs.unlinkSync(aPath); } catch (_e) { /* best-effort */ }
|
|
943
|
+
try { fs.unlinkSync(sPath); } catch (_e) { /* best-effort */ }
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
async function testVerifyEcdsaP384DigestCommitment() {
|
|
948
|
+
// #496 — verify() commits to a SHA3-512 signing digest (matching the framework
|
|
949
|
+
// signer + standaloneVerifier). A SHA3-512-digest DER P-384 sidecar verifies;
|
|
950
|
+
// a signature over a DIFFERENT digest (the curve-default SHA-384 that
|
|
951
|
+
// nodeCrypto.sign(null, …) / b.crypto.sign produce for an EC key) is refused —
|
|
952
|
+
// the digest, not just the key, is part of the accept contract. Pre-fix, verify
|
|
953
|
+
// routed through b.crypto.verify (SHA-384 + DER-only), which INVERTED this:
|
|
954
|
+
// it accepted the SHA-384 sig and rejected the framework's SHA3-512 sidecar.
|
|
955
|
+
var keys = _newEcP384Keys();
|
|
956
|
+
var pubPem = keys.publicKey.export({ type: "spki", format: "pem" });
|
|
957
|
+
var privPem = keys.privateKey.export({ type: "pkcs8", format: "pem" });
|
|
958
|
+
var asset = Buffer.from("ecdsa-p384 asset bytes");
|
|
959
|
+
// SHA3-512 digest-then-sign, DER (default) encoding — the framework's shape.
|
|
960
|
+
var derSig = nodeCrypto.createSign("sha3-512").update(asset).sign(keys.privateKey);
|
|
961
|
+
|
|
962
|
+
var aPath = _tmp("ec-asset.bin");
|
|
963
|
+
var sPath = _tmp("ec-asset.sig");
|
|
964
|
+
fs.writeFileSync(aPath, asset);
|
|
965
|
+
fs.writeFileSync(sPath, derSig);
|
|
966
|
+
try {
|
|
967
|
+
var v = await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: pubPem });
|
|
968
|
+
check("verify: SHA3-512 + DER P-384 detached signature verifies", v.verified === true);
|
|
969
|
+
|
|
970
|
+
// A wrong-digest signature (curve-default SHA-384) must be refused.
|
|
971
|
+
var sha384Sig = b.crypto.sign(asset, privPem);
|
|
972
|
+
fs.writeFileSync(sPath, sha384Sig);
|
|
973
|
+
var t1 = null;
|
|
974
|
+
try {
|
|
975
|
+
await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: pubPem });
|
|
976
|
+
} catch (e) { t1 = e; }
|
|
977
|
+
check("verify: wrong-digest (SHA-384) ECDSA signature is refused",
|
|
978
|
+
t1 && /selfupdate\/(signature-mismatch|verify-failed)/.test(t1.code || ""));
|
|
979
|
+
} finally {
|
|
980
|
+
try { fs.unlinkSync(aPath); } catch (_e) { /* best-effort */ }
|
|
981
|
+
try { fs.unlinkSync(sPath); } catch (_e) { /* best-effort */ }
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
async function testVerifyEcdsaP384Sha3P1363Accepted() {
|
|
986
|
+
// #496 — b.selfUpdate.verify must accept exactly the signature set the
|
|
987
|
+
// framework's own standaloneVerifier accepts: a SHA3-512-digest-then-sign
|
|
988
|
+
// detached signature with a raw IEEE-P1363 (r||s) encoding over an EC P-384
|
|
989
|
+
// key. Before the fix, verify() routed through b.crypto.verify →
|
|
990
|
+
// nodeCrypto.verify(null, ...), which uses the curve-default SHA-384 digest
|
|
991
|
+
// and DER-only, so a SHA3-512 + raw-P1363 release sidecar verified ONLY under
|
|
992
|
+
// standaloneVerifier and was rejected by verify() — a disjoint accept set.
|
|
993
|
+
var keys = _newEcP384Keys();
|
|
994
|
+
var pubPem = keys.publicKey.export({ type: "spki", format: "pem" });
|
|
995
|
+
var asset = Buffer.from("ecdsa-p384 sha3-512 ieee-p1363 release sidecar");
|
|
996
|
+
// SHA3-512 digest-then-sign, raw r||s (mirrors ...ecdsa-encoding.test.js:189).
|
|
997
|
+
var sig = nodeCrypto.createSign("sha3-512").update(asset)
|
|
998
|
+
.sign({ key: keys.privateKey, dsaEncoding: "ieee-p1363" });
|
|
999
|
+
|
|
1000
|
+
var aPath = _tmp("p1363-asset.bin");
|
|
1001
|
+
var sPath = _tmp("p1363-asset.sig");
|
|
1002
|
+
fs.writeFileSync(aPath, asset);
|
|
1003
|
+
fs.writeFileSync(sPath, sig);
|
|
1004
|
+
try {
|
|
1005
|
+
var v = await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: pubPem });
|
|
1006
|
+
check("verify: SHA3-512 + IEEE-P1363 P-384 sidecar verifies", v.verified === true);
|
|
1007
|
+
check("verify: reports the default sha3-512 digest alg", v.alg === "sha3-512");
|
|
1008
|
+
check("verify: reports the asset byte count", v.bytes === asset.length);
|
|
1009
|
+
|
|
1010
|
+
// Equivalence: the exact triple the standaloneVerifier accepts, verify() accepts.
|
|
1011
|
+
var sv = b.selfUpdate.standaloneVerifier.verify(aPath, sPath, pubPem);
|
|
1012
|
+
check("verify/standaloneVerifier accept the same triple",
|
|
1013
|
+
sv.ok === true && v.verified === true && v.hash === sv.sha3_512);
|
|
1014
|
+
} finally {
|
|
1015
|
+
try { fs.unlinkSync(aPath); } catch (_e) { /* best-effort */ }
|
|
1016
|
+
try { fs.unlinkSync(sPath); } catch (_e) { /* best-effort */ }
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
async function testVerifyMaxBytesBound() {
|
|
1021
|
+
// verify's maxBytes caps the asset read (v0.16.18 download-bounds class): an
|
|
1022
|
+
// asset larger than the cap is refused BEFORE it is buffered/hashed, and the
|
|
1023
|
+
// cap is tight (size == maxBytes passes, size == maxBytes+1 refuses).
|
|
1024
|
+
var keys = _newSigningKeys();
|
|
1025
|
+
var pubPem = keys.publicKey.export({ type: "spki", format: "pem" });
|
|
1026
|
+
var asset = Buffer.alloc(2000, 7);
|
|
1027
|
+
var sig = _detachedSign(keys.privateKey, asset);
|
|
1028
|
+
var aPath = _tmp("cap-asset.bin");
|
|
1029
|
+
var sPath = _tmp("cap-asset.sig");
|
|
1030
|
+
fs.writeFileSync(aPath, asset);
|
|
1031
|
+
fs.writeFileSync(sPath, sig);
|
|
1032
|
+
try {
|
|
1033
|
+
var t1 = null;
|
|
1034
|
+
try {
|
|
1035
|
+
await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: pubPem, maxBytes: 1000 });
|
|
1036
|
+
} catch (e) { t1 = e; }
|
|
1037
|
+
check("verify: asset over maxBytes → selfupdate/read-failed",
|
|
1038
|
+
t1 && /selfupdate\/read-failed/.test(t1.code || ""));
|
|
1039
|
+
|
|
1040
|
+
var t2 = null;
|
|
1041
|
+
try {
|
|
1042
|
+
await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: pubPem, maxBytes: 1999 });
|
|
1043
|
+
} catch (e) { t2 = e; }
|
|
1044
|
+
check("verify: asset over maxBytes by one byte → read-failed (tight bound)",
|
|
1045
|
+
t2 && /selfupdate\/read-failed/.test(t2.code || ""));
|
|
1046
|
+
|
|
1047
|
+
var v = await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: pubPem, maxBytes: 2000 });
|
|
1048
|
+
check("verify: asset exactly at maxBytes verifies", v.verified === true);
|
|
1049
|
+
|
|
1050
|
+
// The detached signature read is independently capped (64 KiB); an
|
|
1051
|
+
// oversized signature file is refused at read.
|
|
1052
|
+
fs.writeFileSync(sPath, Buffer.alloc(70000, 1));
|
|
1053
|
+
var t3 = null;
|
|
1054
|
+
try {
|
|
1055
|
+
await b.selfUpdate.verify({ assetPath: aPath, signaturePath: sPath, pubkeyPem: pubPem, maxBytes: 2000 });
|
|
1056
|
+
} catch (e) { t3 = e; }
|
|
1057
|
+
check("verify: oversized signature file → read-failed",
|
|
1058
|
+
t3 && /selfupdate\/read-failed/.test(t3.code || ""));
|
|
1059
|
+
} finally {
|
|
1060
|
+
try { fs.unlinkSync(aPath); } catch (_e) { /* best-effort */ }
|
|
1061
|
+
try { fs.unlinkSync(sPath); } catch (_e) { /* best-effort */ }
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
async function testSwapMaxBytesBound() {
|
|
1066
|
+
// swap re-reads `from` under maxBytes to re-hash it; a from larger than the
|
|
1067
|
+
// cap is refused (swap-read-failed) and NOTHING is installed. A valid cap
|
|
1068
|
+
// installs normally.
|
|
1069
|
+
var dir = _tmp("dir-swap-cap");
|
|
1070
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1071
|
+
var to = path.join(dir, "bin");
|
|
1072
|
+
var from = path.join(dir, "bin.new");
|
|
1073
|
+
var bytes = Buffer.alloc(3000, 9);
|
|
1074
|
+
var hash = nodeCrypto.createHash("sha3-512").update(bytes).digest("hex");
|
|
1075
|
+
fs.writeFileSync(from, bytes);
|
|
1076
|
+
|
|
1077
|
+
var t1 = null;
|
|
1078
|
+
try {
|
|
1079
|
+
await b.selfUpdate.swap({ from: from, to: to, backupTo: path.join(dir, "bin.bak"),
|
|
1080
|
+
expectedHash: hash, maxBytes: 1000 });
|
|
1081
|
+
} catch (e) { t1 = e; }
|
|
1082
|
+
check("swap: from over maxBytes → selfupdate/swap-read-failed",
|
|
1083
|
+
t1 && /selfupdate\/swap-read-failed/.test(t1.code || ""));
|
|
1084
|
+
check("swap: over-cap from was NOT installed", !fs.existsSync(to));
|
|
1085
|
+
|
|
1086
|
+
if (!fs.existsSync(from)) fs.writeFileSync(from, bytes);
|
|
1087
|
+
var rs = await b.selfUpdate.swap({ from: from, to: to, backupTo: path.join(dir, "bin.bak"),
|
|
1088
|
+
expectedHash: hash, maxBytes: 3000 });
|
|
1089
|
+
check("swap: valid maxBytes installs", rs.ok === true && fs.readFileSync(to).length === 3000);
|
|
1090
|
+
|
|
1091
|
+
try { fs.unlinkSync(to); } catch (_e) { /* best-effort */ }
|
|
1092
|
+
try { fs.rmdirSync(dir); } catch (_e) { /* best-effort */ }
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
async function testSwapBackupFailureLeavesOriginal() {
|
|
1096
|
+
// The backup step must fail closed: when the backup destination cannot be
|
|
1097
|
+
// written (here backupTo is an existing directory), swap refuses with
|
|
1098
|
+
// backup-failed and the original `to` is left untouched.
|
|
1099
|
+
var dir = _tmp("dir-backupfail");
|
|
1100
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1101
|
+
var to = path.join(dir, "app.bin");
|
|
1102
|
+
var from = path.join(dir, "app.bin.new");
|
|
1103
|
+
var bytes = Buffer.from("NEW-VERIFIED");
|
|
1104
|
+
fs.writeFileSync(to, Buffer.from("ORIGINAL-BINARY"));
|
|
1105
|
+
fs.writeFileSync(from, bytes);
|
|
1106
|
+
var hash = nodeCrypto.createHash("sha3-512").update(bytes).digest("hex");
|
|
1107
|
+
var backupDir = path.join(dir, "backup-is-a-directory");
|
|
1108
|
+
fs.mkdirSync(backupDir);
|
|
1109
|
+
|
|
1110
|
+
var threw = null;
|
|
1111
|
+
try {
|
|
1112
|
+
await b.selfUpdate.swap({ from: from, to: to, backupTo: backupDir, expectedHash: hash });
|
|
1113
|
+
} catch (e) { threw = e; }
|
|
1114
|
+
check("swap: unwritable backup dest → selfupdate/backup-failed",
|
|
1115
|
+
threw && /selfupdate\/backup-failed/.test(threw.code || ""));
|
|
1116
|
+
check("swap: backup failure leaves original intact", fs.readFileSync(to, "utf8") === "ORIGINAL-BINARY");
|
|
1117
|
+
|
|
1118
|
+
try { fs.unlinkSync(to); } catch (_e) { /* best-effort */ }
|
|
1119
|
+
try { fs.unlinkSync(from); } catch (_e) { /* best-effort */ }
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
async function testSwapSeparateBackupDir() {
|
|
1123
|
+
// backupTo in a DIFFERENT directory than `to` exercises the second
|
|
1124
|
+
// directory-fsync branch (both parents are synced for durability).
|
|
1125
|
+
var toDir = _tmp("dir-to");
|
|
1126
|
+
var bkDir = _tmp("dir-backup");
|
|
1127
|
+
fs.mkdirSync(toDir, { recursive: true });
|
|
1128
|
+
fs.mkdirSync(bkDir, { recursive: true });
|
|
1129
|
+
var to = path.join(toDir, "app.bin");
|
|
1130
|
+
var backupTo = path.join(bkDir, "app.bak");
|
|
1131
|
+
var from = path.join(toDir, "app.bin.new");
|
|
1132
|
+
var bytes = Buffer.from("SEPARATE-DIR-BYTES");
|
|
1133
|
+
fs.writeFileSync(to, Buffer.from("OLD"));
|
|
1134
|
+
fs.writeFileSync(from, bytes);
|
|
1135
|
+
var hash = nodeCrypto.createHash("sha3-512").update(bytes).digest("hex");
|
|
1136
|
+
|
|
1137
|
+
var rs = await b.selfUpdate.swap({ from: from, to: to, backupTo: backupTo, expectedHash: hash });
|
|
1138
|
+
check("swap (separate backup dir): ok=true", rs.ok === true);
|
|
1139
|
+
check("swap (separate backup dir): installed bytes", fs.readFileSync(to, "utf8") === "SEPARATE-DIR-BYTES");
|
|
1140
|
+
check("swap (separate backup dir): backup in other dir", fs.readFileSync(backupTo, "utf8") === "OLD");
|
|
1141
|
+
|
|
1142
|
+
try { fs.unlinkSync(to); } catch (_e) { /* best-effort */ }
|
|
1143
|
+
try { fs.unlinkSync(backupTo); } catch (_e) { /* best-effort */ }
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
async function testSwapWriteFailureNoOriginal() {
|
|
1147
|
+
// A fresh install (no pre-existing `to`) whose install write cannot complete
|
|
1148
|
+
// must surface swap-failed — with no backup to roll back from, the source is
|
|
1149
|
+
// left for the operator. An unwritable target path (embedded NUL) forces the
|
|
1150
|
+
// atomic write to fail deterministically on every platform.
|
|
1151
|
+
var dir = _tmp("dir-writefail");
|
|
1152
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1153
|
+
var from = path.join(dir, "src.bin");
|
|
1154
|
+
var bytes = Buffer.from("FRESH-BYTES");
|
|
1155
|
+
fs.writeFileSync(from, bytes);
|
|
1156
|
+
var hash = nodeCrypto.createHash("sha3-512").update(bytes).digest("hex");
|
|
1157
|
+
var badTo = path.join(dir, "bad" + String.fromCharCode(0) + "name.bin"); // embedded NUL — never openable
|
|
1158
|
+
|
|
1159
|
+
var threw = null;
|
|
1160
|
+
try {
|
|
1161
|
+
await b.selfUpdate.swap({ from: from, to: badTo, backupTo: path.join(dir, "bad.bak"), expectedHash: hash });
|
|
1162
|
+
} catch (e) { threw = e; }
|
|
1163
|
+
check("swap: install write failure (no original) → selfupdate/swap-failed",
|
|
1164
|
+
threw && /selfupdate\/swap-failed/.test(threw.code || ""));
|
|
1165
|
+
|
|
1166
|
+
try { fs.unlinkSync(from); } catch (_e) { /* best-effort */ }
|
|
1167
|
+
try { fs.rmdirSync(dir); } catch (_e) { /* best-effort */ }
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
async function testSwapReplacesLockedTargetWin32() {
|
|
1171
|
+
// #494 — installing over a running Windows image must succeed. Windows locks a
|
|
1172
|
+
// mapped / running executable against an in-place REPLACE (write-temp-then-
|
|
1173
|
+
// rename ONTO it) but allows a RENAME / move of the file. swap now moves the
|
|
1174
|
+
// outgoing `to` ASIDE to backupTo (a rename = lock-safe AND the backup) and
|
|
1175
|
+
// writes the new bytes to the freed path (a create, not a replace). The
|
|
1176
|
+
// read-only attribute (chmodSync 0o400) reproduces the replace-onto-locked
|
|
1177
|
+
// failure the old rename-onto-`to` path hit; rename-away sidesteps it.
|
|
1178
|
+
// Windows-only: POSIX rename ignores the target file mode, so the replace
|
|
1179
|
+
// failure can't be forced there without privileged setup.
|
|
1180
|
+
if (process.platform !== "win32") {
|
|
1181
|
+
check("swap: locked-target replace test skipped (non-win32)", true);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
var dir = _tmp("dir-locked");
|
|
1185
|
+
var bkDir = _tmp("dir-locked-bak");
|
|
1186
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1187
|
+
fs.mkdirSync(bkDir, { recursive: true });
|
|
1188
|
+
var to = path.join(dir, "app.bin");
|
|
1189
|
+
var backupTo = path.join(bkDir, "app.bak");
|
|
1190
|
+
var from = path.join(dir, "app.bin.new");
|
|
1191
|
+
var bytes = Buffer.from("NEW-VERIFIED-BYTES");
|
|
1192
|
+
fs.writeFileSync(from, bytes);
|
|
1193
|
+
fs.writeFileSync(to, Buffer.from("ORIGINAL-BYTES"));
|
|
1194
|
+
fs.chmodSync(to, 0o400); // read-only → in-place replace onto `to` fails; rename-away does not
|
|
1195
|
+
var hash = nodeCrypto.createHash("sha3-512").update(bytes).digest("hex");
|
|
1196
|
+
|
|
1197
|
+
var rs = await b.selfUpdate.swap({ from: from, to: to, backupTo: backupTo, expectedHash: hash });
|
|
1198
|
+
check("swap: locked target still installs (rename-away)", rs && rs.ok === true);
|
|
1199
|
+
check("swap: new bytes installed at target", fs.readFileSync(to, "utf8") === "NEW-VERIFIED-BYTES");
|
|
1200
|
+
check("swap: old bytes preserved in backup", fs.readFileSync(backupTo, "utf8") === "ORIGINAL-BYTES");
|
|
1201
|
+
check("swap: from consumed", !fs.existsSync(from));
|
|
1202
|
+
|
|
1203
|
+
try { fs.chmodSync(to, 0o600); fs.unlinkSync(to); } catch (_e) { /* best-effort */ }
|
|
1204
|
+
try { fs.chmodSync(backupTo, 0o600); fs.unlinkSync(backupTo); } catch (_e) { /* best-effort */ }
|
|
1205
|
+
try { fs.unlinkSync(from); } catch (_e) { /* best-effort */ }
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
async function testRollbackCopyFailure() {
|
|
1209
|
+
// rollback fails closed when the restore copy cannot be written — here `to`
|
|
1210
|
+
// is an existing directory, so the atomic write of the restored bytes fails
|
|
1211
|
+
// and rollback surfaces selfupdate/rollback-failed.
|
|
1212
|
+
var dir = _tmp("dir-rbcopyfail");
|
|
1213
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1214
|
+
var backupTo = path.join(dir, "app.bak");
|
|
1215
|
+
fs.writeFileSync(backupTo, Buffer.from("BACKUP-BYTES"));
|
|
1216
|
+
var toDir = path.join(dir, "to-is-a-directory");
|
|
1217
|
+
fs.mkdirSync(toDir);
|
|
1218
|
+
|
|
1219
|
+
var threw = null;
|
|
1220
|
+
try {
|
|
1221
|
+
await b.selfUpdate.rollback({ to: toDir, backupTo: backupTo });
|
|
1222
|
+
} catch (e) { threw = e; }
|
|
1223
|
+
check("rollback: unwritable restore target → selfupdate/rollback-failed",
|
|
1224
|
+
threw && /selfupdate\/rollback-failed/.test(threw.code || ""));
|
|
1225
|
+
|
|
1226
|
+
try { fs.unlinkSync(backupTo); } catch (_e) { /* best-effort */ }
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// ---- #495 — probation / rollback orchestration. Real on-disk markers under
|
|
1230
|
+
// os.tmpdir(); the wall clock is injected via evaluateOnBoot({ now }) so the
|
|
1231
|
+
// window edges are exercised deterministically (no sleeping). ----
|
|
1232
|
+
|
|
1233
|
+
function _sha3(bytes) { return nodeCrypto.createHash("sha3-512").update(bytes).digest("hex"); }
|
|
1234
|
+
|
|
1235
|
+
async function testProbationExpiredRollsBack() {
|
|
1236
|
+
// Edge 1 — the probation window elapses with no confirmHealthy (the new binary
|
|
1237
|
+
// ran but never reported healthy) → rollback restores the known-good backup.
|
|
1238
|
+
var dir = _tmp("dir-prob-exp");
|
|
1239
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1240
|
+
var to = path.join(dir, "app.bin");
|
|
1241
|
+
var backupTo = path.join(dir, "app.bin.bak");
|
|
1242
|
+
var newBytes = Buffer.from("NEW-PROBATIONARY-BINARY");
|
|
1243
|
+
fs.writeFileSync(to, newBytes); // probationary binary installed at `to`
|
|
1244
|
+
fs.writeFileSync(backupTo, Buffer.from("OLD-KNOWN-GOOD"));
|
|
1245
|
+
var begun = await b.selfUpdate.beginProbation({
|
|
1246
|
+
to: to, backupTo: backupTo, expectedHash: _sha3(newBytes), windowMs: 60000,
|
|
1247
|
+
});
|
|
1248
|
+
check("beginProbation: returns a markerPath", typeof begun.markerPath === "string");
|
|
1249
|
+
check("beginProbation: marker written to disk", fs.existsSync(begun.markerPath));
|
|
1250
|
+
|
|
1251
|
+
var r = await b.selfUpdate.evaluateOnBoot({ to: to, now: begun.expiresAt + 1 });
|
|
1252
|
+
check("evaluateOnBoot: expired + unconfirmed → rollback", r.action === "rollback");
|
|
1253
|
+
check("evaluateOnBoot: restored the known-good backup", fs.readFileSync(to, "utf8") === "OLD-KNOWN-GOOD");
|
|
1254
|
+
check("evaluateOnBoot: marker cleared after rollback", !fs.existsSync(begun.markerPath));
|
|
1255
|
+
|
|
1256
|
+
try { fs.unlinkSync(to); fs.unlinkSync(backupTo); } catch (_e) { /* best-effort */ }
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
async function testProbationCleanStopWithinWindowKeeps() {
|
|
1260
|
+
// Edge 2 — a clean stop / restart INSIDE the window must not be misread as a
|
|
1261
|
+
// crash: keep the new binary and leave the marker for the window to run out.
|
|
1262
|
+
var dir = _tmp("dir-prob-win");
|
|
1263
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1264
|
+
var to = path.join(dir, "app.bin");
|
|
1265
|
+
var backupTo = path.join(dir, "app.bin.bak");
|
|
1266
|
+
var newBytes = Buffer.from("NEW-BINARY-IN-PROBATION");
|
|
1267
|
+
fs.writeFileSync(to, newBytes);
|
|
1268
|
+
fs.writeFileSync(backupTo, Buffer.from("OLD-BINARY"));
|
|
1269
|
+
var begun = await b.selfUpdate.beginProbation({
|
|
1270
|
+
to: to, backupTo: backupTo, expectedHash: _sha3(newBytes), windowMs: 60000,
|
|
1271
|
+
});
|
|
1272
|
+
var r = await b.selfUpdate.evaluateOnBoot({ to: to, now: begun.installedAt + 1 });
|
|
1273
|
+
check("evaluateOnBoot: within-window → keep", r.action === "keep");
|
|
1274
|
+
check("evaluateOnBoot: within-window keeps the new binary", fs.readFileSync(to, "utf8") === "NEW-BINARY-IN-PROBATION");
|
|
1275
|
+
check("evaluateOnBoot: within-window keeps the marker", fs.existsSync(begun.markerPath));
|
|
1276
|
+
|
|
1277
|
+
try { fs.unlinkSync(begun.markerPath); } catch (_e) { /* best-effort */ }
|
|
1278
|
+
try { fs.unlinkSync(to); fs.unlinkSync(backupTo); } catch (_e) { /* best-effort */ }
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
async function testProbationConfirmAndFailedSwapNoPhantom() {
|
|
1282
|
+
// Edge 3a — confirmHealthy clears the marker (a clean/healthy signal): a later
|
|
1283
|
+
// boot finds no marker and keeps, never rolling back a healthy binary.
|
|
1284
|
+
var dir = _tmp("dir-prob-confirm");
|
|
1285
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1286
|
+
var to = path.join(dir, "app.bin");
|
|
1287
|
+
var backupTo = path.join(dir, "app.bin.bak");
|
|
1288
|
+
var newBytes = Buffer.from("NEW-CONFIRMED-HEALTHY");
|
|
1289
|
+
fs.writeFileSync(to, newBytes);
|
|
1290
|
+
fs.writeFileSync(backupTo, Buffer.from("OLD-BINARY"));
|
|
1291
|
+
var begun = await b.selfUpdate.beginProbation({
|
|
1292
|
+
to: to, backupTo: backupTo, expectedHash: _sha3(newBytes), windowMs: 60000,
|
|
1293
|
+
});
|
|
1294
|
+
var conf = await b.selfUpdate.confirmHealthy({ to: to });
|
|
1295
|
+
check("confirmHealthy: clears the marker", conf.cleared === true && !fs.existsSync(begun.markerPath));
|
|
1296
|
+
var r = await b.selfUpdate.evaluateOnBoot({ to: to, now: begun.expiresAt + 1 });
|
|
1297
|
+
check("evaluateOnBoot: confirmed → keep (no marker)", r.action === "keep");
|
|
1298
|
+
check("evaluateOnBoot: confirmed keeps the new binary", fs.readFileSync(to, "utf8") === "NEW-CONFIRMED-HEALTHY");
|
|
1299
|
+
|
|
1300
|
+
// Edge 3b — a marker that SURVIVED a FAILED swap (recorded expectedHash of a
|
|
1301
|
+
// new binary that was never installed; `to` still holds the OLD binary) must
|
|
1302
|
+
// NOT phantom-rollback: the probationary binary isn't the one installed.
|
|
1303
|
+
var dir2 = _tmp("dir-prob-failswap");
|
|
1304
|
+
fs.mkdirSync(dir2, { recursive: true });
|
|
1305
|
+
var to2 = path.join(dir2, "app.bin");
|
|
1306
|
+
var backupTo2 = path.join(dir2, "app.bin.bak");
|
|
1307
|
+
fs.writeFileSync(to2, Buffer.from("OLD-BINARY-STILL-HERE")); // swap failed — new never installed
|
|
1308
|
+
fs.writeFileSync(backupTo2, Buffer.from("OLD-BINARY-STILL-HERE"));
|
|
1309
|
+
var neverInstalled = _sha3(Buffer.from("NEW-BINARY-THAT-FAILED-TO-INSTALL"));
|
|
1310
|
+
var begun2 = await b.selfUpdate.beginProbation({
|
|
1311
|
+
to: to2, backupTo: backupTo2, expectedHash: neverInstalled, windowMs: 60000,
|
|
1312
|
+
});
|
|
1313
|
+
var r2 = await b.selfUpdate.evaluateOnBoot({ to: to2, now: begun2.expiresAt + 1 });
|
|
1314
|
+
check("evaluateOnBoot: failed-swap marker → no phantom rollback", r2.action === "keep");
|
|
1315
|
+
check("evaluateOnBoot: failed-swap `to` untouched", fs.readFileSync(to2, "utf8") === "OLD-BINARY-STILL-HERE");
|
|
1316
|
+
|
|
1317
|
+
try { fs.unlinkSync(to); fs.unlinkSync(backupTo); } catch (_e) { /* best-effort */ }
|
|
1318
|
+
try { fs.unlinkSync(to2); fs.unlinkSync(backupTo2); fs.unlinkSync(begun2.markerPath); } catch (_e) { /* best-effort */ }
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
async function testProbationMarkerRecoverableAfterSpawnFailure() {
|
|
1322
|
+
// Edge 4 — the successor (new binary) fails to spawn / confirm. The marker
|
|
1323
|
+
// beginProbation wrote must be a complete, re-readable record the NEXT boot's
|
|
1324
|
+
// evaluateOnBoot recovers from (rollback), never a half-written / lost marker.
|
|
1325
|
+
var dir = _tmp("dir-prob-spawn");
|
|
1326
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1327
|
+
var to = path.join(dir, "app.bin");
|
|
1328
|
+
var backupTo = path.join(dir, "app.bin.bak");
|
|
1329
|
+
var newBytes = Buffer.from("SUCCESSOR-THAT-DIED");
|
|
1330
|
+
fs.writeFileSync(to, newBytes);
|
|
1331
|
+
fs.writeFileSync(backupTo, Buffer.from("PRIOR-GOOD"));
|
|
1332
|
+
var begun = await b.selfUpdate.beginProbation({
|
|
1333
|
+
to: to, backupTo: backupTo, expectedHash: _sha3(newBytes), windowMs: 60000,
|
|
1334
|
+
});
|
|
1335
|
+
// Marker is a complete JSON record (recoverable — not lost by the spawn failure).
|
|
1336
|
+
var marker = JSON.parse(fs.readFileSync(begun.markerPath, "utf8"));
|
|
1337
|
+
check("probation marker is a complete record",
|
|
1338
|
+
marker.to === to && marker.backupTo === backupTo &&
|
|
1339
|
+
marker.expectedHash === _sha3(newBytes) && typeof marker.expiresAt === "number");
|
|
1340
|
+
check("probation marker records a generation", typeof marker.generation === "number");
|
|
1341
|
+
|
|
1342
|
+
var r = await b.selfUpdate.evaluateOnBoot({ to: to, now: begun.expiresAt + 1 });
|
|
1343
|
+
check("evaluateOnBoot: recovers from spawn failure → rollback", r.action === "rollback");
|
|
1344
|
+
check("evaluateOnBoot: restored the prior-good binary", fs.readFileSync(to, "utf8") === "PRIOR-GOOD");
|
|
1345
|
+
|
|
1346
|
+
try { fs.unlinkSync(to); fs.unlinkSync(backupTo); } catch (_e) { /* best-effort */ }
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
async function testProbationOptValidation() {
|
|
1350
|
+
// beginProbation requires to / backupTo / expectedHash; evaluateOnBoot requires to.
|
|
1351
|
+
var dir = _tmp("dir-prob-val");
|
|
1352
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1353
|
+
var t1 = null;
|
|
1354
|
+
try { await b.selfUpdate.beginProbation({ backupTo: "/x", expectedHash: "00" }); }
|
|
1355
|
+
catch (e) { t1 = e; }
|
|
1356
|
+
check("beginProbation: missing to → selfupdate/bad-to", t1 && /selfupdate\/bad-to/.test(t1.code || ""));
|
|
1357
|
+
|
|
1358
|
+
var t2 = null;
|
|
1359
|
+
try { await b.selfUpdate.beginProbation({ to: "/x", backupTo: "/y" }); }
|
|
1360
|
+
catch (e) { t2 = e; }
|
|
1361
|
+
check("beginProbation: missing expectedHash → selfupdate/bad-expected-hash",
|
|
1362
|
+
t2 && /selfupdate\/bad-expected-hash/.test(t2.code || ""));
|
|
1363
|
+
|
|
1364
|
+
var t3 = null;
|
|
1365
|
+
try { await b.selfUpdate.beginProbation({ to: "/x", backupTo: "/y", expectedHash: "00", windowMs: -1 }); }
|
|
1366
|
+
catch (e) { t3 = e; }
|
|
1367
|
+
check("beginProbation: bad windowMs → selfupdate/bad-window", t3 && /selfupdate\/bad-window/.test(t3.code || ""));
|
|
1368
|
+
|
|
1369
|
+
var t4 = null;
|
|
1370
|
+
try { await b.selfUpdate.evaluateOnBoot({ backupTo: "/y" }); }
|
|
1371
|
+
catch (e) { t4 = e; }
|
|
1372
|
+
check("evaluateOnBoot: missing to → selfupdate/bad-to", t4 && /selfupdate\/bad-to/.test(t4.code || ""));
|
|
1373
|
+
|
|
1374
|
+
// no-marker → keep(no-probation-active), never a rollback.
|
|
1375
|
+
var r = await b.selfUpdate.evaluateOnBoot({ to: path.join(dir, "absent.bin") });
|
|
1376
|
+
check("evaluateOnBoot: no marker → keep(no-probation-active)",
|
|
1377
|
+
r.action === "keep" && r.reason === "no-probation-active");
|
|
1378
|
+
|
|
1379
|
+
try { fs.rmdirSync(dir); } catch (_e) { /* best-effort */ }
|
|
1380
|
+
}
|
|
1381
|
+
|
|
363
1382
|
// selfUpdate.poll dials the releases endpoint through the shared httpClient
|
|
364
1383
|
// keep-alive transport pool; a cached client socket finalizes its destroy on a
|
|
365
1384
|
// later event-loop turn, past the forked worker's grace window. Reset the pool,
|
|
@@ -379,17 +1398,51 @@ async function run() {
|
|
|
379
1398
|
try {
|
|
380
1399
|
testSurface();
|
|
381
1400
|
testCompareTags();
|
|
1401
|
+
testCompareTagsFullPrecedence();
|
|
382
1402
|
await testPollRejectsBadOpts();
|
|
383
1403
|
await testPollRejectsUnsafeAssetPattern();
|
|
384
1404
|
await testPollAvailableAndUpToDate();
|
|
385
1405
|
await testPollArrayShape();
|
|
386
1406
|
await testPollNon2xxRefused();
|
|
1407
|
+
await testPoll304FastPath();
|
|
1408
|
+
await testPollNon2xxSurfacesTypedCode();
|
|
1409
|
+
await testPollEmptyArrayFeed();
|
|
1410
|
+
await testPollMalformedBodies();
|
|
1411
|
+
await testPollAvailableNoAssetMatch();
|
|
1412
|
+
await testPollDigestPassthrough();
|
|
1413
|
+
await testPollStringPatterns();
|
|
1414
|
+
await testPollRegexpPatterns();
|
|
1415
|
+
await testPollDowngradeRefusedWithEtag();
|
|
1416
|
+
await testPollOversizedFeedRefused();
|
|
1417
|
+
await testPollNonArrayAssetsAndMalformedEntries();
|
|
1418
|
+
await testPollSignaturePairsWithAsset();
|
|
1419
|
+
await testPollSignaturePairingEdgeCases();
|
|
1420
|
+
await testPollOptValidation();
|
|
387
1421
|
await testVerifyPassFail();
|
|
1422
|
+
await testVerifyErrorPaths();
|
|
1423
|
+
await testVerifyWrongKeyAndMalformedSig();
|
|
1424
|
+
await testVerifyEcdsaP384DigestCommitment();
|
|
1425
|
+
await testVerifyEcdsaP384Sha3P1363Accepted();
|
|
1426
|
+
await testVerifyMaxBytesBound();
|
|
388
1427
|
await testSwapAndRollback();
|
|
389
1428
|
await testSwapMissingFromRefused();
|
|
390
1429
|
await testSwapHashMismatchRefused();
|
|
391
1430
|
await testSwapSymlinkedFromRefused();
|
|
1431
|
+
await testSwapFreshInstall();
|
|
1432
|
+
await testSwapCustomHashAlgoRoundTrip();
|
|
1433
|
+
await testSwapMaxBytesBound();
|
|
1434
|
+
await testSwapBackupFailureLeavesOriginal();
|
|
1435
|
+
await testSwapSeparateBackupDir();
|
|
1436
|
+
await testSwapWriteFailureNoOriginal();
|
|
1437
|
+
await testSwapReplacesLockedTargetWin32();
|
|
1438
|
+
await testSwapRollbackOptValidation();
|
|
392
1439
|
await testRollbackMissingBackupRefused();
|
|
1440
|
+
await testRollbackCopyFailure();
|
|
1441
|
+
await testProbationExpiredRollsBack();
|
|
1442
|
+
await testProbationCleanStopWithinWindowKeeps();
|
|
1443
|
+
await testProbationConfirmAndFailedSwapNoPhantom();
|
|
1444
|
+
await testProbationMarkerRecoverableAfterSpawnFailure();
|
|
1445
|
+
await testProbationOptValidation();
|
|
393
1446
|
} finally {
|
|
394
1447
|
await _drainTcpHandles();
|
|
395
1448
|
}
|