@runeditrun/rer 0.0.0-stage → 0.1.0
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 +154 -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 +177 -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 +155 -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 +129 -0
- package/global/launch/facts.mjs +93 -0
- package/global/launch/failure.mjs +5 -0
- package/global/launch/local-defaults.mjs +4 -0
- package/global/launch/prepare.mjs +61 -0
- package/global/launch/render.mjs +44 -0
- package/global/launch/state.mjs +97 -0
- package/global/launch/steps.mjs +81 -0
- package/global/launch/worker.mjs +43 -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,23 @@
|
|
|
1
|
+
"""The copyright notice at the top of a SIL Open Font License text."""
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
# The licence text itself, not a path or a mention of the licence: its title
|
|
5
|
+
# line followed by sections only the full text carries.
|
|
6
|
+
TITLE = re.compile(r'(?im)^[ \t]*SIL OPEN FONT LICENSE[ \t]+Version[ \t]+1\.1\b.*$')
|
|
7
|
+
BODY = ('PERMISSION & CONDITIONS', 'Reserved Font Name', 'Font Software')
|
|
8
|
+
# An attribution line names a year after the word: "Copyright (c) 2014 Holder".
|
|
9
|
+
# Other prose that starts with Copyright, such as a contact line, stays scanned.
|
|
10
|
+
NOTICE = re.compile(r'(?im)^[ \t]*Copyright[ \t]+(?:(?:\(c\)|©)[ \t]*)?\d{4}\b.*$')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def without_required_notices(text):
|
|
14
|
+
"""Drop the notice lines above the licence title of a full OFL text.
|
|
15
|
+
|
|
16
|
+
The licence obliges a redistributor to keep these lines, so the address in
|
|
17
|
+
one is required attribution. Anything else in the file, and the same
|
|
18
|
+
notice in a file that is not an OFL text, stays visible.
|
|
19
|
+
"""
|
|
20
|
+
title = TITLE.search(text)
|
|
21
|
+
if not title or not all(part.lower() in text[title.end():].lower() for part in BODY):
|
|
22
|
+
return text
|
|
23
|
+
return NOTICE.sub('', text[:title.start()]) + text[title.start():]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Repository plumbing delegated to the installed Git, never decoded in Python."""
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import subprocess
|
|
5
|
+
from public_base import parse, display, PublicBaseError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InspectionError(Exception):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def git_environment():
|
|
13
|
+
env = {k: v for k, v in os.environ.items() if not k.startswith('GIT_')}
|
|
14
|
+
env.update(GIT_TERMINAL_PROMPT='0', GIT_NO_REPLACE_OBJECTS='1')
|
|
15
|
+
return env
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class History:
|
|
19
|
+
def __init__(self, repo, ref='HEAD', public_base=None):
|
|
20
|
+
self.repo = str(Path(repo).resolve())
|
|
21
|
+
self.env = git_environment()
|
|
22
|
+
self.public_commit = self.public_label = None
|
|
23
|
+
self.reader = None
|
|
24
|
+
self.commit = self.run('rev-parse', '--verify', '--end-of-options', ref + '^{commit}').decode().strip()
|
|
25
|
+
if self.run('rev-parse', '--is-shallow-repository').strip() != b'false':
|
|
26
|
+
raise InspectionError('history.shallow')
|
|
27
|
+
if public_base is not None:
|
|
28
|
+
url, branch = parse(public_base, self.repo)
|
|
29
|
+
try:
|
|
30
|
+
self.run('fetch', '--quiet', '--no-tags', '--no-recurse-submodules', '--no-write-fetch-head', '--',
|
|
31
|
+
url, 'refs/heads/' + branch + ':refs/publish-check/public/base')
|
|
32
|
+
self.public_commit = self.run('rev-parse', 'refs/publish-check/public/base^{commit}').decode().strip()
|
|
33
|
+
self.public_label = display(url) + '#' + branch
|
|
34
|
+
except InspectionError:
|
|
35
|
+
raise PublicBaseError('public-base.fetch-failed') from None
|
|
36
|
+
self.revisions = [self.commit, self.public_commit] if public_base else ['--all', self.commit]
|
|
37
|
+
self.commits = self.run('rev-list', *self.revisions).decode().splitlines()
|
|
38
|
+
self.public_commits = set(self.run('rev-list', self.public_commit).decode().splitlines()) if self.public_commit else set()
|
|
39
|
+
self.trees = {}
|
|
40
|
+
self.tree_ids = dict(line.split() for line in self.run('log', '--format=%H %T', *self.revisions).decode().splitlines())
|
|
41
|
+
self.reader = subprocess.Popen(['git', '-C', self.repo, 'cat-file', '--batch'],
|
|
42
|
+
env=self.env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
|
43
|
+
|
|
44
|
+
def run(self, *args):
|
|
45
|
+
try:
|
|
46
|
+
result = subprocess.run(['git', '-C', self.repo, '-c', 'core.quotePath=false', *args],
|
|
47
|
+
env=self.env, capture_output=True, timeout=120)
|
|
48
|
+
if result.returncode:
|
|
49
|
+
raise InspectionError('history.git-failed')
|
|
50
|
+
return result.stdout
|
|
51
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
52
|
+
raise InspectionError('history.git-unavailable') from None
|
|
53
|
+
|
|
54
|
+
def commit_tree(self, commit):
|
|
55
|
+
known = getattr(self, 'tree_ids', {}).get(commit)
|
|
56
|
+
return known.encode() if known else self.run('rev-parse', commit + '^{tree}').strip()
|
|
57
|
+
|
|
58
|
+
def entries(self, commit, recursive=True):
|
|
59
|
+
tree = self.commit_tree(commit).decode()
|
|
60
|
+
key = (tree, recursive)
|
|
61
|
+
if key not in self.trees:
|
|
62
|
+
args = ['ls-tree', '-z', '-l'] + (['-r'] if recursive else []) + [tree]
|
|
63
|
+
entries = []
|
|
64
|
+
for record in self.run(*args).split(b'\0'):
|
|
65
|
+
if not record:
|
|
66
|
+
continue
|
|
67
|
+
metadata, path = record.split(b'\t', 1)
|
|
68
|
+
mode, kind, oid, size = metadata.split()
|
|
69
|
+
entries.append(dict(path=os.fsdecode(path), mode=mode.decode(), kind=kind.decode(),
|
|
70
|
+
oid=oid.decode(), size=int(size) if size != b'-' else 0))
|
|
71
|
+
self.trees[key] = entries
|
|
72
|
+
return self.trees[key]
|
|
73
|
+
|
|
74
|
+
def root_entries(self, commit):
|
|
75
|
+
return self.entries(commit, False)
|
|
76
|
+
|
|
77
|
+
def blob(self, oid):
|
|
78
|
+
if not oid or any(c not in '0123456789abcdef' for c in oid):
|
|
79
|
+
raise InspectionError('history.invalid-object')
|
|
80
|
+
self.reader.stdin.write(oid.encode() + b'\n')
|
|
81
|
+
self.reader.stdin.flush()
|
|
82
|
+
header = self.reader.stdout.readline().split()
|
|
83
|
+
if len(header) != 3 or header[1] != b'blob':
|
|
84
|
+
raise InspectionError('history.missing-blob')
|
|
85
|
+
size = int(header[2])
|
|
86
|
+
data = self.reader.stdout.read(size)
|
|
87
|
+
if len(data) != size or self.reader.stdout.read(1) != b'\n':
|
|
88
|
+
raise InspectionError('history.missing-blob')
|
|
89
|
+
return data
|
|
90
|
+
|
|
91
|
+
def selected(self, path):
|
|
92
|
+
entry = next((e for e in self.entries(self.commit) if e['path'] == path), None)
|
|
93
|
+
return self.blob(entry['oid']) if entry and entry['kind'] == 'blob' else None
|
|
94
|
+
|
|
95
|
+
def messages(self):
|
|
96
|
+
records = self.run('log', '-z', '--format=%H%x00%B', *self.revisions).split(b'\0')
|
|
97
|
+
for index in range(0, len(records) - 1, 2):
|
|
98
|
+
if records[index]:
|
|
99
|
+
yield '@commit-message', records[index].decode().strip(), records[index + 1]
|
|
100
|
+
for record in self.run('for-each-ref', '--format=%(objecttype) %(objectname)', 'refs/tags').splitlines():
|
|
101
|
+
kind, oid = record.decode().split()
|
|
102
|
+
if kind == 'tag':
|
|
103
|
+
raw = self.run('cat-file', 'tag', oid)
|
|
104
|
+
yield '@tag-message', oid, raw.partition(b'\n\n')[2]
|
|
105
|
+
|
|
106
|
+
def close(self):
|
|
107
|
+
if self.reader:
|
|
108
|
+
self.reader.stdin.close()
|
|
109
|
+
self.reader.stdout.close()
|
|
110
|
+
try:
|
|
111
|
+
self.reader.wait(timeout=2)
|
|
112
|
+
except subprocess.TimeoutExpired:
|
|
113
|
+
self.reader.kill()
|
|
114
|
+
self.reader.wait()
|
|
115
|
+
self.reader = None
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Resolve publication references with gh's local credentials; retain booleans only."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
from repository_intent import declarations, commands
|
|
7
|
+
|
|
8
|
+
SLUG = r'[A-Za-z0-9][A-Za-z0-9_.-]{0,99}'
|
|
9
|
+
PAIR = SLUG + '/' + SLUG
|
|
10
|
+
URL_BOUNDARY = r'(?<![A-Za-z0-9_./@+-])'
|
|
11
|
+
TRANSPORT = r'(?:[a-z][a-z0-9+.-]*://|(?<!:)//)'
|
|
12
|
+
USERINFO = r'''(?:[^/\s@"'<>`]+@)?'''
|
|
13
|
+
PORT = r'(?::[0-9]+)?'
|
|
14
|
+
REPOSITORY_HOSTS = {'github.com': '/', 'www.github.com': '/', 'ssh.github.com': '/',
|
|
15
|
+
'raw.githubusercontent.com': '/', 'api.github.com': '/repos/',
|
|
16
|
+
'codeload.github.com': '/'}
|
|
17
|
+
REPOSITORY_HOST = '(?:' + '|'.join('(?a:' + re.escape(host) + ')' + PORT + route for host, route in REPOSITORY_HOSTS.items()) + ')'
|
|
18
|
+
# Embedded patterns retain ASCII scheme/host folding without changing the
|
|
19
|
+
# surrounding text and repository-path recognition used by privacy inspection.
|
|
20
|
+
URL = re.compile(r'(?i)' + URL_BOUNDARY + r'(?:(?a:' + TRANSPORT + ')' + USERINFO + REPOSITORY_HOST +
|
|
21
|
+
r'|(?a:git@github\.com:))(' + PAIR + r')(?![A-Za-z0-9_.-])')
|
|
22
|
+
FULL = re.compile(PAIR + r'\Z')
|
|
23
|
+
METADATA = re.compile(r'(?i)(?:^|/)(?:[^/]+\.(?:json|ya?ml|toml)|\.gitmodules)$')
|
|
24
|
+
# GitHub documents these web auth endpoints, not repository namespaces.
|
|
25
|
+
SERVICE_URL = re.compile(r'(?i)' + URL_BOUNDARY + r'(?a:https?://|(?<!:)//)' + USERINFO +
|
|
26
|
+
r'(?a:(?:www\.)?github\.com)' + PORT +
|
|
27
|
+
r'''/(?:login/oauth/(?:authorize|access_token)|login/device(?:/code)?)(?=[?#\s"'`<>)]|$)''')
|
|
28
|
+
# Only the complete web endpoint is a service; transports and repository
|
|
29
|
+
# declarations retain their original repository meaning.
|
|
30
|
+
SPONSORS_URL = re.compile(URL_BOUNDARY + r'(?a:https?://|(?<!:)//)' + USERINFO +
|
|
31
|
+
r'(?a:(?:www\.)?github\.com)' + PORT +
|
|
32
|
+
r'''/(?-i:sponsors)/(?a:[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?)/?(?=[?#\s"'`<>)]|$)''', re.I)
|
|
33
|
+
FILE_SUFFIX = re.compile(r'(?i)\.(?:xml|json|ya?ml|toml|[cm]?[jt]sx?|md|txt|html|css|sql|png|jpe?g|svg|zip|csv|pdf|docx|xlsx|rer)$')
|
|
34
|
+
DEPENDENCY = re.compile(r'(?i)\bgithub:(' + PAIR + r')(?![A-Za-z0-9_./-])')
|
|
35
|
+
ACTION = re.compile(r'''(?im)^\s*(?:-\s*)?uses\s*:\s*["']?(''' + PAIR + r''')(?:/[A-Za-z0-9_./-]+)?@[^\s"']+''')
|
|
36
|
+
REPOSITORY_FIELD = re.compile(r'''(?im)(?<![\w$])["'`]?(?:repository|repo|github|sourceRepository)["'`]?\s*[:=]\s*["'`]?(''' + PAIR + r''')(?![A-Za-z0-9_./-])''')
|
|
37
|
+
# A code span such as `dist/client` names a path in the project, not an owner/repo.
|
|
38
|
+
LOCAL_DIRECTORIES = frozenset({'dist', 'src', 'scripts', 'public', 'tests', 'docs', 'exports', 'node_modules', '.rer', '.seed'})
|
|
39
|
+
SOURCE = re.compile(r'(?i)\.[cm]?[jt]sx?$')
|
|
40
|
+
TYPED_REPOSITORY = re.compile(r'''(?im)(?<![\w$])(?:repository|repo|github|sourceRepository)\s*:\s*string\s*=\s*["'`](''' + PAIR + r''')["'`]''')
|
|
41
|
+
DECLARED_SHORTHAND = re.compile(r'''(?im)(?<![\w$])["'`]?(?:url|fullURL)["'`]?\s*[:=]\s*["'`](''' + PAIR + r''')["'`]''')
|
|
42
|
+
REPOSITORY_ROUTE = re.compile(r'/(?:tree|blob|issues|pull|pulls|commit|commits|releases|actions|wiki|discussions|compare|tags)(?:/|$)')
|
|
43
|
+
SHORTHAND = PAIR + r'(?:[/?#][^\s`<>]*)?'
|
|
44
|
+
REPOSITORY_CONTEXT = r'github|repository|repo|source|upstream|downstream|fork(?:ed)?(?:\s+(?:from|of|into))?|clone(?:d)?(?:\s+from)?|gh\s+repo\s+(?:clone|view|fork)'
|
|
45
|
+
MARKUP = r'[`*_~\[\]]*'
|
|
46
|
+
MARKDOWN_REFERENCE = re.compile(r'`(?P<code>' + SHORTHAND + r')`|(?<![A-Za-z])(?P<label>(?i:' + REPOSITORY_CONTEXT + r'))(?:\s+(?:repository|repo))?' + MARKUP + r'(?P<separator>\s*[:=|]\s*|\s+)(?P<markup>' + MARKUP + r')\s*(?P<reference>' + SHORTHAND + r')(?![A-Za-z0-9_./-])')
|
|
47
|
+
|
|
48
|
+
EXPLICIT_URL = re.compile(r'''(?im)["']?(?:repository|repo|github|sourceRepository)["']?\s*[:=]\s*(?:\{\s*["']?(?:url|fullURL)["']?\s*:\s*)?[`*_~\["']*\s*(''' + URL.pattern.replace('(?i)', '') + ')', re.I)
|
|
49
|
+
CONTEXT_URL = re.compile(r'(?i)(?<![A-Za-z])(?:' + REPOSITORY_CONTEXT + r')'
|
|
50
|
+
r'(?:\s+(?:repository|repo))?' + MARKUP + r'(?:\s*[:=|]\s*|\s+)'
|
|
51
|
+
+ r'''[`*_~\[\]\s"']*(?:''' + URL.pattern.replace('(?i)', '') + ')')
|
|
52
|
+
LINK_URL = re.compile(r'(?i)\[(?P<label>[^]\n]+)\]\(\s*(?P<url>' + URL.pattern.replace('(?i)', '') + ')')
|
|
53
|
+
REPOSITORY_LABEL = re.compile(r'(?i)\b(?:' + REPOSITORY_CONTEXT + r')\b')
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def explicit_references(text, path):
|
|
57
|
+
found = {normalize(m.group(2)) for m in EXPLICIT_URL.finditer(text)}
|
|
58
|
+
found.update(normalize(m.group(1)) for m in CONTEXT_URL.finditer(text))
|
|
59
|
+
for match in LINK_URL.finditer(text):
|
|
60
|
+
if REPOSITORY_LABEL.search(match['label']):
|
|
61
|
+
found.update(normalize(m.group(1)) for m in URL.finditer(match['url']))
|
|
62
|
+
for declaration in (*declarations(text), *commands(text)):
|
|
63
|
+
found.update(normalize(m.group(1)) for m in URL.finditer(declaration))
|
|
64
|
+
return found
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def normalize(value):
|
|
68
|
+
return value.lower().rstrip('.').removesuffix('.git')
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class RepositoryObject(dict):
|
|
72
|
+
"""Retain duplicate JSON fields when checking explicit reference context."""
|
|
73
|
+
def __init__(self, pairs):
|
|
74
|
+
super().__init__(pairs)
|
|
75
|
+
self.pairs = pairs
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def references(text, path, local_paths=frozenset(), project_identity=None):
|
|
79
|
+
repository_text = SERVICE_URL.sub('', text)
|
|
80
|
+
found = {normalize(m.group(1)) for m in URL.finditer(repository_text)
|
|
81
|
+
if not SPONSORS_URL.match(repository_text, m.start())}
|
|
82
|
+
# A URL in an explicit repository declaration has stronger context than
|
|
83
|
+
# its coincident service spelling. Never erase the original text first.
|
|
84
|
+
found.update(explicit_references(repository_text, path))
|
|
85
|
+
found.update(normalize(m.group(1)) for m in DEPENDENCY.finditer(text))
|
|
86
|
+
if path.lower().endswith(('.yml', '.yaml', '.md', '.markdown')):
|
|
87
|
+
found.update(normalize(m.group(1)) for m in ACTION.finditer(text))
|
|
88
|
+
# Repository fields avoid mistaking module IDs for repository references.
|
|
89
|
+
# Markdown slash prose needs an explicit code span or repository context.
|
|
90
|
+
if path.lower().endswith(('.md', '.markdown')) or path in ('@commit-message', '@tag-message'):
|
|
91
|
+
markdown = URL.sub('', text)
|
|
92
|
+
markdown = re.sub(r'\]\((?![A-Za-z][A-Za-z0-9+.-]*://)[^)]*\)', ']()', markdown)
|
|
93
|
+
for match in MARKDOWN_REFERENCE.finditer(markdown):
|
|
94
|
+
explicit = match['reference'] is not None
|
|
95
|
+
value = re.split(r'[?#]', match['reference'] or match['code'], maxsplit=1)[0]
|
|
96
|
+
# Underscores are also legal slug characters; remove a closing
|
|
97
|
+
# emphasis marker only when the reference has its matching opener.
|
|
98
|
+
emphasis = re.search(r'_+$', match['markup'] or '')
|
|
99
|
+
if emphasis and value.endswith(emphasis.group()):
|
|
100
|
+
value = value[:-len(emphasis.group())]
|
|
101
|
+
repository = '/'.join(value.split('/')[:2])
|
|
102
|
+
suffix = value[len(repository):]
|
|
103
|
+
if not explicit and (value in local_paths or repository in local_paths or FILE_SUFFIX.search(repository)
|
|
104
|
+
or repository.split('/')[0] in LOCAL_DIRECTORIES):
|
|
105
|
+
continue
|
|
106
|
+
# A plain multi-segment path is ambiguous; GitHub's repository
|
|
107
|
+
# routes, a labeled field or a GitHub command disambiguate it.
|
|
108
|
+
labeled = explicit and (re.search(r'[:=|]', match['separator']) or re.match(r'(?i)github|gh\s', match['label']))
|
|
109
|
+
if not suffix or REPOSITORY_ROUTE.match(suffix) or labeled:
|
|
110
|
+
found.add(normalize(repository))
|
|
111
|
+
elif METADATA.search(path) or SOURCE.search(path):
|
|
112
|
+
if path.lower().endswith('.json'):
|
|
113
|
+
try:
|
|
114
|
+
document = json.loads(text, object_pairs_hook=RepositoryObject)
|
|
115
|
+
except ValueError:
|
|
116
|
+
document = None
|
|
117
|
+
identity_field = {'edition.json': 'id', 'sources.lock.json': 'edition', 'rer-project.json': 'projectId'}.get(path.rsplit('/', 1)[-1])
|
|
118
|
+
identity = document.get(identity_field) if isinstance(document, dict) and identity_field else None
|
|
119
|
+
logical = path in ('edition.json', 'sources.lock.json', 'rer-project.json') and identity == project_identity
|
|
120
|
+
if isinstance(identity, str) and FULL.fullmatch(identity) and not logical:
|
|
121
|
+
found.add(normalize(identity))
|
|
122
|
+
def visit(value):
|
|
123
|
+
if isinstance(value, dict):
|
|
124
|
+
for key, item in value.pairs:
|
|
125
|
+
if key.lower() in ('repository', 'repo', 'github', 'sourcerepository'):
|
|
126
|
+
candidate = item.get('url') if isinstance(item, dict) else item
|
|
127
|
+
if isinstance(candidate, str) and FULL.fullmatch(candidate):
|
|
128
|
+
found.add(normalize(candidate))
|
|
129
|
+
def declared(item):
|
|
130
|
+
if isinstance(item, str):
|
|
131
|
+
found.update(normalize(m.group(1)) for m in URL.finditer(SERVICE_URL.sub('', item)))
|
|
132
|
+
elif isinstance(item, dict):
|
|
133
|
+
for _, child in item.pairs:
|
|
134
|
+
declared(child)
|
|
135
|
+
elif isinstance(item, list):
|
|
136
|
+
for child in item:
|
|
137
|
+
declared(child)
|
|
138
|
+
declared(item)
|
|
139
|
+
visit(item)
|
|
140
|
+
elif isinstance(value, list):
|
|
141
|
+
for item in value:
|
|
142
|
+
visit(item)
|
|
143
|
+
visit(document)
|
|
144
|
+
found.update(normalize(m.group(1)) for m in REPOSITORY_FIELD.finditer(text))
|
|
145
|
+
if SOURCE.search(path):
|
|
146
|
+
found.update(normalize(m.group(1)) for m in TYPED_REPOSITORY.finditer(text))
|
|
147
|
+
for declaration in declarations(text):
|
|
148
|
+
found.update(normalize(m.group(1)) for m in DECLARED_SHORTHAND.finditer(declaration))
|
|
149
|
+
return found
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def local_dependencies(data, path):
|
|
153
|
+
"""Cache only local-path memberships that can change this blob's meaning."""
|
|
154
|
+
if not path.lower().endswith(('.md', '.markdown')):
|
|
155
|
+
return frozenset()
|
|
156
|
+
from hygiene_text import decoded_text
|
|
157
|
+
result = set()
|
|
158
|
+
for match in MARKDOWN_REFERENCE.finditer(decoded_text(data)):
|
|
159
|
+
value = re.split(r'[?#]', match['reference'] or match['code'], maxsplit=1)[0]
|
|
160
|
+
result.update((value, '/'.join(value.split('/')[:2])))
|
|
161
|
+
return frozenset(result)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def response_object(pairs):
|
|
165
|
+
document = {}
|
|
166
|
+
for key, value in pairs:
|
|
167
|
+
if key in document:
|
|
168
|
+
raise ValueError('duplicate API response key')
|
|
169
|
+
document[key] = value
|
|
170
|
+
return document
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def response_constant(value):
|
|
174
|
+
raise ValueError('nonstandard API JSON constant')
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def visibility(output, exit_code):
|
|
178
|
+
headers, body = re.split(r'\r?\n\r?\n', output, maxsplit=1)
|
|
179
|
+
status = re.fullmatch(r'HTTP/(?:1\.[01]|2(?:\.0)?|3(?:\.0)?) (\d{3})(?: [^\r\n]*)?',
|
|
180
|
+
headers.splitlines()[0])
|
|
181
|
+
if not status:
|
|
182
|
+
return 'incomplete'
|
|
183
|
+
code = int(status.group(1))
|
|
184
|
+
document = json.loads(body, object_pairs_hook=response_object, parse_constant=response_constant)
|
|
185
|
+
if not isinstance(document, dict):
|
|
186
|
+
return 'incomplete'
|
|
187
|
+
if code == 200 and exit_code == 0 and type(document.get('private')) is bool:
|
|
188
|
+
return 'private' if document['private'] else 'public'
|
|
189
|
+
# gh exits 1 for HTTP errors. A header alone or an arbitrary error document
|
|
190
|
+
# is not evidence of a missing repository, even at the intended destination.
|
|
191
|
+
if (code == 404 and exit_code == 1 and document.get('message') == 'Not Found' and
|
|
192
|
+
document.get('status') == '404' and isinstance(document.get('documentation_url'), str) and
|
|
193
|
+
re.fullmatch(r'https://docs\.github\.com/rest/[^\s]+', document['documentation_url'])):
|
|
194
|
+
return 'missing'
|
|
195
|
+
return 'incomplete'
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class GitHubResolver:
|
|
199
|
+
def __init__(self, spool=None):
|
|
200
|
+
self.cache = {}
|
|
201
|
+
|
|
202
|
+
def resolve(self, repository):
|
|
203
|
+
if repository in self.cache:
|
|
204
|
+
return self.cache[repository]
|
|
205
|
+
# No disk cache, auth arguments, diagnostic output or response metadata.
|
|
206
|
+
# Disable gh debug logging even if inherited from the calling shell.
|
|
207
|
+
env = {k: v for k, v in os.environ.items() if k not in ('GH_DEBUG', 'DEBUG')}
|
|
208
|
+
env['GH_PROMPT_DISABLED'] = '1'
|
|
209
|
+
try:
|
|
210
|
+
response = subprocess.run(['gh', 'api', '--hostname', 'github.com', '--include', 'repos/' + repository],
|
|
211
|
+
env=env, capture_output=True, timeout=60)
|
|
212
|
+
output = response.stdout.decode('utf-8')
|
|
213
|
+
result = visibility(output, response.returncode)
|
|
214
|
+
except (OSError, subprocess.TimeoutExpired, ValueError, IndexError):
|
|
215
|
+
result = 'incomplete'
|
|
216
|
+
self.cache[repository] = result
|
|
217
|
+
return result
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Pinned Gitleaks is the only secret detection engine."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import tempfile
|
|
8
|
+
from git_history import InspectionError
|
|
9
|
+
|
|
10
|
+
VERSION = '8.30.1'
|
|
11
|
+
CONFIG = Path(__file__).with_name('gitleaks.toml')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def executable():
|
|
15
|
+
binary = shutil.which('gitleaks') or str(Path.home() / '.local/bin/gitleaks')
|
|
16
|
+
try:
|
|
17
|
+
version = subprocess.run([binary, 'version'], capture_output=True, timeout=10)
|
|
18
|
+
if version.returncode or version.stdout.decode().strip() != VERSION:
|
|
19
|
+
raise InspectionError('scan.gitleaks-version')
|
|
20
|
+
except OSError:
|
|
21
|
+
raise InspectionError('scan.gitleaks-unavailable') from None
|
|
22
|
+
return binary
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def inspect(binary, mode, repo=None, revisions=None, data=None, timeout=300, redact=True):
|
|
26
|
+
with tempfile.TemporaryDirectory(prefix='publish-gitleaks-') as directory:
|
|
27
|
+
report = Path(directory) / 'report.json'
|
|
28
|
+
report.touch(mode=0o600)
|
|
29
|
+
config = CONFIG
|
|
30
|
+
if mode == 'dir':
|
|
31
|
+
config = Path(directory) / 'directory.toml'
|
|
32
|
+
# Extending a config and defining a global allowlist replaces its
|
|
33
|
+
# global rules. Keep the maintained policy intact in both modes;
|
|
34
|
+
# append only the directory exclusions already used here.
|
|
35
|
+
config.write_text(CONFIG.read_text() + '\n[[allowlists]]\npaths = [\"(^|/)(\\\\.git|node_modules)(/|$)\"]\n')
|
|
36
|
+
args = [binary, mode, '--config', str(config), '--redact=' + ('100' if redact else '0'), '--no-banner', '--no-color',
|
|
37
|
+
'--ignore-gitleaks-allow', '--gitleaks-ignore-path', directory, '--report-format', 'json',
|
|
38
|
+
'--report-path', str(report), '--timeout', str(max(1, int(timeout)))]
|
|
39
|
+
if mode == 'git':
|
|
40
|
+
args += ['--log-opts=' + ' '.join(['--full-history', '-m', '--no-ext-diff', '--no-textconv', *revisions])]
|
|
41
|
+
if repo is not None:
|
|
42
|
+
args.append(repo)
|
|
43
|
+
env = {k: v for k, v in os.environ.items() if not k.startswith(('GITLEAKS_', 'GIT_'))}
|
|
44
|
+
env.update(GIT_NO_REPLACE_OBJECTS='1', GIT_TERMINAL_PROMPT='0')
|
|
45
|
+
try:
|
|
46
|
+
result = subprocess.run(args, input=data, capture_output=True, env=env, timeout=timeout)
|
|
47
|
+
if result.returncode not in (0, 1):
|
|
48
|
+
raise InspectionError('scan.gitleaks-failed')
|
|
49
|
+
# Native reports may contain raw matches for location-bound proof.
|
|
50
|
+
# The enclosing temporary directory is private; values never enter
|
|
51
|
+
# the caller-facing report or logs. Gitleaks replaces file mode.
|
|
52
|
+
report.chmod(0o600)
|
|
53
|
+
findings = json.loads(report.read_text())
|
|
54
|
+
if not isinstance(findings, list):
|
|
55
|
+
raise ValueError()
|
|
56
|
+
return findings
|
|
57
|
+
except subprocess.TimeoutExpired:
|
|
58
|
+
raise InspectionError('scan.deadline-exceeded') from None
|
|
59
|
+
except (OSError, ValueError):
|
|
60
|
+
raise InspectionError('scan.gitleaks-failed') from None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def rule(identifier):
|
|
64
|
+
if identifier in ('generic-api-key', 'hex-api-key'):
|
|
65
|
+
return 'secret.assignment'
|
|
66
|
+
if 'private-key' in identifier:
|
|
67
|
+
return 'secret.private-key'
|
|
68
|
+
if 'jwt' in identifier:
|
|
69
|
+
return 'secret.jwt'
|
|
70
|
+
if 'password' in identifier or 'connection-string' in identifier:
|
|
71
|
+
return 'secret.url-credentials'
|
|
72
|
+
return 'secret.provider-token'
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
title = "Base publication scan"
|
|
2
|
+
[extend]
|
|
3
|
+
useDefault = true
|
|
4
|
+
|
|
5
|
+
[[allowlists]]
|
|
6
|
+
description = "Explicit synthetic credential values; no exemption by file or directory"
|
|
7
|
+
regexes = ['''(?i)^(?:test|fixture|fake|example|dummy|placeholder|your)[_-]''', '''(?i)^(?:gh[pousr]_|sk-(?:proj-|ant-)?|glpat-|npm_|whsec_|rk_live_|sk_live_|hf_|xapp-)(?:test|fixture|fake|example|dummy|placeholder)[_-]''']
|
|
8
|
+
|
|
9
|
+
[[allowlists]]
|
|
10
|
+
description = "Explicit synthetic values used by redaction fixtures"
|
|
11
|
+
regexes = ['''(?i)^synthetic[_-]''']
|
|
12
|
+
|
|
13
|
+
[[rules]]
|
|
14
|
+
id = "private-key"
|
|
15
|
+
[[rules.allowlists]]
|
|
16
|
+
description = "A PEM-shaped fixture starts its body with a synthetic marker"
|
|
17
|
+
regexes = ['''(?i)^-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY(?: BLOCK)?-----(?:\s|\\[nr])+(?:test|fixture|fake|example|dummy|placeholder|synthetic)[_-]''']
|
|
18
|
+
|
|
19
|
+
[[rules.allowlists]]
|
|
20
|
+
description = "A private-key header literal without an attached body is not a stored key"
|
|
21
|
+
regexTarget = "match"
|
|
22
|
+
regexes = ['''(?i)^-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY(?: BLOCK)?-----(?:\s|\\[nr])*['"]''']
|
|
23
|
+
|
|
24
|
+
[[rules]]
|
|
25
|
+
id = "jwt"
|
|
26
|
+
[[rules.allowlists]]
|
|
27
|
+
description = "A JWT-shaped fixture explicitly marks the signature synthetic"
|
|
28
|
+
regexes = ['''\.(?:test|fixture|fake|example|dummy|placeholder|synthetic)[_-][a-zA-Z0-9_-]+$''']
|
|
29
|
+
|
|
30
|
+
# SHA-shaped credentials must not disappear because their random bytes contain
|
|
31
|
+
# an English stopword (for example dead/feed) or fall below generic entropy.
|
|
32
|
+
# Source/module identities still require the location-bound proof in Python.
|
|
33
|
+
[[rules]]
|
|
34
|
+
id = "hex-api-key"
|
|
35
|
+
description = "Hexadecimal credential assignment"
|
|
36
|
+
regex = '''(?i)[\w.-]{0,50}?(?:access|auth|(?-i:[Aa]pi|API)|credential|creds|key|passw(?:or)?d|secret|token)(?:[ \t\w.-]{0,20})[\s'"]{0,3}(?:=|>|:{1,3}=|\|\||:|=>|\?=|,)[\x60'"\s=]{0,5}([a-f0-9]{64}|[a-f0-9]{40})(?:[\x60'"\s;]|\\[nr]|$)'''
|
|
37
|
+
keywords = ["access", "auth", "api", "credential", "creds", "key", "passwd", "password", "secret", "token"]
|
|
38
|
+
|
|
39
|
+
[[rules]]
|
|
40
|
+
id = "generic-api-key"
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Scan current files and full history using native Git and pinned Gitleaks."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from git_history import History, InspectionError
|
|
4
|
+
from public_base import PublicBaseError
|
|
5
|
+
from gitleaks import executable, inspect as secrets, rule as secret_rule
|
|
6
|
+
from hygiene_config import CONFIG, load_config
|
|
7
|
+
from github_refs import GitHubResolver, local_dependencies
|
|
8
|
+
from destination import destination, INVALID
|
|
9
|
+
from env_values import load_hashes
|
|
10
|
+
from licenses import EXCEPTIONS, exceptions, license_rules, registry
|
|
11
|
+
from reviewed import REVIEWED, load_reviewed
|
|
12
|
+
from report import new_report, Findings, finish
|
|
13
|
+
from tree_scan import context, text_rules, working_files, working_regular
|
|
14
|
+
from content_identities import ContentIdentities, candidate as identity_candidate
|
|
15
|
+
from checksum_maps import ChecksumMaps
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def scan(repo, ref='HEAD', public_base=None, env_file=None, project=None, repository=None, *, cache_dir=None, timeout=300):
|
|
19
|
+
report = new_report(public_base is not None)
|
|
20
|
+
history = None
|
|
21
|
+
add = Findings(report, {}, frozenset()).add
|
|
22
|
+
try:
|
|
23
|
+
hashes = load_hashes(env_file)
|
|
24
|
+
report['guarantees']['environmentChecked'] = env_file is not None
|
|
25
|
+
binary = executable()
|
|
26
|
+
history = History(repo, ref, public_base)
|
|
27
|
+
report['source'] = dict(commit=history.commit, tree=history.commit_tree(history.commit).decode())
|
|
28
|
+
report['publicBase'] = dict(ref=history.public_label, commit=history.public_commit) if public_base else None
|
|
29
|
+
config = load_config(history.selected(CONFIG))
|
|
30
|
+
reviewed_bytes = history.selected(REVIEWED)
|
|
31
|
+
reviewed = load_reviewed(reviewed_bytes) if reviewed_bytes else {}
|
|
32
|
+
add = Findings(report, reviewed, hashes).add
|
|
33
|
+
try:
|
|
34
|
+
config['pendingDestination'] = destination(history, project, repository)
|
|
35
|
+
except (ValueError, TypeError, UnicodeError):
|
|
36
|
+
add(INVALID, 'edition.json', history.commit)
|
|
37
|
+
config['pendingDestination'] = None
|
|
38
|
+
recorded = registry() + exceptions(history.selected(EXCEPTIONS))
|
|
39
|
+
resolver = GitHubResolver()
|
|
40
|
+
public_blobs = {e['oid'] for c in history.public_commits for e in history.entries(c)}
|
|
41
|
+
report['history']['preExistingCommits'] = sorted(history.public_commits)
|
|
42
|
+
report['history']['newlyPublicCommits'] = [c for c in history.commits if c not in history.public_commits]
|
|
43
|
+
seen_trees, seen_blobs, inspected = set(), set(), {}
|
|
44
|
+
entries_by_commit, dependencies = {}, {}
|
|
45
|
+
release_license_good = not license_rules(history.selected('LICENSE'), recorded)
|
|
46
|
+
for commit in history.commits:
|
|
47
|
+
entries = history.entries(commit)
|
|
48
|
+
entries_by_commit[commit] = {e['path']: e for e in entries}
|
|
49
|
+
tree = history.commit_tree(commit).decode()
|
|
50
|
+
seen_trees.add(tree)
|
|
51
|
+
scope = 'release-tree' if commit == history.commit else 'pre-existing' if commit in history.public_commits else 'newly-public'
|
|
52
|
+
config_tree, invalid = context(history, commit, config)
|
|
53
|
+
for path in invalid:
|
|
54
|
+
add(INVALID if (project is not None or repository is not None) and path == 'edition.json' else 'scan.identity-invalid', path, commit, scope=scope)
|
|
55
|
+
license_entry = entries_by_commit[commit].get('LICENSE')
|
|
56
|
+
license_oid = license_entry['oid'] if license_entry and license_entry['mode'] in ('100644', '100755') else None
|
|
57
|
+
for rule in license_rules(history.blob(license_oid) if license_oid else None, recorded):
|
|
58
|
+
bucket = 'warnings' if rule == 'license.missing' and public_base and scope == 'newly-public' and release_license_good else None
|
|
59
|
+
add(rule, 'LICENSE', commit, license_oid, scope, bucket)
|
|
60
|
+
signature = (config_tree['declaredSources'], config_tree['projectIdentity'], frozenset(config_tree['trustBundles'].items()))
|
|
61
|
+
for entry in entries:
|
|
62
|
+
path, oid = entry['path'], entry['oid']
|
|
63
|
+
seen_blobs.add(oid)
|
|
64
|
+
if entry['kind'] != 'blob':
|
|
65
|
+
add('scan.submodule-uninspected', path, commit, oid, scope)
|
|
66
|
+
continue
|
|
67
|
+
pair = (path, oid)
|
|
68
|
+
if pair not in dependencies:
|
|
69
|
+
data = history.blob(oid)
|
|
70
|
+
dependencies[pair] = local_dependencies(data, path)
|
|
71
|
+
local = dependencies[pair] & config_tree['localPaths']
|
|
72
|
+
key = (path, oid, signature, local)
|
|
73
|
+
if key not in inspected:
|
|
74
|
+
inspected[key] = text_rules(history.blob(oid), path, config_tree, resolver, hashes)
|
|
75
|
+
rules, provenance = inspected[key]
|
|
76
|
+
entry_scope = 'pre-existing' if scope != 'release-tree' and oid in public_blobs else scope
|
|
77
|
+
for rule in rules:
|
|
78
|
+
add(rule, path, commit, oid, entry_scope)
|
|
79
|
+
if provenance:
|
|
80
|
+
add('provenance.declared-source', path, commit, oid, entry_scope)
|
|
81
|
+
report['history'].update(commits=len(history.commits), trees=len(seen_trees), blobs=len(seen_blobs))
|
|
82
|
+
selected_context, _ = context(history, history.commit, config)
|
|
83
|
+
working_paths = []
|
|
84
|
+
working_identities = {}
|
|
85
|
+
for path, data in working_files(Path(history.repo)):
|
|
86
|
+
working_paths.append(path)
|
|
87
|
+
target = Path(history.repo) / path
|
|
88
|
+
if identity_candidate(path) and not any(p.is_symlink() for p in [target, *target.parents]):
|
|
89
|
+
working_identities[path] = data
|
|
90
|
+
# Native hash-object is unnecessary here: unchanged current bytes use
|
|
91
|
+
# their known immutable identity; new bytes have no history identity.
|
|
92
|
+
entry = entries_by_commit[history.commit].get(path)
|
|
93
|
+
oid = entry['oid'] if entry and entry['kind'] == 'blob' and history.blob(entry['oid']) == data else None
|
|
94
|
+
rules, _ = text_rules(data, path, selected_context, resolver, hashes)
|
|
95
|
+
for rule in rules:
|
|
96
|
+
add(rule, path, history.commit, oid, 'working-tree')
|
|
97
|
+
if path == 'LICENSE':
|
|
98
|
+
for rule in license_rules(data, recorded):
|
|
99
|
+
add(rule, path, history.commit, oid, 'working-tree')
|
|
100
|
+
if not (Path(history.repo) / 'LICENSE').is_file():
|
|
101
|
+
add('license.missing', 'LICENSE', scope='working-tree')
|
|
102
|
+
identities = {'working-tree': ContentIdentities(working_identities,
|
|
103
|
+
lambda path: working_regular(Path(history.repo), path))}
|
|
104
|
+
checksums = {}
|
|
105
|
+
for mode in ('git', 'dir'):
|
|
106
|
+
for hit in secrets(binary, mode, history.repo, history.revisions, timeout=timeout, redact=False):
|
|
107
|
+
path, commit = hit.get('File', '.'), hit.get('Commit') or history.commit
|
|
108
|
+
if mode == 'dir' and Path(path).is_absolute():
|
|
109
|
+
path = Path(path).resolve().relative_to(Path(history.repo).resolve()).as_posix()
|
|
110
|
+
entry = entries_by_commit.get(commit, {}).get(path)
|
|
111
|
+
oid = entry['oid'] if entry and entry['kind'] == 'blob' else None
|
|
112
|
+
scope = 'working-tree' if mode == 'dir' else 'release-tree' if commit == history.commit else 'pre-existing' if oid in public_blobs else 'newly-public'
|
|
113
|
+
if hit['RuleID'] in ('generic-api-key', 'hex-api-key'):
|
|
114
|
+
identity_scope = 'working-tree' if mode == 'dir' else commit
|
|
115
|
+
scoped_entries = entries_by_commit.get(commit, {})
|
|
116
|
+
def read_regular(path, scoped_entries=scoped_entries):
|
|
117
|
+
entry = scoped_entries.get(path)
|
|
118
|
+
return (history.blob(entry['oid']), entry['mode']) if entry and entry['kind'] == 'blob' and entry['mode'] in ('100644', '100755') else None
|
|
119
|
+
if identity_scope not in checksums:
|
|
120
|
+
checksums[identity_scope] = ChecksumMaps(
|
|
121
|
+
working_paths if mode == 'dir' else scoped_entries,
|
|
122
|
+
(lambda path: working_regular(Path(history.repo), path)) if mode == 'dir' else read_regular)
|
|
123
|
+
if identity_candidate(path):
|
|
124
|
+
if identity_scope not in identities:
|
|
125
|
+
identities[identity_scope] = ContentIdentities({
|
|
126
|
+
p: history.blob(e['oid']) for p, e in scoped_entries.items()
|
|
127
|
+
if identity_candidate(p) and e['kind'] == 'blob' and e['mode'] in ('100644', '100755')}, read_regular)
|
|
128
|
+
if identities[identity_scope].matches(path, hit):
|
|
129
|
+
continue
|
|
130
|
+
else:
|
|
131
|
+
# Content-identity documents keep their own stricter proof.
|
|
132
|
+
held = working_regular(Path(history.repo), path) if mode == 'dir' else read_regular(path)
|
|
133
|
+
if held and checksums[identity_scope].matches(held[0], hit):
|
|
134
|
+
continue
|
|
135
|
+
add(secret_rule(hit['RuleID']), path, commit, oid, scope, scanner_rule=hit['RuleID'])
|
|
136
|
+
messages, lines = [], []
|
|
137
|
+
count = 1
|
|
138
|
+
all_paths = dict.fromkeys(working_paths, history.commit)
|
|
139
|
+
for commit, entries in entries_by_commit.items():
|
|
140
|
+
for path in entries:
|
|
141
|
+
all_paths.setdefault(path, commit)
|
|
142
|
+
for path, commit in sorted(all_paths.items()):
|
|
143
|
+
data = path.encode('utf-8', 'surrogateescape') + b'\n'
|
|
144
|
+
messages.append(data)
|
|
145
|
+
length = data.count(b'\n')
|
|
146
|
+
scope = 'working-tree' if path in working_paths else 'pre-existing' if commit in history.public_commits else 'newly-public'
|
|
147
|
+
lines.append((count, count + length, path, commit, scope))
|
|
148
|
+
count += length
|
|
149
|
+
for path, commit, data in history.messages():
|
|
150
|
+
report['history']['tagMessages' if path == '@tag-message' else 'commitMessages'] += 1
|
|
151
|
+
rules, _ = text_rules(data, path, selected_context, resolver, hashes)
|
|
152
|
+
scope = 'release-tree' if commit == history.commit else 'pre-existing' if commit in history.public_commits else 'newly-public'
|
|
153
|
+
for rule in rules:
|
|
154
|
+
add(rule, path, commit, scope=scope)
|
|
155
|
+
messages.append(data + b'\n')
|
|
156
|
+
length = (data + b'\n').count(b'\n')
|
|
157
|
+
lines.append((count, count + length, path, commit, scope))
|
|
158
|
+
count += length
|
|
159
|
+
for hit in secrets(binary, 'stdin', data=b''.join(messages), timeout=timeout):
|
|
160
|
+
line = hit.get('StartLine', 0)
|
|
161
|
+
location = next(((p, c, s) for start, end, p, c, s in lines if start <= line < end), ('@message', None, 'newly-public'))
|
|
162
|
+
add(secret_rule(hit['RuleID']), location[0], location[1], scope=location[2], scanner_rule=hit['RuleID'])
|
|
163
|
+
except PublicBaseError:
|
|
164
|
+
add('scan.public-base-rejected', '.')
|
|
165
|
+
except InspectionError as error:
|
|
166
|
+
if str(error).startswith('scan.'):
|
|
167
|
+
add(str(error), '.')
|
|
168
|
+
else:
|
|
169
|
+
add('scan.history-incomplete', '.')
|
|
170
|
+
report['findings'][-1]['reason'] = str(error)
|
|
171
|
+
except (OSError, ValueError, TypeError, KeyError, UnicodeError):
|
|
172
|
+
add('scan.configuration-incomplete', '.')
|
|
173
|
+
finally:
|
|
174
|
+
if history:
|
|
175
|
+
history.close()
|
|
176
|
+
return finish(report)
|