@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,18 @@
|
|
|
1
|
+
"""Resolve a native Gitleaks hit to the exact byte spans of its match."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def match_spans(data, hit):
|
|
5
|
+
lines = data.splitlines(keepends=True)
|
|
6
|
+
try:
|
|
7
|
+
first, last = hit['StartLine'] - 1, hit['EndLine'] - 1
|
|
8
|
+
if not (0 <= first <= last < len(lines)):
|
|
9
|
+
return []
|
|
10
|
+
start = sum(map(len, lines[:first])) + hit['StartColumn'] - 1
|
|
11
|
+
end = sum(map(len, lines[:last])) + hit['EndColumn']
|
|
12
|
+
# Gitleaks 8.30.1 includes the preceding newline in columns
|
|
13
|
+
# after fragment line one; Git fragments may begin mid-file.
|
|
14
|
+
# Resolve that one-byte difference only against the exact match.
|
|
15
|
+
return [(start - shift, end - shift) for shift in (0, 1)
|
|
16
|
+
if data[start - shift:end - shift] == hit['Match'].encode('utf-8')]
|
|
17
|
+
except (KeyError, TypeError, IndexError, UnicodeError):
|
|
18
|
+
return []
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Base-owned defaults and explicit committed seed publication declarations."""
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from publication_config import intended_public_repositories
|
|
5
|
+
|
|
6
|
+
# A home segment within a relative path is not an absolute home directory.
|
|
7
|
+
# Match the username itself so a bare home, including one with spaces, counts.
|
|
8
|
+
LOCAL_PATH = re.compile(r'''(?x)(?<![^\s("'`=:\[<>{|])(?:[*_]|~~)*(?:(?:(?i:(?a:file://)))?(?<!/[A-Z]:)/(?:Users|home)/|(?i:(?:(?a:file:///))?[A-Z]:[\\/]+Users[\\/]+))[^/\\\s"'<>`]+''')
|
|
9
|
+
CONFIG = '.publish-check/config.json'
|
|
10
|
+
# An alphabetic top-level label ends before a following digit, such as the DER
|
|
11
|
+
# tag after a certificate's email value; other labels keep the full extent.
|
|
12
|
+
EMAIL = re.compile(r"(?<![A-Za-z0-9_.+%-])[A-Za-z0-9_][A-Za-z0-9_.+%'-]{0,63}@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)*[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.(?:[A-Za-z]{2,63}(?![A-Za-z.-])|[A-Za-z][A-Za-z0-9-]{1,62}(?![A-Za-z0-9.-]))|(?<![A-Za-z0-9_.+%-])[A-Za-z0-9_][A-Za-z0-9_.+%'-]{0,63}@(?:example|test|invalid)(?![A-Za-z0-9.-])", re.I)
|
|
13
|
+
RULES = {'hygiene.local-path', 'hygiene.github-private', 'hygiene.github-missing',
|
|
14
|
+
'hygiene.email', 'hygiene.internal-reference'}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_config(data=None):
|
|
18
|
+
config = json.loads(data) if data is not None else {'schemaVersion': 1}
|
|
19
|
+
if not isinstance(config, dict) or config.get('schemaVersion') != 1 or set(config) - {'schemaVersion', 'publicEmails', 'internalReferencePatterns'}:
|
|
20
|
+
raise ValueError('invalid publication config')
|
|
21
|
+
emails = config.get('publicEmails', [])
|
|
22
|
+
extra_patterns = config.get('internalReferencePatterns', [])
|
|
23
|
+
if not isinstance(extra_patterns, list):
|
|
24
|
+
raise ValueError('invalid internal references')
|
|
25
|
+
patterns = extra_patterns
|
|
26
|
+
if not isinstance(emails, list) or any(not isinstance(e, str) or not EMAIL.fullmatch(e) or '*' in e for e in emails):
|
|
27
|
+
raise ValueError('invalid public emails')
|
|
28
|
+
if not isinstance(patterns, list) or any(not isinstance(p, str) or not p for p in patterns):
|
|
29
|
+
raise ValueError('invalid internal references')
|
|
30
|
+
try:
|
|
31
|
+
compiled = [re.compile(p) for p in patterns]
|
|
32
|
+
except re.error:
|
|
33
|
+
raise ValueError('invalid internal reference regex') from None
|
|
34
|
+
return {'publicEmails': {e.lower() for e in emails}, 'internalReferencePatterns': compiled,
|
|
35
|
+
'intendedPublicRepositories': intended_public_repositories()}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def public_email(address, config):
|
|
39
|
+
domain = address.lower().rsplit('@', 1)[1]
|
|
40
|
+
# RFC 2606 reserved names; .localhost also holds local dev personas (<as>@dev.localhost).
|
|
41
|
+
reserved = ('example', 'test', 'invalid', 'localhost', 'example.com', 'example.net', 'example.org')
|
|
42
|
+
return address.lower() in config['publicEmails'] or any(domain == d or domain.endswith('.' + d) for d in reserved)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Decode common textual representations without executing source."""
|
|
2
|
+
import html
|
|
3
|
+
import re
|
|
4
|
+
from urllib.parse import unquote
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def decoded_text(data):
|
|
8
|
+
text = data.replace(b'\0', b'').decode('utf-8', 'replace')
|
|
9
|
+
text = html.unescape(unquote(text))
|
|
10
|
+
return re.sub(r'\\(?:u([0-9a-fA-F]{4})|x([0-9a-fA-F]{2}))',
|
|
11
|
+
lambda match: chr(int(match.group(1) or match.group(2), 16)), text).replace('\\/', '/')
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Validate inert committed JSON with the maintained recipe implementation.
|
|
2
|
+
// Never import or execute a tool from the repository being inspected.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {manifest, VERSION} from '../../edition-authoring/manifest.mjs';
|
|
7
|
+
import {hash, json} from '../../edition-authoring/files.mjs';
|
|
8
|
+
|
|
9
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
10
|
+
const identity = value => typeof value === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value) && !/\s/.test(value);
|
|
11
|
+
function optionalArray(value, key) {
|
|
12
|
+
if (key in value && !Array.isArray(value[key])) throw Error('Invalid array');
|
|
13
|
+
}
|
|
14
|
+
function origin(value) {
|
|
15
|
+
if (!object(value) || typeof value.repository !== 'string' || typeof value.revision !== 'string' || value.revision.length !== 40) throw Error('Invalid origin');
|
|
16
|
+
optionalArray(value, 'paths');
|
|
17
|
+
}
|
|
18
|
+
function shape(recipe) {
|
|
19
|
+
if (!object(recipe) || recipe.identityKind !== 'project' || !identity(recipe.id) || !object(recipe.toolchain)) throw Error('Invalid identity');
|
|
20
|
+
if (recipe.parent !== undefined && recipe.parent !== null) origin(recipe.parent);
|
|
21
|
+
if (!Array.isArray(recipe.components)) throw Error('Invalid components');
|
|
22
|
+
for (const component of recipe.components) {
|
|
23
|
+
if (!object(component) || !identity(component.id) || !object(component.source)) throw Error('Invalid component');
|
|
24
|
+
for (const key of ['requires', 'conflicts']) {
|
|
25
|
+
optionalArray(component, key);
|
|
26
|
+
if (component[key]?.some(value => typeof value !== 'string')) throw Error('Invalid dependency');
|
|
27
|
+
}
|
|
28
|
+
const selected = component.source;
|
|
29
|
+
if (selected.kind === 'git') origin(selected);
|
|
30
|
+
else if (selected.kind === 'local') {
|
|
31
|
+
if (selected.origin !== undefined) origin(selected.origin);
|
|
32
|
+
optionalArray(selected, 'mappings');
|
|
33
|
+
if (selected.mappings?.some(value => !object(value))) throw Error('Invalid mapping');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let directory;
|
|
39
|
+
try {
|
|
40
|
+
const data = fs.readFileSync(0, 'utf8');
|
|
41
|
+
const recipe = JSON.parse(data);
|
|
42
|
+
shape(recipe);
|
|
43
|
+
if (VERSION !== '1.1.0') throw Error('Unsupported recipe validator');
|
|
44
|
+
directory = fs.mkdtempSync(path.join(os.tmpdir(), 'publish-identity-'));
|
|
45
|
+
fs.writeFileSync(path.join(directory, 'edition.json'), data);
|
|
46
|
+
// manifest currently returns the complete parsed recipe, including metadata.
|
|
47
|
+
// Use its exact JSON.stringify(..., null, 2) + newline hash, as cli lock does.
|
|
48
|
+
process.stdout.write(hash(json(manifest(directory))));
|
|
49
|
+
} catch {
|
|
50
|
+
// Input, paths and diagnostics are intentionally absent from the report.
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
} finally {
|
|
53
|
+
if (directory) fs.rmSync(directory, {recursive: true, force: true});
|
|
54
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Strict JSON documents and exact native-match byte locations."""
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from hit_location import match_spans
|
|
6
|
+
|
|
7
|
+
HEX = re.compile(r'[a-f0-9]{64}\Z')
|
|
8
|
+
GIT_REVISION = re.compile(r'[a-f0-9]{40}\Z')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Document:
|
|
12
|
+
"""Reject ambiguous/non-JSON input and retain exact byte spans of strings."""
|
|
13
|
+
def __init__(self, data):
|
|
14
|
+
self.data = data
|
|
15
|
+
text = data.decode('utf-8')
|
|
16
|
+
self.spans = {}
|
|
17
|
+
def invalid_constant(_):
|
|
18
|
+
raise ValueError('Non-JSON constant')
|
|
19
|
+
|
|
20
|
+
decoder = json.JSONDecoder(parse_constant=invalid_constant)
|
|
21
|
+
|
|
22
|
+
def whitespace(at):
|
|
23
|
+
while at < len(text) and text[at] in ' \t\r\n':
|
|
24
|
+
at += 1
|
|
25
|
+
return at
|
|
26
|
+
|
|
27
|
+
def parse(at, pointer):
|
|
28
|
+
at = whitespace(at)
|
|
29
|
+
if text[at] in '{[':
|
|
30
|
+
mapping = text[at] == '{'
|
|
31
|
+
end = '}' if mapping else ']'
|
|
32
|
+
result = {} if mapping else []
|
|
33
|
+
at = whitespace(at + 1)
|
|
34
|
+
if text[at] == end:
|
|
35
|
+
return result, at + 1
|
|
36
|
+
while True:
|
|
37
|
+
if mapping:
|
|
38
|
+
key, at = decoder.raw_decode(text, at)
|
|
39
|
+
if not isinstance(key, str) or key in result:
|
|
40
|
+
raise ValueError('Ambiguous JSON member')
|
|
41
|
+
at = whitespace(at)
|
|
42
|
+
if text[at] != ':':
|
|
43
|
+
raise ValueError('Invalid JSON member')
|
|
44
|
+
at += 1
|
|
45
|
+
else:
|
|
46
|
+
key = len(result)
|
|
47
|
+
value, at = parse(at, (*pointer, key))
|
|
48
|
+
if mapping:
|
|
49
|
+
result[key] = value
|
|
50
|
+
else:
|
|
51
|
+
result.append(value)
|
|
52
|
+
at = whitespace(at)
|
|
53
|
+
if text[at] == end:
|
|
54
|
+
return result, at + 1
|
|
55
|
+
if text[at] != ',':
|
|
56
|
+
raise ValueError('Invalid JSON separator')
|
|
57
|
+
at = whitespace(at + 1)
|
|
58
|
+
value, end = decoder.raw_decode(text, at)
|
|
59
|
+
if isinstance(value, str) and (HEX.fullmatch(value) or GIT_REVISION.fullmatch(value)):
|
|
60
|
+
self.spans[pointer] = (len(text[:at].encode('utf-8')), len(text[:end].encode('utf-8')), value)
|
|
61
|
+
return value, end
|
|
62
|
+
|
|
63
|
+
self.value, end = parse(0, ())
|
|
64
|
+
if whitespace(end) != len(text):
|
|
65
|
+
raise ValueError('Trailing JSON data')
|
|
66
|
+
|
|
67
|
+
def matches(self, hit, pointers):
|
|
68
|
+
if hit.get('RuleID') not in ('generic-api-key', 'hex-api-key'):
|
|
69
|
+
return False
|
|
70
|
+
located = set()
|
|
71
|
+
for left, right in match_spans(self.data, hit):
|
|
72
|
+
for pointer in pointers:
|
|
73
|
+
span = self.spans.get(pointer)
|
|
74
|
+
if span:
|
|
75
|
+
a, b, value = span
|
|
76
|
+
if (left <= a + 1 and b - 1 <= right and hit.get('Secret') == value
|
|
77
|
+
and self.data[a + 1:b - 1] == value.encode('ascii')):
|
|
78
|
+
located.add((a, b))
|
|
79
|
+
return len(located) == 1
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"exceptions": [{
|
|
4
|
+
"rule": "license.holder",
|
|
5
|
+
"path": "LICENSE",
|
|
6
|
+
"holder": "Peter Industries Pty Ltd as trustee for Peter Family Trust, trading as JustEvery",
|
|
7
|
+
"date": "2026-10-06",
|
|
8
|
+
"sha256": "75de081fbc3da1ca679dff36ae665709d8c312652527d321511a10f66a17fd7c",
|
|
9
|
+
"reason": "Owner-authorized preservation of HappyToHelp's original attribution, exactly as shipped in public v0.2.1 and retained for the fast-forward v0.2.2 candidate. Applies only to these complete LICENSE bytes."
|
|
10
|
+
}]
|
|
11
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Root licence holder checks and narrowly bound, dated exceptions."""
|
|
2
|
+
from datetime import date, datetime, timezone
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
EXCEPTIONS = 'PUBLISH-EXCEPTIONS.json'
|
|
9
|
+
HOLDER = re.compile(r'^\s*Copyright\s*(?:\(c\)|©)?\s*(?:\d{4}(?:\s*[-–,]\s*\d{4})*\s+)?(.+?)\s*$', re.I | re.M)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def exceptions(data):
|
|
13
|
+
if data is None:
|
|
14
|
+
return []
|
|
15
|
+
document = json.loads(data)
|
|
16
|
+
if document.get('schemaVersion') != 1 or not isinstance(document.get('exceptions'), list):
|
|
17
|
+
raise ValueError('invalid exceptions')
|
|
18
|
+
for entry in document['exceptions']:
|
|
19
|
+
if (entry.get('rule') != 'license.holder' or entry.get('path') != 'LICENSE'
|
|
20
|
+
or not isinstance(entry.get('holder'), str) or not entry['holder'].strip()
|
|
21
|
+
or not re.fullmatch(r'[0-9a-f]{64}', entry.get('sha256', ''))
|
|
22
|
+
or not isinstance(entry.get('reason'), str) or not entry['reason'].strip()
|
|
23
|
+
or not re.fullmatch(r'\d{4}-\d{2}-\d{2}', entry.get('date', ''))
|
|
24
|
+
or date.fromisoformat(entry['date']) > datetime.now(timezone.utc).date()):
|
|
25
|
+
raise ValueError('invalid exception')
|
|
26
|
+
return document['exceptions']
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def license_rules(data, recorded):
|
|
30
|
+
if data is None or not data.strip():
|
|
31
|
+
return ['license.missing']
|
|
32
|
+
try:
|
|
33
|
+
text = data.decode('utf-8')
|
|
34
|
+
except UnicodeError:
|
|
35
|
+
return ['license.unreadable']
|
|
36
|
+
holders = HOLDER.findall(text)
|
|
37
|
+
if not holders:
|
|
38
|
+
return ['license.holder-missing']
|
|
39
|
+
digest = hashlib.sha256(data).hexdigest()
|
|
40
|
+
if any(holder != 'Run Edit Run' and not any(e['holder'] == holder and e['sha256'] == digest for e in recorded)
|
|
41
|
+
for holder in holders):
|
|
42
|
+
return ['license.holder']
|
|
43
|
+
return []
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def registry():
|
|
47
|
+
"""Base-reviewed exceptions, bound to holder and complete LICENSE bytes."""
|
|
48
|
+
path = Path(__file__).with_name('license-exceptions.json')
|
|
49
|
+
return exceptions(path.read_bytes())
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Report redaction, separate from the Gitleaks secret detector."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import re
|
|
4
|
+
from hygiene_config import EMAIL, LOCAL_PATH
|
|
5
|
+
from hygiene_text import decoded_text
|
|
6
|
+
|
|
7
|
+
READ_LIMIT = 64 * 1024 * 1024
|
|
8
|
+
TOKEN_PATH = re.compile(r'(?:gh[pousr]_|github_pat_|sk-|glpat-|npm_|whsec_|rk_live_|sk_live_|hf_|xapp-|xox[baprs]-|SG\.|re_|AIza|AKIA|ASIA)[A-Za-z0-9_./+\-]{16,}')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def safe_path(path, env_hashes=frozenset(), internal_patterns=()):
|
|
12
|
+
raw = path.encode('utf-8', 'surrogateescape')
|
|
13
|
+
text = decoded_text(raw)
|
|
14
|
+
private = EMAIL.search(text) or LOCAL_PATH.search(text) or TOKEN_PATH.search(text)
|
|
15
|
+
if (private or hashlib.sha256(raw).digest() in env_hashes or any(re.search(p, text) for p in internal_patterns)
|
|
16
|
+
or any(ord(c) < 32 or 0xD800 <= ord(c) <= 0xDFFF for c in path) or len(path) > 500):
|
|
17
|
+
return '[redacted-path-sha256:' + hashlib.sha256(raw).hexdigest() + ']'
|
|
18
|
+
return path
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Same-tree, explicit project identities; never a repository-name allowlist."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
from hygiene_text import decoded_text
|
|
7
|
+
from policy import READ_LIMIT
|
|
8
|
+
from reviewed import unique_fields
|
|
9
|
+
|
|
10
|
+
FIELDS = {'edition.json': 'id', 'sources.lock.json': 'edition', 'rer-project.json': 'projectId'}
|
|
11
|
+
TOOLCHAIN = {'id': 'runeditrun/edition-authoring', 'version': '1.1.0'}
|
|
12
|
+
VALIDATOR = Path(__file__).with_name('identity-recipe.mjs')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def document(data):
|
|
16
|
+
def reject_constant(value):
|
|
17
|
+
raise ValueError('non-JSON constant')
|
|
18
|
+
value = json.loads(data, object_pairs_hook=unique_fields, parse_constant=reject_constant)
|
|
19
|
+
if not isinstance(value, dict):
|
|
20
|
+
raise ValueError('invalid identity document')
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def schema_one(value):
|
|
25
|
+
return not isinstance(value, bool) and value == 1
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def same_toolchain(value):
|
|
29
|
+
return isinstance(value, dict) and all(value.get(k) == v for k, v in TOOLCHAIN.items())
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def ordinary_document(entry):
|
|
33
|
+
return bool(entry and entry['kind'] == 'blob' and entry['mode'] in ('100644', '100755')
|
|
34
|
+
and entry['size'] <= READ_LIMIT)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def tree_identity(history, root_entries):
|
|
38
|
+
"""Validate actual nonrecursive root entries, including empty Git trees."""
|
|
39
|
+
roots = {e['path']: e for e in root_entries if e['path'] in FIELDS}
|
|
40
|
+
entry = roots.get('edition.json')
|
|
41
|
+
if entry is None:
|
|
42
|
+
return None, frozenset()
|
|
43
|
+
|
|
44
|
+
def read(entry):
|
|
45
|
+
if not ordinary_document(entry):
|
|
46
|
+
raise ValueError('unreadable identity document')
|
|
47
|
+
return history.blob(entry['oid'])
|
|
48
|
+
|
|
49
|
+
# An unreadable root cannot establish whether the edition opts in. It must
|
|
50
|
+
# not silently become an absent/legacy edition, even when Git omits it from
|
|
51
|
+
# the recursive file inventory. Ordinary legacy JSON keeps its old rules.
|
|
52
|
+
try:
|
|
53
|
+
data = read(entry)
|
|
54
|
+
except (ValueError, TypeError):
|
|
55
|
+
return None, frozenset({'edition.json'})
|
|
56
|
+
try:
|
|
57
|
+
recipe = document(data)
|
|
58
|
+
except (ValueError, TypeError, UnicodeError):
|
|
59
|
+
# Malformed opt-ins cannot become ordinary, permissive metadata. Other
|
|
60
|
+
# legacy malformed documents keep the existing scanner behaviour.
|
|
61
|
+
opted = re.search(r'\bidentityKind\b', decoded_text(data))
|
|
62
|
+
return None, frozenset({'edition.json'} if opted else ())
|
|
63
|
+
if 'identityKind' not in recipe:
|
|
64
|
+
return None, frozenset()
|
|
65
|
+
try:
|
|
66
|
+
if recipe['identityKind'] != 'project':
|
|
67
|
+
raise ValueError('unknown identity kind')
|
|
68
|
+
result = subprocess.run(['node', str(VALIDATOR)], input=data, capture_output=True, timeout=15)
|
|
69
|
+
canonical = result.stdout.decode('ascii')
|
|
70
|
+
if result.returncode or not re.fullmatch(r'[0-9a-f]{64}', canonical):
|
|
71
|
+
raise ValueError('invalid recipe')
|
|
72
|
+
except (OSError, ValueError, UnicodeError, subprocess.TimeoutExpired):
|
|
73
|
+
return None, frozenset({'edition.json'})
|
|
74
|
+
invalid = set()
|
|
75
|
+
for name in ('sources.lock.json', 'rer-project.json'):
|
|
76
|
+
if name not in roots:
|
|
77
|
+
continue
|
|
78
|
+
try:
|
|
79
|
+
value = document(read(roots[name]))
|
|
80
|
+
if value.get(FIELDS[name]) != recipe['id']:
|
|
81
|
+
raise ValueError('inconsistent project identity')
|
|
82
|
+
if name == 'sources.lock.json':
|
|
83
|
+
if not schema_one(value.get('schemaVersion')) or not same_toolchain(value.get('toolchain')) or value.get('manifest') != canonical:
|
|
84
|
+
raise ValueError('inconsistent source lock')
|
|
85
|
+
elif not schema_one(value.get('schema')):
|
|
86
|
+
raise ValueError('invalid project metadata')
|
|
87
|
+
except (ValueError, TypeError, UnicodeError):
|
|
88
|
+
invalid.add(name)
|
|
89
|
+
return (None if invalid else recipe['id']), frozenset(invalid)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Recognize source declarations, never ordinary repository mentions."""
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from github_refs import URL, FULL, normalize
|
|
5
|
+
|
|
6
|
+
MANIFESTS = ('edition.json', 'sources.lock.json')
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def source_key(value):
|
|
10
|
+
if not isinstance(value, dict):
|
|
11
|
+
return None
|
|
12
|
+
repository = value.get('repository')
|
|
13
|
+
revision = value.get('revision', value.get('commit'))
|
|
14
|
+
if not isinstance(repository, str) or not isinstance(revision, str) or not re.fullmatch(r'[0-9a-f]{40}|[0-9a-f]{64}', revision):
|
|
15
|
+
return None
|
|
16
|
+
if '://' in repository and repository.split('://', 1)[0].lower() not in ('http', 'https', 'git', 'ssh', 'git+http', 'git+https', 'git+ssh'):
|
|
17
|
+
return None
|
|
18
|
+
match = URL.fullmatch(repository)
|
|
19
|
+
repo = normalize(match.group(1)) if match else normalize(repository) if FULL.fullmatch(repository) else None
|
|
20
|
+
return (repo, revision) if repo else None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def declarations(document):
|
|
24
|
+
if not isinstance(document, dict):
|
|
25
|
+
return
|
|
26
|
+
pending = [document.get('parent')]
|
|
27
|
+
components = document.get('components', [])
|
|
28
|
+
if isinstance(components, list):
|
|
29
|
+
pending.extend(c.get('source') for c in components if isinstance(c, dict))
|
|
30
|
+
while pending:
|
|
31
|
+
value = pending.pop()
|
|
32
|
+
if not isinstance(value, dict):
|
|
33
|
+
continue
|
|
34
|
+
if source_key(value):
|
|
35
|
+
yield value
|
|
36
|
+
pending.append(value.get('origin'))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def declared_keys(data):
|
|
40
|
+
try:
|
|
41
|
+
return {source_key(value) for value in declarations(json.loads(data))}
|
|
42
|
+
except (ValueError, TypeError):
|
|
43
|
+
return set()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def copy_record(document, declared):
|
|
47
|
+
"""Canonical copy inventories bind the acquisition identity to declared pins."""
|
|
48
|
+
copies = document.get('copies')
|
|
49
|
+
if document.get('schemaVersion') != 1 or not isinstance(copies, list) or not copies:
|
|
50
|
+
return False
|
|
51
|
+
for copy in copies:
|
|
52
|
+
if not isinstance(copy, dict) or source_key(dict(repository=document.get('repository'), revision=copy.get('revision'))) not in declared:
|
|
53
|
+
return False
|
|
54
|
+
path, origin = copy.get('path'), copy.get('originPath')
|
|
55
|
+
if not all(isinstance(value, str) and value and not value.startswith('/') and '..' not in value.split('/') for value in (path, origin)):
|
|
56
|
+
return False
|
|
57
|
+
files, modes = copy.get('files'), copy.get('modes')
|
|
58
|
+
if not isinstance(files, dict) or not files or not isinstance(modes, dict) or files.keys() != modes.keys():
|
|
59
|
+
return False
|
|
60
|
+
if any(not (name == path or name.startswith(path + '/')) or not isinstance(value, str) or not re.fullmatch(r'[0-9a-f]{64}', value) or modes[name] not in ('100644', '100755') for name, value in files.items()):
|
|
61
|
+
return False
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def provenance_text(text, path, declared=frozenset()):
|
|
66
|
+
snapshot = re.fullmatch(r'\.edition/sources/[0-9a-f]{64}\.json', path)
|
|
67
|
+
copied = path.rsplit('/', 1)[-1].lower() in ('provenance.json', 'origin.json')
|
|
68
|
+
if path not in MANIFESTS and not snapshot and not copied and not path.endswith('.json'):
|
|
69
|
+
return text, False
|
|
70
|
+
try:
|
|
71
|
+
document = json.loads(text)
|
|
72
|
+
except ValueError:
|
|
73
|
+
return text, False
|
|
74
|
+
if not isinstance(document, dict):
|
|
75
|
+
return text, False
|
|
76
|
+
if copy_record(document, declared):
|
|
77
|
+
document['repository'] = ''
|
|
78
|
+
return json.dumps(document), True
|
|
79
|
+
candidates = list(declarations(document)) if path in MANIFESTS else []
|
|
80
|
+
if snapshot and isinstance(document.get('files'), dict) and source_key(document.get('origin')) in declared:
|
|
81
|
+
candidates.append(document['origin'])
|
|
82
|
+
hashes = document.get('files')
|
|
83
|
+
copies = document.get('copies')
|
|
84
|
+
mapped = isinstance(copies, dict) and copies and all(isinstance(k, str) and isinstance(v, str) and k and v for k, v in copies.items())
|
|
85
|
+
hashed = isinstance(hashes, dict) and hashes and all(isinstance(h, str) and re.fullmatch(r'[0-9a-f]{64}', h) for h in hashes.values())
|
|
86
|
+
selected = isinstance(document.get('path'), str) and bool(document['path'])
|
|
87
|
+
if copied and source_key(document) in declared and (mapped or hashed or selected):
|
|
88
|
+
candidates.append(document)
|
|
89
|
+
for value in candidates:
|
|
90
|
+
value['repository'] = ''
|
|
91
|
+
return (json.dumps(document) if candidates else text), bool(candidates)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Git object IDs at declared module-provenance locations, never whole files."""
|
|
2
|
+
from json_identity import GIT_REVISION
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def module_pins(path, document):
|
|
6
|
+
if not isinstance(document, dict):
|
|
7
|
+
return
|
|
8
|
+
if path == 'base/PROVENANCE.json':
|
|
9
|
+
maps = [(('moduleCommits',), document.get('moduleCommits'))]
|
|
10
|
+
elif path in ('edition.json', 'sources.lock.json'):
|
|
11
|
+
if type(document.get('schemaVersion')) is not int or document['schemaVersion'] != 1:
|
|
12
|
+
return
|
|
13
|
+
components = document.get('components')
|
|
14
|
+
if not isinstance(components, list):
|
|
15
|
+
return
|
|
16
|
+
maps = []
|
|
17
|
+
for index, component in enumerate(components):
|
|
18
|
+
source = component.get('source') if isinstance(component, dict) else None
|
|
19
|
+
if not isinstance(source, dict):
|
|
20
|
+
continue
|
|
21
|
+
suffix = () if source.get('kind') == 'git' else ('origin',) if source.get('kind') == 'local' else None
|
|
22
|
+
if suffix is None:
|
|
23
|
+
continue
|
|
24
|
+
origin = source if not suffix else source.get('origin')
|
|
25
|
+
if isinstance(origin, dict):
|
|
26
|
+
maps.append((('components', index, 'source', *suffix, 'moduleRevisions'), origin.get('moduleRevisions')))
|
|
27
|
+
else:
|
|
28
|
+
return
|
|
29
|
+
for pointer, pins in maps:
|
|
30
|
+
if isinstance(pins, dict):
|
|
31
|
+
for module, revision in pins.items():
|
|
32
|
+
if module and isinstance(revision, str) and GIT_REVISION.fullmatch(revision):
|
|
33
|
+
yield (*pointer, module)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""A public base is a branch of an explicitly named remote, never a local ref.
|
|
2
|
+
|
|
3
|
+
The declared base decides which history counts as already public, so it must
|
|
4
|
+
come from the remote itself: `<url>#<branch>` is fetched into an isolated
|
|
5
|
+
namespace and local branches, HEAD, tags and commit IDs are refused.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
|
|
11
|
+
NAMESPACE = 'refs/publish-check/public/'
|
|
12
|
+
URL = re.compile(r'(?:(?:https|ssh|git|file)://[^\s/#]*/[^\s#]*|[A-Za-z0-9._-]+@[A-Za-z0-9.-]+:[^\s#]+|/[^\s#]*)')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PublicBaseError(Exception):
|
|
16
|
+
"""Carries a stable, value-free reason code."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, code):
|
|
19
|
+
super().__init__(code)
|
|
20
|
+
self.code = code
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse(spec, repo):
|
|
24
|
+
"""Return (url, branch) or refuse."""
|
|
25
|
+
url, separator, branch = spec.rpartition('#')
|
|
26
|
+
if not separator or not url or not branch:
|
|
27
|
+
raise PublicBaseError('public-base.not-remote-branch')
|
|
28
|
+
if not URL.fullmatch(url) or url.startswith('-') or any(ord(c) < 32 for c in spec):
|
|
29
|
+
raise PublicBaseError('public-base.not-remote-url')
|
|
30
|
+
if branch.startswith(('-', 'refs/')) or branch == 'HEAD':
|
|
31
|
+
raise PublicBaseError('public-base.not-branch-name')
|
|
32
|
+
if not valid_ref('refs/heads/' + branch):
|
|
33
|
+
raise PublicBaseError('public-base.not-branch-name')
|
|
34
|
+
local = url[len('file://'):] if url.startswith('file://') else (url if url.startswith('/') else None)
|
|
35
|
+
if local and same_repository(local, repo):
|
|
36
|
+
raise PublicBaseError('public-base.is-candidate-repository')
|
|
37
|
+
return url, branch
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def valid_ref(name):
|
|
41
|
+
try:
|
|
42
|
+
return subprocess.run(['git', 'check-ref-format', name], capture_output=True, timeout=10).returncode == 0
|
|
43
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def common_dir(path, env):
|
|
48
|
+
result = subprocess.run(['git', '-C', path, 'rev-parse', '--path-format=absolute', '--git-common-dir'],
|
|
49
|
+
env=env, capture_output=True, timeout=10)
|
|
50
|
+
return os.path.realpath(os.fsdecode(result.stdout.strip())) if result.returncode == 0 else None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def same_repository(path, repo, env=None):
|
|
54
|
+
env = env or {k: v for k, v in os.environ.items() if not k.startswith('GIT_')}
|
|
55
|
+
try:
|
|
56
|
+
remote = common_dir(path, env)
|
|
57
|
+
return remote is not None and remote == common_dir(repo, env)
|
|
58
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
59
|
+
raise PublicBaseError('public-base.fetch-failed') from None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def display(url):
|
|
63
|
+
return re.sub(r'^([a-z]+://)[^/@]*@', r'\1', url)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Public-tree and message hygiene; findings never include matched values."""
|
|
2
|
+
from collections import Counter
|
|
3
|
+
import re
|
|
4
|
+
from github_refs import REPOSITORY_HOSTS, references
|
|
5
|
+
from destination import RULE as PENDING_DESTINATION
|
|
6
|
+
from hygiene_text import decoded_text
|
|
7
|
+
from provenance import provenance_text
|
|
8
|
+
from hygiene_config import EMAIL, LOCAL_PATH, public_email
|
|
9
|
+
from font_licence import without_required_notices
|
|
10
|
+
from trust_bundle import anchor_values
|
|
11
|
+
URI_AUTHORITY = re.compile(r'''(?i)(?a:(?P<scheme>[a-z][a-z0-9+.-]*:))?//(?P<authority>[^/?#\s<>"']+)''')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def email_userinfo(match):
|
|
15
|
+
userinfo, _, host = match['authority'].rpartition('@')
|
|
16
|
+
# Without a scheme, arbitrary //text can also be a source comment.
|
|
17
|
+
if not match['scheme'] and host.lower().split(':', 1)[0] not in REPOSITORY_HOSTS:
|
|
18
|
+
return match.group()
|
|
19
|
+
# Keep actual email-valued userinfo, including a decoded %40, while
|
|
20
|
+
# excluding the host delimiter in an ordinary transport username@host.
|
|
21
|
+
return userinfo
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def private_emails(text, config):
|
|
25
|
+
# URL paths, queries and fragments remain visible to email inspection.
|
|
26
|
+
email_text = URI_AUTHORITY.sub(email_userinfo, without_required_notices(text))
|
|
27
|
+
email_text = re.sub(r'(?i)\b(?a:git@github\.com:)(?=[A-Za-z0-9])', '', email_text)
|
|
28
|
+
version = r'(?:v?[0-9]+(?:\.[0-9]+)*(?:-[A-Za-z0-9-]+)?|[a-f0-9]{40})(?![A-Za-z0-9_.-])'
|
|
29
|
+
email_text = re.sub(r'(?m)^\s*-?\s*uses:\s*[\"\']?[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@[^\s\"\']+', '', email_text)
|
|
30
|
+
email_text = re.sub(r'(?<![A-Za-z0-9])(?:@[A-Za-z0-9_.-]+/)?[A-Za-z0-9_.-]+(?:/[A-Za-z0-9_.-]+)?@' + version, '', email_text)
|
|
31
|
+
return [match.group() for match in EMAIL.finditer(email_text) if not public_email(match.group(), config)]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def certificate_metadata(data, bundle, emails, config):
|
|
35
|
+
# Every match lies inside a verified trust-anchor subject value: none
|
|
36
|
+
# remains once those values are blanked, and each is one of theirs.
|
|
37
|
+
spans = anchor_values(data, bundle)
|
|
38
|
+
if not spans:
|
|
39
|
+
return False
|
|
40
|
+
blanked = bytearray(data)
|
|
41
|
+
for start, end in spans:
|
|
42
|
+
blanked[start:end] = b' ' * (end - start)
|
|
43
|
+
if private_emails(decoded_text(bytes(blanked)), config):
|
|
44
|
+
return False
|
|
45
|
+
inside = Counter(match.group() for start, end in spans for match in EMAIL.finditer(decoded_text(data[start:end])))
|
|
46
|
+
return not Counter(emails) - inside
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def inspect(data, path, config, resolver):
|
|
50
|
+
return inspect_text(data, path, config, resolver)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def inspect_text(data, path, config, resolver):
|
|
54
|
+
# NUL removal covers UTF-16/32 ASCII content, matching the secret scanner.
|
|
55
|
+
text = decoded_text(data)
|
|
56
|
+
rules = set()
|
|
57
|
+
if LOCAL_PATH.search(text):
|
|
58
|
+
rules.add('hygiene.local-path')
|
|
59
|
+
if any(pattern.search(text) for pattern in config['internalReferencePatterns']):
|
|
60
|
+
rules.add('hygiene.internal-reference')
|
|
61
|
+
if '@' in text:
|
|
62
|
+
emails = private_emails(text, config)
|
|
63
|
+
bundle = config.get('trustBundles', {}).get(path)
|
|
64
|
+
if emails and not (bundle and certificate_metadata(data, bundle, emails, config)):
|
|
65
|
+
rules.add('hygiene.email')
|
|
66
|
+
ref_text, provenance = provenance_text(text, path, config.get('declaredSources', frozenset()))
|
|
67
|
+
for repository in references(ref_text, path, config.get('localPaths', frozenset()), config.get('projectIdentity')):
|
|
68
|
+
# A future source identity does not assert current GitHub visibility.
|
|
69
|
+
# Explicit release destinations still take the strict visibility path.
|
|
70
|
+
if (repository in config.get('intendedPublicRepositories', frozenset()) and
|
|
71
|
+
repository != config.get('pendingDestination')):
|
|
72
|
+
continue
|
|
73
|
+
state = resolver.resolve(repository)
|
|
74
|
+
if state == 'missing' and repository == config.get('pendingDestination'):
|
|
75
|
+
rules.add(PENDING_DESTINATION)
|
|
76
|
+
elif state in ('private', 'missing'):
|
|
77
|
+
rules.add('hygiene.github-' + state)
|
|
78
|
+
elif state != 'public':
|
|
79
|
+
rules.add('scan.github-incomplete')
|
|
80
|
+
return rules, provenance
|