@runeditrun/rer 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +157 -2
- package/bin/rer.mjs +3 -0
- package/cli/README.md +243 -0
- package/cli/adapter.mjs +10 -0
- package/cli/ai-local.mjs +65 -0
- package/cli/archive-path.mjs +52 -0
- package/cli/archive-vite.mjs +55 -0
- package/cli/archive.mjs +8 -0
- package/cli/auth-copy.mjs +80 -0
- package/cli/bounded.mjs +70 -0
- package/cli/cache-doctor.mjs +63 -0
- package/cli/container-transfer.mjs +35 -0
- package/cli/container.mjs +62 -0
- package/cli/contract.mjs +25 -0
- package/cli/dependencies.mjs +45 -0
- package/cli/dependency-approval.mjs +33 -0
- package/cli/dependency-doctor.mjs +34 -0
- package/cli/dependency-input.mjs +43 -0
- package/cli/deploy-config.mjs +118 -0
- package/cli/deploy.mjs +37 -0
- package/cli/dev-identity.mjs +80 -0
- package/cli/errors.mjs +14 -0
- package/cli/export.mjs +42 -0
- package/cli/font-cache.mjs +29 -0
- package/cli/hosted-output.mjs +110 -0
- package/cli/independent.mjs +29 -0
- package/cli/inspect.mjs +174 -0
- package/cli/jsonc.mjs +111 -0
- package/cli/lifecycle.mjs +148 -0
- package/cli/local.mjs +100 -0
- package/cli/main.mjs +57 -0
- package/cli/managed-server.mjs +143 -0
- package/cli/node-runtime-cache.mjs +68 -0
- package/cli/node-runtime-discovery.mjs +56 -0
- package/cli/node-runtime-download.mjs +59 -0
- package/cli/node-runtime.mjs +50 -0
- package/cli/output.mjs +22 -0
- package/cli/parse.mjs +77 -0
- package/cli/port.mjs +52 -0
- package/cli/portability-worker.mjs +56 -0
- package/cli/portability.mjs +66 -0
- package/cli/preview-cache.mjs +99 -0
- package/cli/project.mjs +48 -0
- package/cli/release.mjs +24 -0
- package/cli/runtime-front-door.mjs +46 -0
- package/cli/scheduled.mjs +81 -0
- package/cli/server-activity.mjs +12 -0
- package/cli/server-proxy.mjs +93 -0
- package/cli/server-registry.mjs +182 -0
- package/cli/server-resources.mjs +22 -0
- package/cli/settings.mjs +15 -0
- package/cli/setup-answers.mjs +29 -0
- package/cli/setup-input.mjs +113 -0
- package/cli/shared.mjs +7 -0
- package/cli/transfers.mjs +33 -0
- package/cli/version.mjs +36 -0
- package/composition/git-source.mjs +111 -0
- package/edition-authoring/files.mjs +81 -0
- package/edition-authoring/manifest.mjs +65 -0
- package/global/README.md +158 -0
- package/global/checkout.mjs +72 -0
- package/global/cli/command.mjs +13 -0
- package/global/cli/install.mjs +75 -0
- package/global/cli/layout.mjs +45 -0
- package/global/cli/status.mjs +54 -0
- package/global/cli/uninstall.mjs +36 -0
- package/global/delegate.mjs +13 -0
- package/global/launch/capacity.mjs +72 -0
- package/global/launch/dev.mjs +117 -0
- package/global/launch/facts.mjs +94 -0
- package/global/launch/failure.mjs +5 -0
- package/global/launch/local-defaults.mjs +4 -0
- package/global/launch/prepare.mjs +63 -0
- package/global/launch/render.mjs +44 -0
- package/global/launch/state.mjs +106 -0
- package/global/launch/steps.mjs +90 -0
- package/global/launch/stop.mjs +89 -0
- package/global/launch/worker.mjs +62 -0
- package/global/list.mjs +59 -0
- package/global/main.mjs +53 -0
- package/global/package-info.mjs +38 -0
- package/global/registry.mjs +24 -0
- package/global/rer.mjs +29 -0
- package/global/update/archive.mjs +89 -0
- package/global/update/command.mjs +90 -0
- package/global/update/latest.mjs +6 -0
- package/global/update/notice.mjs +58 -0
- package/global/update/registry.mjs +47 -0
- package/global/version.mjs +29 -0
- package/install.sh +144 -0
- package/modules/ai/LICENSE +21 -0
- package/modules/ai/README.md +90 -0
- package/modules/ai/client.mjs +71 -0
- package/modules/ai/config.mjs +39 -0
- package/modules/ai/dev/index.d.mts +6 -0
- package/modules/ai/dev/index.mjs +64 -0
- package/modules/ai/dev/outbox.mjs +33 -0
- package/modules/ai/errors.mjs +28 -0
- package/modules/ai/fake/index.d.mts +3 -0
- package/modules/ai/fake/index.mjs +82 -0
- package/modules/ai/index.d.mts +10 -0
- package/modules/ai/index.mjs +3 -0
- package/modules/ai/package.json +6 -0
- package/modules/container/README.md +156 -0
- package/modules/container/adapters/d1.mjs +34 -0
- package/modules/container/adapters/egress.mjs +40 -0
- package/modules/container/adapters/jobs.mjs +47 -0
- package/modules/container/adapters/objects.mjs +84 -0
- package/modules/container/adapters/retained.mjs +14 -0
- package/modules/container/adapters/sqlite-error.mjs +7 -0
- package/modules/container/adapters/sqlite-worker.mjs +26 -0
- package/modules/container/assets.mjs +14 -0
- package/modules/container/bindings.mjs +1 -0
- package/modules/container/boundary.mjs +38 -0
- package/modules/container/build.mjs +65 -0
- package/modules/container/deploy-input.mjs +17 -0
- package/modules/container/entrypoint.sh +5 -0
- package/modules/container/host.mjs +43 -0
- package/modules/container/ingress.mjs +23 -0
- package/modules/container/lock.mjs +11 -0
- package/modules/container/migrations.mjs +16 -0
- package/modules/container/node-engine.mjs +39 -0
- package/modules/container/portability-schema.mjs +14 -0
- package/modules/container/remote/deploy.py +76 -0
- package/modules/container/remote/files.py +275 -0
- package/modules/container/remote/resources.py +157 -0
- package/modules/container/runner.mjs +10 -0
- package/modules/container/ssh-plan.mjs +44 -0
- package/modules/container/ssh.mjs +63 -0
- package/modules/container/transfer.mjs +27 -0
- package/modules/container/worker-engine.mjs +32 -0
- package/modules/dependencies/README.md +211 -0
- package/modules/dependencies/approval.mjs +51 -0
- package/modules/dependencies/auth.mjs +40 -0
- package/modules/dependencies/authority.mjs +14 -0
- package/modules/dependencies/crypto.mjs +13 -0
- package/modules/dependencies/index.d.mts +23 -0
- package/modules/dependencies/index.mjs +113 -0
- package/modules/dependencies/logout.mjs +42 -0
- package/modules/dependencies/native.mjs +9 -0
- package/modules/dependencies/protocol.mjs +23 -0
- package/modules/dependencies/schema/approval-attempt.sql +3 -0
- package/modules/dependencies/schema/dependencies.sql +15 -0
- package/modules/dependencies/store.mjs +19 -0
- package/modules/deploy-kit/README.md +308 -0
- package/modules/deploy-kit/artifact.mjs +47 -0
- package/modules/deploy-kit/build-modules.mjs +21 -0
- package/modules/deploy-kit/check.mjs +13 -0
- package/modules/deploy-kit/cli.mjs +50 -0
- package/modules/deploy-kit/container-config.mjs +8 -0
- package/modules/deploy-kit/default-stage.mjs +14 -0
- package/modules/deploy-kit/deploy.mjs +80 -0
- package/modules/deploy-kit/health-request.mjs +86 -0
- package/modules/deploy-kit/health.mjs +99 -0
- package/modules/deploy-kit/index.mjs +12 -0
- package/modules/deploy-kit/local-only.mjs +10 -0
- package/modules/deploy-kit/migrations.mjs +48 -0
- package/modules/deploy-kit/owner-cli.mjs +63 -0
- package/modules/deploy-kit/owner.mjs +297 -0
- package/modules/deploy-kit/prepare.mjs +62 -0
- package/modules/deploy-kit/provider.mjs +99 -0
- package/modules/deploy-kit/queue-consumer.mjs +10 -0
- package/modules/deploy-kit/receipt.mjs +16 -0
- package/modules/deploy-kit/render.mjs +47 -0
- package/modules/deploy-kit/resources.mjs +106 -0
- package/modules/deploy-kit/service-grants.mjs +25 -0
- package/modules/deploy-kit/sql-safety.mjs +94 -0
- package/modules/deploy-kit/sql-tokens.mjs +49 -0
- package/modules/deploy-kit/teardown.mjs +100 -0
- package/modules/deploy-kit/templates.mjs +31 -0
- package/modules/deploy-kit/validation.mjs +58 -0
- package/modules/deploy-kit/worker.mjs +105 -0
- package/modules/erasure/CONTRACT.md +90 -0
- package/modules/erasure/README.md +221 -0
- package/modules/erasure/audit.mjs +23 -0
- package/modules/erasure/blobs.mjs +45 -0
- package/modules/erasure/cascade.mjs +91 -0
- package/modules/erasure/completion.mjs +62 -0
- package/modules/erasure/declaration.mjs +51 -0
- package/modules/erasure/fence.mjs +19 -0
- package/modules/erasure/index.d.mts +62 -0
- package/modules/erasure/index.mjs +85 -0
- package/modules/erasure/order.mjs +56 -0
- package/modules/erasure/plan.mjs +121 -0
- package/modules/erasure/rate.mjs +57 -0
- package/modules/erasure/receipt.mjs +32 -0
- package/modules/erasure/root.mjs +21 -0
- package/modules/erasure/schema/erasure-rates.sql +5 -0
- package/modules/erasure/schema/erasure.sql +27 -0
- package/modules/erasure/scope.mjs +29 -0
- package/modules/erasure/sql.mjs +23 -0
- package/modules/erasure/verification.mjs +69 -0
- package/modules/integrations/core/CONTRACT.md +50 -0
- package/modules/integrations/core/README.md +27 -0
- package/modules/integrations/core/crypto.ts +19 -0
- package/modules/integrations/core/database.ts +21 -0
- package/modules/integrations/core/delivery.ts +97 -0
- package/modules/integrations/core/destination.ts +13 -0
- package/modules/integrations/core/index.ts +7 -0
- package/modules/integrations/core/node-transport.ts +72 -0
- package/modules/integrations/core/public-address.ts +57 -0
- package/modules/integrations/core/repository.ts +74 -0
- package/modules/integrations/core/schema.sql +36 -0
- package/modules/integrations/core/sqlite.ts +20 -0
- package/modules/integrations/core/timestamped-body-signature.ts +38 -0
- package/modules/integrations/core/tokens.ts +71 -0
- package/modules/integrations/core/types.ts +52 -0
- package/modules/integrations/core/upgrade-token-usage-context.sql +5 -0
- package/modules/integrations/core/webhook.ts +73 -0
- package/modules/job-queue/README.md +173 -0
- package/modules/job-queue/cli.mjs +64 -0
- package/modules/job-queue/command.mjs +64 -0
- package/modules/job-queue/config.mjs +48 -0
- package/modules/job-queue/identity.mjs +48 -0
- package/modules/job-queue/index.mjs +135 -0
- package/modules/job-queue/membership.mjs +30 -0
- package/modules/job-queue/module.json +10 -0
- package/modules/job-queue/policy.mjs +29 -0
- package/modules/job-queue/processes.mjs +80 -0
- package/modules/job-queue/resources.mjs +37 -0
- package/modules/job-queue/storage.mjs +64 -0
- package/modules/job-queue/supervisor.mjs +30 -0
- package/modules/job-queue/waiting.mjs +16 -0
- package/modules/mail/LICENSE +21 -0
- package/modules/mail/README.md +336 -0
- package/modules/mail/doctor.mjs +81 -0
- package/modules/mail/index.mjs +8 -0
- package/modules/mail/live-proof/README.md +113 -0
- package/modules/mail/live-proof/run.mjs +55 -0
- package/modules/mail/package.json +19 -0
- package/modules/mail/pnpm-lock.yaml +537 -0
- package/modules/mail/src/adapters/cloudflare/README.md +84 -0
- package/modules/mail/src/adapters/cloudflare/headers.mjs +63 -0
- package/modules/mail/src/adapters/cloudflare/index.mjs +47 -0
- package/modules/mail/src/adapters/cloudflare/response.mjs +99 -0
- package/modules/mail/src/adapters/messaging/README.md +92 -0
- package/modules/mail/src/adapters/messaging/contract.mjs +66 -0
- package/modules/mail/src/adapters/messaging/event-schema.mjs +28 -0
- package/modules/mail/src/adapters/messaging/index.mjs +61 -0
- package/modules/mail/src/adapters/messaging/jws.mjs +68 -0
- package/modules/mail/src/adapters/messaging/request.mjs +52 -0
- package/modules/mail/src/adapters/messaging/serialize.mjs +21 -0
- package/modules/mail/src/adapters/resend/README.md +120 -0
- package/modules/mail/src/adapters/resend/index.mjs +22 -0
- package/modules/mail/src/adapters/resend/response.mjs +54 -0
- package/modules/mail/src/adapters/resend/serialize.mjs +46 -0
- package/modules/mail/src/adapters/resend/size.mjs +99 -0
- package/modules/mail/src/adapters/sendgrid/README.md +75 -0
- package/modules/mail/src/adapters/sendgrid/index.mjs +35 -0
- package/modules/mail/src/adapters/sendgrid/serialize.mjs +104 -0
- package/modules/mail/src/core/errors.mjs +9 -0
- package/modules/mail/src/core/transport.mjs +33 -0
- package/modules/mail/src/core/validation.mjs +172 -0
- package/modules/mail/src/development/index.d.mts +37 -0
- package/modules/mail/src/development/index.mjs +35 -0
- package/modules/mail/src/development/outbox.mjs +78 -0
- package/modules/mail/src/development/schema.sql +9 -0
- package/modules/mail/src/development/viewer-html.mjs +42 -0
- package/modules/mail/src/development/viewer.mjs +65 -0
- package/modules/mail/src/doctor/README.md +107 -0
- package/modules/mail/src/doctor/common.mjs +64 -0
- package/modules/mail/src/doctor/dns.mjs +102 -0
- package/modules/mail/src/doctor/index.mjs +28 -0
- package/modules/mail/src/doctor/probe.mjs +66 -0
- package/modules/mail/src/doctor/receive.mjs +81 -0
- package/modules/mail/src/doctor/records.mjs +45 -0
- package/modules/mail/src/events/README.md +268 -0
- package/modules/mail/src/events/d1-schema.sql +25 -0
- package/modules/mail/src/events/d1-storage.mjs +76 -0
- package/modules/mail/src/events/index.mjs +3 -0
- package/modules/mail/src/events/intake.mjs +201 -0
- package/modules/mail/src/events/storage.mjs +52 -0
- package/modules/mail/src/events/verify.mjs +92 -0
- package/modules/mail/src/inbound/README.md +137 -0
- package/modules/mail/src/inbound/bytes.mjs +91 -0
- package/modules/mail/src/inbound/headers.mjs +143 -0
- package/modules/mail/src/inbound/index.mjs +29 -0
- package/modules/mail/src/inbound/mime.mjs +36 -0
- package/modules/mail/src/inbound/multipart.mjs +27 -0
- package/modules/mail/src/inbound/resend-attachment.mjs +43 -0
- package/modules/mail/src/inbound/resend.mjs +54 -0
- package/modules/mail/src/inbound/sendgrid.mjs +60 -0
- package/modules/mail/src/sender.mjs +33 -0
- package/modules/mail/src/sendgrid-email/index.d.mts +33 -0
- package/modules/mail/src/sendgrid-email/index.mjs +86 -0
- package/modules/mail/src/unsubscribe/index.mjs +142 -0
- package/modules/operator-auth/README.md +599 -0
- package/modules/operator-auth/accounts/adapter.mjs +41 -0
- package/modules/operator-auth/accounts/admin.mjs +159 -0
- package/modules/operator-auth/accounts/credentials.mjs +27 -0
- package/modules/operator-auth/accounts/editor-owner.mjs +13 -0
- package/modules/operator-auth/accounts/index.d.mts +9 -0
- package/modules/operator-auth/accounts/index.mjs +9 -0
- package/modules/operator-auth/accounts/native.d.mts +38 -0
- package/modules/operator-auth/accounts/native.mjs +171 -0
- package/modules/operator-auth/accounts/oa.mjs +90 -0
- package/modules/operator-auth/accounts/provider-control.mjs +50 -0
- package/modules/operator-auth/accounts/provider-pin.mjs +14 -0
- package/modules/operator-auth/accounts/roles.mjs +26 -0
- package/modules/operator-auth/accounts/schema/dependency-setup.sql +17 -0
- package/modules/operator-auth/accounts/schema/dev-sessions.sql +22 -0
- package/modules/operator-auth/accounts/schema/domain-rules-step-up.sql +20 -0
- package/modules/operator-auth/accounts/schema/invitation-cancellation.sql +17 -0
- package/modules/operator-auth/accounts/schema/operator-auth.sql +142 -0
- package/modules/operator-auth/accounts/schema/provider-control.sql +30 -0
- package/modules/operator-auth/accounts/schema/provider-pins.sql +6 -0
- package/modules/operator-auth/accounts/settings-page.mjs +4 -0
- package/modules/operator-auth/accounts/store.mjs +246 -0
- package/modules/operator-auth/branding.mjs +66 -0
- package/modules/operator-auth/config.mjs +35 -0
- package/modules/operator-auth/conformance/README.md +81 -0
- package/modules/operator-auth/conformance/index.d.mts +43 -0
- package/modules/operator-auth/conformance/index.mjs +300 -0
- package/modules/operator-auth/conformance/presentation.mjs +57 -0
- package/modules/operator-auth/crypto.mjs +17 -0
- package/modules/operator-auth/dev/badge.mjs +62 -0
- package/modules/operator-auth/dev/index.d.mts +14 -0
- package/modules/operator-auth/dev/index.mjs +137 -0
- package/modules/operator-auth/dev/personas.mjs +17 -0
- package/modules/operator-auth/dev/sign-in-as.d.mts +3 -0
- package/modules/operator-auth/dev/sign-in-as.mjs +25 -0
- package/modules/operator-auth/dev-admission.d.mts +4 -0
- package/modules/operator-auth/dev-admission.mjs +10 -0
- package/modules/operator-auth/dev-gate.mjs +86 -0
- package/modules/operator-auth/docs/README.md +78 -0
- package/modules/operator-auth/docs/native-accounts.md +313 -0
- package/modules/operator-auth/docs/tanstack-start.md +218 -0
- package/modules/operator-auth/docs/vite-spa.md +144 -0
- package/modules/operator-auth/errors.mjs +9 -0
- package/modules/operator-auth/http.mjs +42 -0
- package/modules/operator-auth/identity/admission.mjs +105 -0
- package/modules/operator-auth/identity/after-confirm.mjs +18 -0
- package/modules/operator-auth/identity/email-link/confirmation.mjs +14 -0
- package/modules/operator-auth/identity/email-link/index.d.mts +48 -0
- package/modules/operator-auth/identity/email-link/index.mjs +128 -0
- package/modules/operator-auth/identity/email-link/message.mjs +16 -0
- package/modules/operator-auth/identity/index.d.mts +7 -0
- package/modules/operator-auth/identity/index.mjs +5 -0
- package/modules/operator-auth/identity/oidc/README.md +171 -0
- package/modules/operator-auth/identity/oidc/client-auth.mjs +16 -0
- package/modules/operator-auth/identity/oidc/config.mjs +102 -0
- package/modules/operator-auth/identity/oidc/crypto.mjs +29 -0
- package/modules/operator-auth/identity/oidc/errors.mjs +5 -0
- package/modules/operator-auth/identity/oidc/index.d.mts +33 -0
- package/modules/operator-auth/identity/oidc/index.mjs +287 -0
- package/modules/operator-auth/identity/oidc/jwt.mjs +102 -0
- package/modules/operator-auth/identity/oidc/network.mjs +93 -0
- package/modules/operator-auth/identity/oidc/presets.mjs +26 -0
- package/modules/operator-auth/identity/oidc/schema/oidc.sql +50 -0
- package/modules/operator-auth/identity/oidc/store.mjs +68 -0
- package/modules/operator-auth/identity/principal.mjs +28 -0
- package/modules/operator-auth/index.d.mts +223 -0
- package/modules/operator-auth/index.mjs +11 -0
- package/modules/operator-auth/input.mjs +55 -0
- package/modules/operator-auth/local-cli.mjs +16 -0
- package/modules/operator-auth/page.mjs +34 -0
- package/modules/operator-auth/policy.mjs +20 -0
- package/modules/operator-auth/routes.mjs +91 -0
- package/modules/operator-auth/service.mjs +152 -0
- package/modules/operator-auth/signin/index.mjs +67 -0
- package/modules/owner-claim/README.md +137 -0
- package/modules/owner-claim/idp.mjs +40 -0
- package/modules/owner-claim/index.d.mts +27 -0
- package/modules/owner-claim/index.mjs +39 -0
- package/modules/owner-claim/page.mjs +11 -0
- package/modules/owner-claim/schema/idp.sql +4 -0
- package/modules/owner-claim/schema/owner-claim.sql +18 -0
- package/modules/owner-claim/transaction.mjs +87 -0
- package/modules/owner-claim/verify.mjs +80 -0
- package/modules/portability/README.md +444 -0
- package/modules/portability/bridge.mjs +102 -0
- package/modules/portability/catalogue.mjs +118 -0
- package/modules/portability/cli-options.mjs +38 -0
- package/modules/portability/cli.mjs +56 -0
- package/modules/portability/copies.mjs +68 -0
- package/modules/portability/dev-data.mjs +32 -0
- package/modules/portability/diff.mjs +33 -0
- package/modules/portability/export.mjs +129 -0
- package/modules/portability/files.mjs +65 -0
- package/modules/portability/format.mjs +122 -0
- package/modules/portability/generated.mjs +60 -0
- package/modules/portability/hash.mjs +79 -0
- package/modules/portability/http.mjs +42 -0
- package/modules/portability/import.mjs +185 -0
- package/modules/portability/index.d.mts +58 -0
- package/modules/portability/index.mjs +22 -0
- package/modules/portability/journal.mjs +49 -0
- package/modules/portability/legacy-files.mjs +43 -0
- package/modules/portability/legacy-safety.mjs +27 -0
- package/modules/portability/lines.mjs +36 -0
- package/modules/portability/migration-history.mjs +18 -0
- package/modules/portability/migration-statements.mjs +23 -0
- package/modules/portability/payloads.mjs +52 -0
- package/modules/portability/provider-data.mjs +25 -0
- package/modules/portability/restore-safety.mjs +30 -0
- package/modules/portability/schema/portability.sql +25 -0
- package/modules/portability/storage.mjs +49 -0
- package/modules/release/README.md +212 -0
- package/modules/release/catalogue.mjs +79 -0
- package/modules/release/export.mjs +51 -0
- package/modules/release/produce.mjs +136 -0
- package/modules/release/public-child-transport-state.cjs +29 -0
- package/modules/release/public-child.mjs +71 -0
- package/modules/release/public-git.mjs +21 -0
- package/modules/release/publication-scan.mjs +22 -0
- package/modules/release/publish-handoff.mjs +37 -0
- package/modules/release/source.mjs +71 -0
- package/modules/release/test-summary.mjs +50 -0
- package/onboarding/README.md +163 -0
- package/onboarding/activity.mjs +31 -0
- package/onboarding/adapter-auth.mjs +88 -0
- package/onboarding/adapter-hosting.mjs +35 -0
- package/onboarding/adapter-labels.mjs +13 -0
- package/onboarding/adapter-settings.mjs +129 -0
- package/onboarding/adapter.mjs +238 -0
- package/onboarding/agent-handoff.mjs +9 -0
- package/onboarding/app-processes.mjs +21 -0
- package/onboarding/archive-output.d.mts +3 -0
- package/onboarding/archive-output.mjs +54 -0
- package/onboarding/command-custody.mjs +24 -0
- package/onboarding/command-recovery.mjs +8 -0
- package/onboarding/commands.mjs +166 -0
- package/onboarding/dependency-approval.mjs +36 -0
- package/onboarding/dev-identity.mjs +23 -0
- package/onboarding/email.mjs +24 -0
- package/onboarding/failures.mjs +42 -0
- package/onboarding/input-limits.mjs +7 -0
- package/onboarding/installer-inputs.mjs +24 -0
- package/onboarding/launcher.mjs +34 -0
- package/onboarding/local/build-files.mjs +64 -0
- package/onboarding/local/build.mjs +49 -0
- package/onboarding/local/environment.mjs +30 -0
- package/onboarding/local/files.mjs +35 -0
- package/onboarding/local/operation.mjs +166 -0
- package/onboarding/local/owner.mjs +34 -0
- package/onboarding/local/process-table.mjs +114 -0
- package/onboarding/local/recovery.mjs +79 -0
- package/onboarding/local/state.mjs +31 -0
- package/onboarding/metadata.mjs +7 -0
- package/onboarding/operator-install.mjs +45 -0
- package/onboarding/operator-installer.mjs +39 -0
- package/onboarding/operator-prerequisites.mjs +27 -0
- package/onboarding/package.json +6 -0
- package/onboarding/prefill.mjs +16 -0
- package/onboarding/private-files.mjs +97 -0
- package/onboarding/private.mjs +35 -0
- package/onboarding/process-cleanup.mjs +134 -0
- package/onboarding/process-diagnostics.mjs +91 -0
- package/onboarding/process-scope.mjs +118 -0
- package/onboarding/process-table.mjs +118 -0
- package/onboarding/process-tree.mjs +36 -0
- package/onboarding/production-modules.mjs +22 -0
- package/onboarding/production-options.mjs +40 -0
- package/onboarding/production-plan.mjs +8 -0
- package/onboarding/production-runtime.mjs +30 -0
- package/onboarding/project-vite.mjs +14 -0
- package/onboarding/providers.mjs +289 -0
- package/onboarding/queue.mjs +53 -0
- package/onboarding/queued-entry.mjs +34 -0
- package/onboarding/readiness.mjs +59 -0
- package/onboarding/redaction-jwt.mjs +32 -0
- package/onboarding/redaction.mjs +298 -0
- package/onboarding/schema/SETUP.schema.json +514 -0
- package/onboarding/schema/dependencies.mjs +35 -0
- package/onboarding/schema/json-schema.mjs +202 -0
- package/onboarding/schema/setup-validation.mjs +325 -0
- package/onboarding/secrets.mjs +21 -0
- package/onboarding/server.mjs +188 -0
- package/onboarding/setup-manifest.mjs +116 -0
- package/onboarding/state.mjs +46 -0
- package/onboarding/theme-files.mjs +37 -0
- package/onboarding/theme.mjs +118 -0
- package/onboarding/ui/activity.js +98 -0
- package/onboarding/ui/address-client.mjs +33 -0
- package/onboarding/ui/app.js +377 -0
- package/onboarding/ui/index.html +1 -0
- package/onboarding/ui/model-core.mjs +127 -0
- package/onboarding/ui/model.mjs +5 -0
- package/onboarding/ui/style.css +63 -0
- package/onboarding/ui/theme.css +37 -0
- package/onboarding/ui/workspace-art.js +30 -0
- package/onboarding/wrangler-auth.mjs +43 -0
- package/operations/README.md +246 -0
- package/operations/operations.mjs +168 -0
- package/operations/project-operations.mjs +157 -0
- package/package.json +11 -4
- package/scripts/publish-check/README.md +72 -0
- package/scripts/publish-check/checksum_maps.py +52 -0
- package/scripts/publish-check/cli.py +34 -0
- package/scripts/publish-check/command_arguments.py +51 -0
- package/scripts/publish-check/command_shell_quotes.py +41 -0
- package/scripts/publish-check/command_source_quotes.py +120 -0
- package/scripts/publish-check/content_identities.py +250 -0
- package/scripts/publish-check/copy_identities.py +41 -0
- package/scripts/publish-check/der.py +61 -0
- package/scripts/publish-check/destination.py +117 -0
- package/scripts/publish-check/env_values.py +74 -0
- package/scripts/publish-check/font_licence.py +23 -0
- package/scripts/publish-check/git_history.py +115 -0
- package/scripts/publish-check/github_refs.py +217 -0
- package/scripts/publish-check/gitleaks.py +72 -0
- package/scripts/publish-check/gitleaks.toml +40 -0
- package/scripts/publish-check/history_scan.py +176 -0
- package/scripts/publish-check/hit_location.py +18 -0
- package/scripts/publish-check/hygiene_config.py +42 -0
- package/scripts/publish-check/hygiene_text.py +11 -0
- package/scripts/publish-check/identity-recipe.mjs +54 -0
- package/scripts/publish-check/json_identity.py +79 -0
- package/scripts/publish-check/license-exceptions.json +11 -0
- package/scripts/publish-check/licenses.py +49 -0
- package/scripts/publish-check/policy.py +18 -0
- package/scripts/publish-check/project_identity.py +89 -0
- package/scripts/publish-check/provenance.py +91 -0
- package/scripts/publish-check/provenance_pins.py +33 -0
- package/scripts/publish-check/public_base.py +63 -0
- package/scripts/publish-check/public_hygiene.py +80 -0
- package/scripts/publish-check/publication-config.json +12 -0
- package/scripts/publish-check/publication_config.py +34 -0
- package/scripts/publish-check/report.py +52 -0
- package/scripts/publish-check/repository_commands.py +75 -0
- package/scripts/publish-check/repository_intent.py +52 -0
- package/scripts/publish-check/reviewed.py +41 -0
- package/scripts/publish-check/source_hashes.mjs +6 -0
- package/scripts/publish-check/tree_scan.py +88 -0
- package/scripts/publish-check/trust_bundle.py +87 -0
- package/scripts/publish-check.mjs +58 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"intendedPublicRepositories": [
|
|
4
|
+
{ "repository": "just-every/cradle", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
|
|
5
|
+
{ "repository": "just-every/penny", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
|
|
6
|
+
{ "repository": "just-every/mango", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
|
|
7
|
+
{ "repository": "just-every/lamp", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
|
|
8
|
+
{ "repository": "just-every/dial", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
|
|
9
|
+
{ "repository": "just-every/orbit", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." },
|
|
10
|
+
{ "repository": "just-every/uptime-monitor", "reason": "Owner-declared public identity; created by clean-history export at the first public tag." }
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Exact owner-declared future public identities, maintained by Base only."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from github_refs import FULL
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def unique_object(pairs):
|
|
8
|
+
result = {}
|
|
9
|
+
for key, value in pairs:
|
|
10
|
+
if key in result:
|
|
11
|
+
raise ValueError('duplicate publication configuration key')
|
|
12
|
+
result[key] = value
|
|
13
|
+
return result
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def intended_public_repositories():
|
|
17
|
+
path = Path(__file__).with_name('publication-config.json')
|
|
18
|
+
document = json.loads(path.read_bytes(), object_pairs_hook=unique_object)
|
|
19
|
+
if (not isinstance(document, dict) or
|
|
20
|
+
set(document) != {'schemaVersion', 'intendedPublicRepositories'} or
|
|
21
|
+
document['schemaVersion'] != 1 or
|
|
22
|
+
not isinstance(document['intendedPublicRepositories'], list)):
|
|
23
|
+
raise ValueError('invalid Base publication configuration')
|
|
24
|
+
repositories = set()
|
|
25
|
+
for entry in document['intendedPublicRepositories']:
|
|
26
|
+
if not isinstance(entry, dict) or set(entry) != {'repository', 'reason'}:
|
|
27
|
+
raise ValueError('invalid intended public repository')
|
|
28
|
+
repository, reason = entry['repository'], entry['reason']
|
|
29
|
+
if (not isinstance(repository, str) or not FULL.fullmatch(repository) or
|
|
30
|
+
repository != repository.lower() or repository in repositories or
|
|
31
|
+
not isinstance(reason, str) or not reason.strip()):
|
|
32
|
+
raise ValueError('invalid intended public repository')
|
|
33
|
+
repositories.add(repository)
|
|
34
|
+
return frozenset(repositories)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Caller-compatible, value-free publication reports."""
|
|
2
|
+
from policy import safe_path
|
|
3
|
+
from reviewed import FORBIDDEN
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def new_report(public_base=False):
|
|
7
|
+
return dict(schema='rer.publish-check/v3', status='INCOMPLETE', complete=False, source=None,
|
|
8
|
+
scanner=dict(name='gitleaks', version='8.30.1'),
|
|
9
|
+
history=dict(scope='selected-and-public-base' if public_base else 'all-local-refs-and-selected-commit',
|
|
10
|
+
commits=0, trees=0, blobs=0, commitMessages=0, tagMessages=0),
|
|
11
|
+
findings=[], preExisting=[], warnings=[], fixtures=[], reviewed=[], info=[],
|
|
12
|
+
guarantees=dict(historyScanned=False, visualRecognition=False, environmentChecked=False))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Findings:
|
|
16
|
+
def __init__(self, report, reviewed, env_hashes):
|
|
17
|
+
self.report, self.reviewed, self.env_hashes = report, reviewed, env_hashes
|
|
18
|
+
self.seen = {}
|
|
19
|
+
|
|
20
|
+
def add(self, rule, path, commit=None, oid=None, scope='newly-public', bucket=None, scanner_rule=None):
|
|
21
|
+
bucket = bucket or ('preExisting' if scope == 'pre-existing' and rule != 'secret.env-match' else 'findings')
|
|
22
|
+
if rule in ('pending-public-destination', 'provenance.declared-source'):
|
|
23
|
+
bucket = 'info'
|
|
24
|
+
if (rule, path, oid) in self.reviewed and bucket != 'info':
|
|
25
|
+
if rule in FORBIDDEN:
|
|
26
|
+
self.add('reviewed.forbidden', path, commit, oid, scope)
|
|
27
|
+
else:
|
|
28
|
+
bucket = 'reviewed'
|
|
29
|
+
key = (bucket, rule, path, oid, scanner_rule)
|
|
30
|
+
if key in self.seen:
|
|
31
|
+
item = self.seen[key]
|
|
32
|
+
if commit not in item['commits']:
|
|
33
|
+
item['commits'].append(commit)
|
|
34
|
+
if scope not in item['scopes']:
|
|
35
|
+
item['scopes'].append(scope)
|
|
36
|
+
return
|
|
37
|
+
item = dict(rule=rule, path=safe_path(path, self.env_hashes), commit=commit, commits=[commit], scope=scope, scopes=[scope])
|
|
38
|
+
if oid:
|
|
39
|
+
item['blob'] = oid
|
|
40
|
+
if scanner_rule:
|
|
41
|
+
item['scannerRule'] = scanner_rule
|
|
42
|
+
if bucket == 'info':
|
|
43
|
+
item['severity'] = 'INFO'
|
|
44
|
+
self.seen[key] = item
|
|
45
|
+
self.report[bucket].append(item)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def finish(report):
|
|
49
|
+
report['complete'] = not any(f['rule'].startswith('scan.') for bucket in ('findings', 'preExisting') for f in report[bucket])
|
|
50
|
+
report['guarantees']['historyScanned'] = report['complete']
|
|
51
|
+
report['status'] = 'INCOMPLETE' if not report['complete'] else 'FAIL' if report['findings'] else 'PASS'
|
|
52
|
+
return report
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Bound shell command arguments by their surrounding source/prose context."""
|
|
2
|
+
import re
|
|
3
|
+
from command_arguments import arguments
|
|
4
|
+
from command_source_quotes import SourceQuotes
|
|
5
|
+
|
|
6
|
+
COMMAND = re.compile(r'(?i)(?<![\w-])(?:git[ \t]+clone|gh[ \t]+repo[ \t]+(?:clone|view|fork))[ \t]+')
|
|
7
|
+
QUOTES = re.compile(r'`+|\"{3}|\'{3}|[\"\']')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def enclosing_quotes(text, stops):
|
|
11
|
+
"""Walk quote scopes once, yielding the innermost scope at each command."""
|
|
12
|
+
delimiters, index = [], 0
|
|
13
|
+
source_quotes = SourceQuotes(text)
|
|
14
|
+
for stop in stops:
|
|
15
|
+
while index < stop:
|
|
16
|
+
if text[index] == '\\':
|
|
17
|
+
index += 2
|
|
18
|
+
continue
|
|
19
|
+
delimiter, opened = delimiters[-1] if delimiters else (None, 0)
|
|
20
|
+
if delimiter:
|
|
21
|
+
if text[index] == '\n' and delimiter in ("'", '"'):
|
|
22
|
+
delimiters.pop()
|
|
23
|
+
index += 1
|
|
24
|
+
continue
|
|
25
|
+
if text.startswith(delimiter, index):
|
|
26
|
+
index += len(delimiter)
|
|
27
|
+
delimiters.pop()
|
|
28
|
+
continue
|
|
29
|
+
if not delimiter.startswith('`'):
|
|
30
|
+
index += 1
|
|
31
|
+
continue
|
|
32
|
+
# Inline code can contain source strings. The innermost source
|
|
33
|
+
# quote bounds its command before another argument or funding URL.
|
|
34
|
+
match = QUOTES.match(text, index)
|
|
35
|
+
# An already-open quote closes above, even when it follows a word.
|
|
36
|
+
apostrophe = match and match[0] == "'" and index and text[index - 1].isalnum()
|
|
37
|
+
if apostrophe:
|
|
38
|
+
start = opened + len(delimiter) if delimiter else 0
|
|
39
|
+
end = context_end(text, start, delimiter) if delimiter else len(text)
|
|
40
|
+
apostrophe = not source_quotes.contains(index, start, end)
|
|
41
|
+
if match and not apostrophe:
|
|
42
|
+
delimiters.append((match[0], index))
|
|
43
|
+
index = match.end()
|
|
44
|
+
else:
|
|
45
|
+
index += 1
|
|
46
|
+
yield delimiters[-1][0] if delimiters else None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def context_end(text, start, delimiter, stop=None):
|
|
50
|
+
stop = len(text) if stop is None else stop
|
|
51
|
+
if delimiter:
|
|
52
|
+
index = start
|
|
53
|
+
while index < stop:
|
|
54
|
+
if text[index] == '\\':
|
|
55
|
+
index += 2
|
|
56
|
+
elif text.startswith(delimiter, index):
|
|
57
|
+
return index
|
|
58
|
+
else:
|
|
59
|
+
index += 1
|
|
60
|
+
return stop
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def commands(text):
|
|
64
|
+
"""Retain complete arguments even if later surrounding prose is malformed."""
|
|
65
|
+
text = text.replace('\\\n', ' ')
|
|
66
|
+
matches = list(COMMAND.finditer(text))
|
|
67
|
+
scopes = enclosing_quotes(text, (match.start() for match in matches))
|
|
68
|
+
for match, delimiter in zip(matches, scopes):
|
|
69
|
+
line_end = text.find('\n', match.end())
|
|
70
|
+
if line_end < 0:
|
|
71
|
+
line_end = len(text)
|
|
72
|
+
end = context_end(text, match.end(), delimiter, line_end)
|
|
73
|
+
# Options may name reference repositories too. Shell separators
|
|
74
|
+
# and the outer text delimiter bound their repository intent.
|
|
75
|
+
yield ' '.join(arguments(text[match.end():end]))
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Repository declaration scopes independent of source language/extension."""
|
|
2
|
+
import re
|
|
3
|
+
from repository_commands import commands
|
|
4
|
+
|
|
5
|
+
FIELD = r'''["']?(?:repository|repo|github|sourceRepository)["']?'''
|
|
6
|
+
OBJECT = re.compile(r'(?i)(?<![\w$])' + FIELD + r'\s*[:=]\s*(?P<open>\{)')
|
|
7
|
+
TABLE = re.compile(r'''(?m)^\s*\[\[?\s*(?P<name>[^]\n]+)\]\]?[^\n]*''')
|
|
8
|
+
DECLARATION = re.compile(r'(?im)^(?P<indent>[ \t]*)(?:-\s*)?' + FIELD + r'[ \t]*:[ \t]*(?P<value>[^\n]*)')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def declarations(text):
|
|
12
|
+
for match in OBJECT.finditer(text):
|
|
13
|
+
start = match.start('open')
|
|
14
|
+
nesting, quote, escaped = 0, None, False
|
|
15
|
+
for end in range(start, len(text)):
|
|
16
|
+
char = text[end]
|
|
17
|
+
if quote:
|
|
18
|
+
if escaped:
|
|
19
|
+
escaped = False
|
|
20
|
+
elif char == '\\':
|
|
21
|
+
escaped = True
|
|
22
|
+
elif char == quote:
|
|
23
|
+
quote = None
|
|
24
|
+
elif char in ('"', "'", '`'):
|
|
25
|
+
quote = char
|
|
26
|
+
elif char == '{':
|
|
27
|
+
nesting += 1
|
|
28
|
+
elif char == '}':
|
|
29
|
+
nesting -= 1
|
|
30
|
+
if nesting == 0:
|
|
31
|
+
yield text[start:end + 1]
|
|
32
|
+
break
|
|
33
|
+
tables = list(TABLE.finditer(text))
|
|
34
|
+
for index, match in enumerate(tables):
|
|
35
|
+
names = [part.strip(' \t\'"').lower() for part in match['name'].split('.')]
|
|
36
|
+
if any(name in ('repository', 'repo', 'github', 'sourcerepository') for name in names):
|
|
37
|
+
end = tables[index + 1].start() if index + 1 < len(tables) else len(text)
|
|
38
|
+
yield text[match.end():end]
|
|
39
|
+
for match in DECLARATION.finditer(text):
|
|
40
|
+
if match['value'].lstrip().startswith('{'):
|
|
41
|
+
# Inline object boundaries were handled above; a sibling on the
|
|
42
|
+
# same line does not belong to the repository object.
|
|
43
|
+
continue
|
|
44
|
+
declaration = [match['value']]
|
|
45
|
+
indent = len(match['indent'].expandtabs())
|
|
46
|
+
for line in text[match.end():].splitlines()[1:]:
|
|
47
|
+
if not line.strip():
|
|
48
|
+
continue
|
|
49
|
+
if len(line.expandtabs()) - len(line.expandtabs().lstrip()) <= indent:
|
|
50
|
+
break
|
|
51
|
+
declaration.append(line)
|
|
52
|
+
yield '\n'.join(declaration)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Exact committed finding identities; provider tokens and known environment values cannot be waived."""
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from datetime import date, datetime, timezone
|
|
5
|
+
from hygiene_config import RULES
|
|
6
|
+
|
|
7
|
+
REVIEWED = '.publish-check/reviewed.json'
|
|
8
|
+
SECRET_RULES = {'secret.provider-token', 'secret.assignment', 'secret.private-key', 'secret.jwt',
|
|
9
|
+
'secret.url-credentials', 'secret.authorization', 'secret.shaped-file'}
|
|
10
|
+
FORBIDDEN = {'secret.provider-token', 'secret.env-match', 'license.missing'}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def unique_fields(pairs):
|
|
14
|
+
result = {}
|
|
15
|
+
for key, value in pairs:
|
|
16
|
+
if key in result:
|
|
17
|
+
raise ValueError('duplicate object field')
|
|
18
|
+
result[key] = value
|
|
19
|
+
return result
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_reviewed(data):
|
|
23
|
+
document = json.loads(data, object_pairs_hook=unique_fields)
|
|
24
|
+
if not isinstance(document, dict) or document.get('schemaVersion') != 1 or not isinstance(document.get('findings'), list):
|
|
25
|
+
raise ValueError('invalid reviewed document')
|
|
26
|
+
result = {}
|
|
27
|
+
for entry in document['findings']:
|
|
28
|
+
fields = {'rule', 'path', 'blob', 'reason', 'reviewer', 'date'}
|
|
29
|
+
if not isinstance(entry, dict) or set(entry) not in (fields, fields | {'fixtureValuesSha256'}, fields | {'containedFixtures'}) or entry['rule'] not in RULES | SECRET_RULES | FORBIDDEN | {'license.holder'}:
|
|
30
|
+
raise ValueError('invalid reviewed entry')
|
|
31
|
+
if not all(isinstance(entry[k], str) and entry[k].strip() for k in fields):
|
|
32
|
+
raise ValueError('invalid reviewed entry')
|
|
33
|
+
if not re.fullmatch(r'[0-9a-f]{40}|[0-9a-f]{64}', entry['blob']):
|
|
34
|
+
raise ValueError('invalid reviewed identity')
|
|
35
|
+
if date.fromisoformat(entry['date']) > datetime.now(timezone.utc).date():
|
|
36
|
+
raise ValueError('invalid review date')
|
|
37
|
+
key = (entry['rule'], entry['path'], entry['blob'])
|
|
38
|
+
# Older exports may repeat the same blob identity for distinct contained
|
|
39
|
+
# fixtures. Those fields no longer affect scanning; keep the review.
|
|
40
|
+
result[key] = entry
|
|
41
|
+
return result
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Reuse the maintained Source serializer; never approximate JSON.stringify in
|
|
2
|
+
// another language. Input and output stay on private subprocess pipes.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import {hash, json} from '../../edition-authoring/files.mjs';
|
|
5
|
+
const values = JSON.parse(fs.readFileSync(0, 'utf8'));
|
|
6
|
+
process.stdout.write(JSON.stringify(values.map(value => hash(json(value)))));
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Text hygiene and licence policy on immutable Git trees and present files."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import re
|
|
6
|
+
import stat
|
|
7
|
+
from public_hygiene import inspect
|
|
8
|
+
from provenance import MANIFESTS, declared_keys
|
|
9
|
+
from project_identity import tree_identity
|
|
10
|
+
from licenses import license_rules
|
|
11
|
+
from git_history import InspectionError
|
|
12
|
+
from trust_bundle import bundles
|
|
13
|
+
|
|
14
|
+
DEPENDENCY_DIRS = {'.git', 'node_modules'}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def environment_match(data, hashes):
|
|
18
|
+
# Known values are compared in memory. No value or digest enters a report.
|
|
19
|
+
candidates = [data, *re.findall(rb'[A-Za-z0-9_./+\-]{8,}(?:={1,2})?', data)]
|
|
20
|
+
return any(hashlib.sha256(value).digest() in hashes for value in candidates)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def context(history, commit, config):
|
|
24
|
+
entries = history.entries(commit)
|
|
25
|
+
local = set()
|
|
26
|
+
for entry in entries:
|
|
27
|
+
parts = entry['path'].split('/')
|
|
28
|
+
local.update('/'.join(parts[:n]) for n in range(1, len(parts) + 1))
|
|
29
|
+
declared = set()
|
|
30
|
+
for entry in history.root_entries(commit):
|
|
31
|
+
if entry['path'] in MANIFESTS and entry['kind'] == 'blob':
|
|
32
|
+
declared.update(declared_keys(history.blob(entry['oid'])))
|
|
33
|
+
identity, invalid = tree_identity(history, history.root_entries(commit))
|
|
34
|
+
blobs = {entry['path']: entry['oid'] for entry in entries if entry['kind'] == 'blob'}
|
|
35
|
+
trust = bundles(blobs, lambda path: history.blob(blobs[path]))
|
|
36
|
+
return {**config, 'localPaths': frozenset(local), 'declaredSources': frozenset(declared), 'projectIdentity': identity,
|
|
37
|
+
'trustBundles': trust}, invalid
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def text_rules(data, path, config, resolver, env_hashes):
|
|
41
|
+
rules, provenance = inspect(data, path, config, resolver)
|
|
42
|
+
path_rules, _ = inspect(path.encode('utf-8', 'surrogateescape'), '@path', config, resolver)
|
|
43
|
+
rules.update(path_rules)
|
|
44
|
+
if env_hashes and (environment_match(data, env_hashes) or environment_match(path.encode(), env_hashes)):
|
|
45
|
+
rules.add('secret.env-match')
|
|
46
|
+
if data.startswith(b'version https://git-lfs.github.com/spec/v1'):
|
|
47
|
+
rules.add('scan.lfs-uninspected')
|
|
48
|
+
return rules, provenance
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def working_files(repo):
|
|
52
|
+
for directory, children, files in os.walk(repo, followlinks=False):
|
|
53
|
+
children[:] = [name for name in children if name not in DEPENDENCY_DIRS]
|
|
54
|
+
for name in files:
|
|
55
|
+
if name == '.git':
|
|
56
|
+
continue
|
|
57
|
+
target = Path(directory) / name
|
|
58
|
+
path = target.relative_to(repo).as_posix()
|
|
59
|
+
try:
|
|
60
|
+
data = os.fsencode(os.readlink(target)) if target.is_symlink() else target.read_bytes()
|
|
61
|
+
except OSError:
|
|
62
|
+
raise InspectionError('scan.working-tree-unreadable') from None
|
|
63
|
+
yield path, data
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def working_regular(repo, path):
|
|
67
|
+
# Anchor reads to opened directories and refuse links at every step.
|
|
68
|
+
# The file descriptor's bytes and mode prove the same regular preimage.
|
|
69
|
+
directory = None
|
|
70
|
+
try:
|
|
71
|
+
flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
|
|
72
|
+
directory = os.open(repo, flags)
|
|
73
|
+
parts = path.split('/')
|
|
74
|
+
for part in parts[:-1]:
|
|
75
|
+
child = os.open(part, flags, dir_fd=directory)
|
|
76
|
+
os.close(directory)
|
|
77
|
+
directory = child
|
|
78
|
+
descriptor = os.open(parts[-1], os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=directory)
|
|
79
|
+
with os.fdopen(descriptor, 'rb') as file:
|
|
80
|
+
status = os.fstat(file.fileno())
|
|
81
|
+
if not stat.S_ISREG(status.st_mode):
|
|
82
|
+
return None
|
|
83
|
+
return file.read(), '100755' if status.st_mode & 0o111 else '100644'
|
|
84
|
+
except OSError:
|
|
85
|
+
return None
|
|
86
|
+
finally:
|
|
87
|
+
if directory is not None:
|
|
88
|
+
os.close(directory)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Public CA metadata inside pinned WebPKI trust-bundle artifacts.
|
|
2
|
+
|
|
3
|
+
A committed trust-bundle.json, regenerated by the build that produces its
|
|
4
|
+
artifacts, pins each artifact's SHA-256 and lists the SHA-256 of every compiled
|
|
5
|
+
trust anchor's subject Name and SubjectPublicKeyInfo DER. Its webpki-roots
|
|
6
|
+
release must be the one the same directory's Cargo.lock locks. Only attribute
|
|
7
|
+
values of a subject Name parsed from such an artifact, whose anchor's subject
|
|
8
|
+
and public key both occur there as a listed pair, are certificate metadata.
|
|
9
|
+
"""
|
|
10
|
+
from collections import namedtuple
|
|
11
|
+
from functools import lru_cache
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
from der import names, public_keys
|
|
16
|
+
|
|
17
|
+
MANIFEST = 'trust-bundle.json'
|
|
18
|
+
REGISTRY = 'registry+https://github.com/rust-lang/crates.io-index'
|
|
19
|
+
DIGEST = re.compile(r'[0-9a-f]{64}')
|
|
20
|
+
ARTIFACT = re.compile(r'[A-Za-z0-9_][A-Za-z0-9_.-]*')
|
|
21
|
+
Bundle = namedtuple('Bundle', 'digest anchors')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def locked_packages(lock):
|
|
25
|
+
packages, current = [], None
|
|
26
|
+
for line in lock.decode('utf-8').splitlines():
|
|
27
|
+
if line == '[[package]]':
|
|
28
|
+
current = {}
|
|
29
|
+
packages.append(current)
|
|
30
|
+
elif line.startswith('['):
|
|
31
|
+
current = None
|
|
32
|
+
elif current is not None and ' = "' in line and line.endswith('"'):
|
|
33
|
+
key, value = line.split(' = "', 1)
|
|
34
|
+
current[key] = value[:-1]
|
|
35
|
+
return packages
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@lru_cache(maxsize=16)
|
|
39
|
+
def parse(manifest, lock):
|
|
40
|
+
try:
|
|
41
|
+
record = json.loads(manifest)
|
|
42
|
+
roots, artifacts, anchors = record['roots'], record['artifacts'], record['anchors']
|
|
43
|
+
if (set(record) != {'schemaVersion', 'roots', 'artifacts', 'anchors'} or record['schemaVersion'] != 1 or
|
|
44
|
+
set(roots) != {'crate', 'version', 'checksum'} or roots['crate'] != 'webpki-roots' or
|
|
45
|
+
not isinstance(roots['version'], str) or not DIGEST.fullmatch(roots['checksum']) or
|
|
46
|
+
not artifacts or not all(isinstance(name, str) and ARTIFACT.fullmatch(name) and isinstance(digest, str) and DIGEST.fullmatch(digest)
|
|
47
|
+
for name, digest in artifacts.items()) or
|
|
48
|
+
not anchors or not all(isinstance(a, dict) and set(a) == {'subject', 'spki'} and
|
|
49
|
+
all(isinstance(v, str) and DIGEST.fullmatch(v) for v in a.values()) for a in anchors)):
|
|
50
|
+
return None
|
|
51
|
+
locked = [p for p in locked_packages(lock) if p.get('name') == roots['crate']]
|
|
52
|
+
except (ValueError, TypeError, KeyError, AttributeError, UnicodeError):
|
|
53
|
+
return None
|
|
54
|
+
if len(locked) != 1 or locked[0] != dict(name=roots['crate'], version=roots['version'], source=REGISTRY, checksum=roots['checksum']):
|
|
55
|
+
return None
|
|
56
|
+
pairs = frozenset((a['subject'], a['spki']) for a in anchors)
|
|
57
|
+
return tuple((name, Bundle(digest, pairs)) for name, digest in artifacts.items())
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def bundles(paths, read):
|
|
61
|
+
"""{artifact path: Bundle} declared by valid manifests among a tree's paths."""
|
|
62
|
+
result = {}
|
|
63
|
+
for path in paths:
|
|
64
|
+
directory, _, name = path.rpartition('/')
|
|
65
|
+
prefix = directory + '/' if directory else ''
|
|
66
|
+
if name != MANIFEST or prefix + 'Cargo.lock' not in paths:
|
|
67
|
+
continue
|
|
68
|
+
for artifact, bundle in parse(read(path), read(prefix + 'Cargo.lock')) or ():
|
|
69
|
+
result[prefix + artifact] = bundle
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def anchor_values(data, bundle):
|
|
74
|
+
"""Attribute value spans of verified trust-anchor subjects in exact pinned bytes."""
|
|
75
|
+
if hashlib.sha256(data).hexdigest() != bundle.digest:
|
|
76
|
+
return []
|
|
77
|
+
subjects = {subject for subject, _ in bundle.anchors}
|
|
78
|
+
keys = {spki for _, spki in bundle.anchors}
|
|
79
|
+
found = {}
|
|
80
|
+
for start, end, values in names(data):
|
|
81
|
+
digest = hashlib.sha256(data[start:end]).hexdigest()
|
|
82
|
+
if digest in subjects:
|
|
83
|
+
found.setdefault(digest, []).append(values)
|
|
84
|
+
present = {digest for start, end in public_keys(data)
|
|
85
|
+
if (digest := hashlib.sha256(data[start:end]).hexdigest()) in keys}
|
|
86
|
+
return [span for subject, spki in bundle.anchors if spki in present
|
|
87
|
+
for values in found.get(subject, ()) for span in values]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { mkdtempSync, readFileSync, openSync, closeSync, rmSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
let seconds = 300;
|
|
9
|
+
for (let i = 0; i < args.length; i++) {
|
|
10
|
+
const raw = args[i] === '--timeout-seconds' ? args[i + 1] : args[i].startsWith('--timeout-seconds=') ? args[i].slice(18) : null;
|
|
11
|
+
if (raw !== null && Number(raw) > 0 && Number(raw) <= 7200) seconds = Number(raw);
|
|
12
|
+
}
|
|
13
|
+
const scratch = mkdtempSync(join(tmpdir(), 'publish-check-'));
|
|
14
|
+
const output = join(scratch, 'report.json');
|
|
15
|
+
const fd = openSync(output, 'wx', 0o600);
|
|
16
|
+
let child, reason, timer;
|
|
17
|
+
const stop = value => {
|
|
18
|
+
reason ??= value;
|
|
19
|
+
// The child remains unreaped until its close handler; descendants (native
|
|
20
|
+
// Git and Gitleaks) share the private process group and this total deadline.
|
|
21
|
+
if (child?.pid) {
|
|
22
|
+
try { process.kill(-child.pid, 'SIGKILL'); } catch { /* already stopped */ }
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const interrupt = () => stop('scan.interrupted');
|
|
26
|
+
process.on('SIGINT', interrupt);
|
|
27
|
+
process.on('SIGTERM', interrupt);
|
|
28
|
+
let report, code = 2;
|
|
29
|
+
try {
|
|
30
|
+
child = spawn('python3', ['-B', new URL('./publish-check/cli.py', import.meta.url).pathname, ...args], {
|
|
31
|
+
detached: true, stdio: ['ignore', fd, 'ignore'], env: { ...process.env, TMPDIR: scratch },
|
|
32
|
+
});
|
|
33
|
+
closeSync(fd);
|
|
34
|
+
timer = setTimeout(() => stop('scan.deadline-exceeded'), seconds * 1000);
|
|
35
|
+
const result = await new Promise(resolve => {
|
|
36
|
+
child.once('error', () => resolve(null));
|
|
37
|
+
child.once('close', value => resolve(value));
|
|
38
|
+
});
|
|
39
|
+
if (!reason && [0, 1, 2].includes(result)) {
|
|
40
|
+
report = JSON.parse(readFileSync(output, 'utf8'));
|
|
41
|
+
code = result;
|
|
42
|
+
} else reason ??= 'scan.worker-incomplete';
|
|
43
|
+
} catch { reason ??= 'scan.worker-incomplete'; }
|
|
44
|
+
finally {
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
process.removeListener('SIGINT', interrupt);
|
|
47
|
+
process.removeListener('SIGTERM', interrupt);
|
|
48
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
if (reason) report = {
|
|
51
|
+
schema: 'rer.publish-check/v3', status: 'INCOMPLETE', complete: false, source: null,
|
|
52
|
+
findings: [{ rule: reason, path: '.', commit: null, scope: 'newly-public' }],
|
|
53
|
+
preExisting: [], warnings: [], fixtures: [], reviewed: [], info: [],
|
|
54
|
+
history: { commits: 0, trees: 0, blobs: 0 },
|
|
55
|
+
guarantees: { historyScanned: false, visualRecognition: false, environmentChecked: false },
|
|
56
|
+
};
|
|
57
|
+
process.stdout.write(JSON.stringify(report) + '\n');
|
|
58
|
+
process.exitCode = code;
|