@m2-ai/lattice-cli 0.0.0-stage → 0.8.7
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/DISCLOSURE +1 -0
- package/README.md +118 -2
- package/bundles/expo-mobile/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/expo-mobile/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/expo-mobile/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-mobile/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/expo-mobile/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/expo-mobile/.claude/hooks/README.md +9 -0
- package/bundles/expo-mobile/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/expo-mobile/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/expo-mobile/.claude/rules/lattice-core.md +20 -0
- package/bundles/expo-mobile/.claude/settings.json +38 -0
- package/bundles/expo-mobile/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-mobile/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/expo-mobile/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/expo-mobile/.cursor/mcp.json +34 -0
- package/bundles/expo-mobile/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/expo-mobile/.cursor/rules.md +94 -0
- package/bundles/expo-mobile/.env.example +19 -0
- package/bundles/expo-mobile/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/expo-mobile/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/expo-mobile/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/expo-mobile/.github/copilot-instructions.md +37 -0
- package/bundles/expo-mobile/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/expo-mobile/.github/workflows/ci.yml +26 -0
- package/bundles/expo-mobile/.gitmessage +24 -0
- package/bundles/expo-mobile/.lattice/config.json +9 -0
- package/bundles/expo-mobile/.lattice/dependency-security.json +91 -0
- package/bundles/expo-mobile/.lattice/prompts/01-feature.md +36 -0
- package/bundles/expo-mobile/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/expo-mobile/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/expo-mobile/.lattice/prompts/04-test.md +31 -0
- package/bundles/expo-mobile/.lattice/prompts/05-polish.md +38 -0
- package/bundles/expo-mobile/.lattice/prompts/06-security.md +32 -0
- package/bundles/expo-mobile/.lattice/release-workflow.json +55 -0
- package/bundles/expo-mobile/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/expo-mobile/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/expo-mobile/.mcp.json +35 -0
- package/bundles/expo-mobile/AGENTS.md +58 -0
- package/bundles/expo-mobile/CLAUDE.md +75 -0
- package/bundles/expo-mobile/PROJECT_GUIDE.md +299 -0
- package/bundles/expo-mobile/README.md +39 -0
- package/bundles/expo-mobile/__tests__/home.test.tsx +22 -0
- package/bundles/expo-mobile/app/_layout.tsx +20 -0
- package/bundles/expo-mobile/app/index.tsx +72 -0
- package/bundles/expo-mobile/app.json +33 -0
- package/bundles/expo-mobile/components/ui/Button.tsx +75 -0
- package/bundles/expo-mobile/components/ui/Input.tsx +50 -0
- package/bundles/expo-mobile/components/ui/index.ts +2 -0
- package/bundles/expo-mobile/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/expo-mobile/docs/rules/codex.md +62 -0
- package/bundles/expo-mobile/eslint.config.js +7 -0
- package/bundles/expo-mobile/lib/supabase/client.ts +22 -0
- package/bundles/expo-mobile/lib/supabase/realtime.ts +10 -0
- package/bundles/expo-mobile/lib/supabase/storage.ts +6 -0
- package/bundles/expo-mobile/package.json +65 -0
- package/bundles/expo-mobile/plugins/with-lattice-commons-io.cjs +42 -0
- package/bundles/expo-mobile/prompts/01-feature-implementation.md +48 -0
- package/bundles/expo-mobile/prompts/02-bug-fix.md +43 -0
- package/bundles/expo-mobile/prompts/03-safe-refactor.md +38 -0
- package/bundles/expo-mobile/prompts/04-test-first.md +41 -0
- package/bundles/expo-mobile/prompts/05-ui-polish.md +44 -0
- package/bundles/expo-mobile/prompts/06-security-review.md +39 -0
- package/bundles/expo-mobile/prompts/quality.md +143 -0
- package/bundles/expo-mobile/supabase/migrations/001_initial_schema.sql +66 -0
- package/bundles/expo-mobile/tsconfig.json +21 -0
- package/bundles/expo-mobile/types/database.types.ts +76 -0
- package/bundles/expo-mobile/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/expo-supabase/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/expo-supabase/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/expo-supabase/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-supabase/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/expo-supabase/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/expo-supabase/.claude/hooks/README.md +9 -0
- package/bundles/expo-supabase/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/expo-supabase/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/expo-supabase/.claude/rules/lattice-core.md +20 -0
- package/bundles/expo-supabase/.claude/settings.json +38 -0
- package/bundles/expo-supabase/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-supabase/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/expo-supabase/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/expo-supabase/.cursor/mcp.json +34 -0
- package/bundles/expo-supabase/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/expo-supabase/.cursor/rules.md +94 -0
- package/bundles/expo-supabase/.env.example +12 -0
- package/bundles/expo-supabase/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/expo-supabase/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/expo-supabase/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/expo-supabase/.github/copilot-instructions.md +37 -0
- package/bundles/expo-supabase/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/expo-supabase/.github/workflows/ci.yml +26 -0
- package/bundles/expo-supabase/.gitmessage +24 -0
- package/bundles/expo-supabase/.lattice/config.json +9 -0
- package/bundles/expo-supabase/.lattice/dependency-security.json +91 -0
- package/bundles/expo-supabase/.lattice/prompts/01-feature.md +36 -0
- package/bundles/expo-supabase/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/expo-supabase/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/expo-supabase/.lattice/prompts/04-test.md +31 -0
- package/bundles/expo-supabase/.lattice/prompts/05-polish.md +38 -0
- package/bundles/expo-supabase/.lattice/prompts/06-security.md +32 -0
- package/bundles/expo-supabase/.lattice/release-workflow.json +55 -0
- package/bundles/expo-supabase/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/expo-supabase/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/expo-supabase/.mcp.json +35 -0
- package/bundles/expo-supabase/AGENTS.md +58 -0
- package/bundles/expo-supabase/CLAUDE.md +75 -0
- package/bundles/expo-supabase/PROJECT_GUIDE.md +201 -0
- package/bundles/expo-supabase/README.md +38 -0
- package/bundles/expo-supabase/__tests__/home.test.tsx +22 -0
- package/bundles/expo-supabase/app/_layout.tsx +14 -0
- package/bundles/expo-supabase/app/index.tsx +72 -0
- package/bundles/expo-supabase/app.json +33 -0
- package/bundles/expo-supabase/components/ui/Button.tsx +75 -0
- package/bundles/expo-supabase/components/ui/Input.tsx +50 -0
- package/bundles/expo-supabase/components/ui/index.ts +2 -0
- package/bundles/expo-supabase/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/expo-supabase/docs/rules/codex.md +62 -0
- package/bundles/expo-supabase/eslint.config.js +7 -0
- package/bundles/expo-supabase/lib/supabase/client.ts +22 -0
- package/bundles/expo-supabase/lib/supabase/realtime.ts +10 -0
- package/bundles/expo-supabase/lib/supabase/storage.ts +6 -0
- package/bundles/expo-supabase/package.json +60 -0
- package/bundles/expo-supabase/plugins/with-lattice-commons-io.cjs +42 -0
- package/bundles/expo-supabase/prompts/01-feature-implementation.md +8 -0
- package/bundles/expo-supabase/prompts/02-bug-fix.md +9 -0
- package/bundles/expo-supabase/prompts/03-safe-refactor.md +8 -0
- package/bundles/expo-supabase/prompts/04-test-first.md +7 -0
- package/bundles/expo-supabase/prompts/05-ui-polish.md +7 -0
- package/bundles/expo-supabase/prompts/06-security-review.md +7 -0
- package/bundles/expo-supabase/prompts/quality.md +143 -0
- package/bundles/expo-supabase/supabase/migrations/001_initial_schema.sql +66 -0
- package/bundles/expo-supabase/tsconfig.json +21 -0
- package/bundles/expo-supabase/types/database.types.ts +76 -0
- package/bundles/expo-supabase/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/nextjs-landing/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/nextjs-landing/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/nextjs-landing/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-landing/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/nextjs-landing/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/nextjs-landing/.claude/hooks/README.md +9 -0
- package/bundles/nextjs-landing/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/nextjs-landing/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/nextjs-landing/.claude/rules/lattice-core.md +20 -0
- package/bundles/nextjs-landing/.claude/settings.json +38 -0
- package/bundles/nextjs-landing/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-landing/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/nextjs-landing/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/nextjs-landing/.cursor/mcp.json +34 -0
- package/bundles/nextjs-landing/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/nextjs-landing/.cursor/rules.md +95 -0
- package/bundles/nextjs-landing/.env.example +24 -0
- package/bundles/nextjs-landing/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/nextjs-landing/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/nextjs-landing/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/nextjs-landing/.github/copilot-instructions.md +37 -0
- package/bundles/nextjs-landing/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/nextjs-landing/.github/workflows/ci.yml +26 -0
- package/bundles/nextjs-landing/.gitmessage +24 -0
- package/bundles/nextjs-landing/.lattice/config.json +9 -0
- package/bundles/nextjs-landing/.lattice/dependency-security.json +46 -0
- package/bundles/nextjs-landing/.lattice/prompts/01-feature.md +36 -0
- package/bundles/nextjs-landing/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/nextjs-landing/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/nextjs-landing/.lattice/prompts/04-test.md +31 -0
- package/bundles/nextjs-landing/.lattice/prompts/05-polish.md +38 -0
- package/bundles/nextjs-landing/.lattice/prompts/06-security.md +32 -0
- package/bundles/nextjs-landing/.lattice/release-workflow.json +55 -0
- package/bundles/nextjs-landing/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/nextjs-landing/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/nextjs-landing/.mcp.json +35 -0
- package/bundles/nextjs-landing/AGENTS.md +58 -0
- package/bundles/nextjs-landing/CLAUDE.md +76 -0
- package/bundles/nextjs-landing/PROJECT_GUIDE.md +216 -0
- package/bundles/nextjs-landing/README.md +39 -0
- package/bundles/nextjs-landing/app/api/example/[id]/route.ts +34 -0
- package/bundles/nextjs-landing/app/api/example/route.ts +24 -0
- package/bundles/nextjs-landing/app/api/health/route.ts +8 -0
- package/bundles/nextjs-landing/app/global-error.tsx +17 -0
- package/bundles/nextjs-landing/app/globals.css +55 -0
- package/bundles/nextjs-landing/app/layout.tsx +22 -0
- package/bundles/nextjs-landing/app/page.tsx +42 -0
- package/bundles/nextjs-landing/components/ui/button.tsx +39 -0
- package/bundles/nextjs-landing/components/ui/card.tsx +53 -0
- package/bundles/nextjs-landing/components/ui/index.ts +4 -0
- package/bundles/nextjs-landing/components/ui/input.tsx +23 -0
- package/bundles/nextjs-landing/components/ui/spinner.tsx +22 -0
- package/bundles/nextjs-landing/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/nextjs-landing/docs/rules/codex.md +62 -0
- package/bundles/nextjs-landing/eslint.config.mjs +16 -0
- package/bundles/nextjs-landing/instrumentation-client.ts +30 -0
- package/bundles/nextjs-landing/instrumentation.ts +12 -0
- package/bundles/nextjs-landing/jest.config.mjs +16 -0
- package/bundles/nextjs-landing/jest.setup.js +1 -0
- package/bundles/nextjs-landing/lib/utils.ts +6 -0
- package/bundles/nextjs-landing/next-env.d.ts +5 -0
- package/bundles/nextjs-landing/next.config.ts +15 -0
- package/bundles/nextjs-landing/package.json +48 -0
- package/bundles/nextjs-landing/postcss.config.mjs +7 -0
- package/bundles/nextjs-landing/prompts/01-feature-implementation.md +9 -0
- package/bundles/nextjs-landing/prompts/02-bug-fix.md +10 -0
- package/bundles/nextjs-landing/prompts/03-safe-refactor.md +8 -0
- package/bundles/nextjs-landing/prompts/04-test-first.md +8 -0
- package/bundles/nextjs-landing/prompts/05-ui-polish.md +8 -0
- package/bundles/nextjs-landing/prompts/06-security-review.md +7 -0
- package/bundles/nextjs-landing/prompts/quality.md +269 -0
- package/bundles/nextjs-landing/sentry.edge.config.ts +15 -0
- package/bundles/nextjs-landing/sentry.server.config.ts +15 -0
- package/bundles/nextjs-landing/tsconfig.json +42 -0
- package/bundles/nextjs-landing/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/nextjs-saas/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/nextjs-saas/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/nextjs-saas/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-saas/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/nextjs-saas/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/nextjs-saas/.claude/hooks/README.md +9 -0
- package/bundles/nextjs-saas/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/nextjs-saas/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/nextjs-saas/.claude/rules/lattice-core.md +20 -0
- package/bundles/nextjs-saas/.claude/settings.json +38 -0
- package/bundles/nextjs-saas/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-saas/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/nextjs-saas/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/nextjs-saas/.cursor/mcp.json +34 -0
- package/bundles/nextjs-saas/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/nextjs-saas/.cursor/rules.md +95 -0
- package/bundles/nextjs-saas/.env.example +48 -0
- package/bundles/nextjs-saas/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/nextjs-saas/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/nextjs-saas/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/nextjs-saas/.github/copilot-instructions.md +37 -0
- package/bundles/nextjs-saas/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/nextjs-saas/.github/workflows/ci.yml +26 -0
- package/bundles/nextjs-saas/.gitmessage +24 -0
- package/bundles/nextjs-saas/.lattice/config.json +9 -0
- package/bundles/nextjs-saas/.lattice/dependency-security.json +46 -0
- package/bundles/nextjs-saas/.lattice/prompts/01-feature.md +36 -0
- package/bundles/nextjs-saas/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/nextjs-saas/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/nextjs-saas/.lattice/prompts/04-test.md +31 -0
- package/bundles/nextjs-saas/.lattice/prompts/05-polish.md +38 -0
- package/bundles/nextjs-saas/.lattice/prompts/06-security.md +32 -0
- package/bundles/nextjs-saas/.lattice/prompts/quality.md +269 -0
- package/bundles/nextjs-saas/.lattice/release-workflow.json +55 -0
- package/bundles/nextjs-saas/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/nextjs-saas/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/nextjs-saas/.mcp.json +35 -0
- package/bundles/nextjs-saas/AGENTS.md +58 -0
- package/bundles/nextjs-saas/CLAUDE.md +76 -0
- package/bundles/nextjs-saas/PROJECT_GUIDE.md +279 -0
- package/bundles/nextjs-saas/README.md +42 -0
- package/bundles/nextjs-saas/app/(auth)/forgot-password/page.tsx +101 -0
- package/bundles/nextjs-saas/app/(auth)/layout.tsx +7 -0
- package/bundles/nextjs-saas/app/(auth)/login/page.tsx +108 -0
- package/bundles/nextjs-saas/app/(auth)/reset-password/page.tsx +135 -0
- package/bundles/nextjs-saas/app/(auth)/signup/page.tsx +144 -0
- package/bundles/nextjs-saas/app/(dashboard)/dashboard/page.tsx +80 -0
- package/bundles/nextjs-saas/app/(dashboard)/layout.tsx +49 -0
- package/bundles/nextjs-saas/app/api/billing/checkout/route.ts +47 -0
- package/bundles/nextjs-saas/app/api/billing/portal/route.ts +29 -0
- package/bundles/nextjs-saas/app/api/example/[id]/route.ts +93 -0
- package/bundles/nextjs-saas/app/api/example/route.ts +61 -0
- package/bundles/nextjs-saas/app/api/health/route.ts +8 -0
- package/bundles/nextjs-saas/app/api/stripe/webhook/route.ts +57 -0
- package/bundles/nextjs-saas/app/api/user/route.ts +49 -0
- package/bundles/nextjs-saas/app/auth/callback/route.ts +16 -0
- package/bundles/nextjs-saas/app/auth/signout/route.ts +10 -0
- package/bundles/nextjs-saas/app/global-error.tsx +17 -0
- package/bundles/nextjs-saas/app/globals.css +55 -0
- package/bundles/nextjs-saas/app/layout.tsx +22 -0
- package/bundles/nextjs-saas/app/page.tsx +84 -0
- package/bundles/nextjs-saas/components/ui/button.tsx +39 -0
- package/bundles/nextjs-saas/components/ui/card.tsx +53 -0
- package/bundles/nextjs-saas/components/ui/index.ts +4 -0
- package/bundles/nextjs-saas/components/ui/input.tsx +23 -0
- package/bundles/nextjs-saas/components/ui/spinner.tsx +22 -0
- package/bundles/nextjs-saas/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/nextjs-saas/docs/rules/codex.md +62 -0
- package/bundles/nextjs-saas/eslint.config.mjs +16 -0
- package/bundles/nextjs-saas/instrumentation-client.ts +30 -0
- package/bundles/nextjs-saas/instrumentation.ts +12 -0
- package/bundles/nextjs-saas/jest.config.mjs +16 -0
- package/bundles/nextjs-saas/jest.setup.js +1 -0
- package/bundles/nextjs-saas/lib/auth/password-recovery.ts +94 -0
- package/bundles/nextjs-saas/lib/stripe.ts +10 -0
- package/bundles/nextjs-saas/lib/supabase/admin.ts +14 -0
- package/bundles/nextjs-saas/lib/supabase/client.ts +8 -0
- package/bundles/nextjs-saas/lib/supabase/server.ts +27 -0
- package/bundles/nextjs-saas/lib/utils.ts +6 -0
- package/bundles/nextjs-saas/next-env.d.ts +5 -0
- package/bundles/nextjs-saas/next.config.ts +15 -0
- package/bundles/nextjs-saas/package.json +50 -0
- package/bundles/nextjs-saas/postcss.config.mjs +7 -0
- package/bundles/nextjs-saas/prompts/01-feature-implementation.md +92 -0
- package/bundles/nextjs-saas/prompts/02-bug-fix.md +78 -0
- package/bundles/nextjs-saas/prompts/03-safe-refactor.md +82 -0
- package/bundles/nextjs-saas/prompts/04-test-first.md +80 -0
- package/bundles/nextjs-saas/prompts/05-ui-polish.md +82 -0
- package/bundles/nextjs-saas/prompts/06-security-review.md +74 -0
- package/bundles/nextjs-saas/proxy.ts +61 -0
- package/bundles/nextjs-saas/sentry.edge.config.ts +15 -0
- package/bundles/nextjs-saas/sentry.server.config.ts +15 -0
- package/bundles/nextjs-saas/supabase/migrations/001_initial_schema.sql +66 -0
- package/bundles/nextjs-saas/supabase/migrations/002_billing.sql +17 -0
- package/bundles/nextjs-saas/tsconfig.json +42 -0
- package/bundles/nextjs-saas/types/database.types.ts +76 -0
- package/bundles/nextjs-saas/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/nextjs-trpc/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/nextjs-trpc/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/nextjs-trpc/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-trpc/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/nextjs-trpc/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/nextjs-trpc/.claude/hooks/README.md +9 -0
- package/bundles/nextjs-trpc/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/nextjs-trpc/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/nextjs-trpc/.claude/rules/lattice-core.md +20 -0
- package/bundles/nextjs-trpc/.claude/settings.json +38 -0
- package/bundles/nextjs-trpc/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-trpc/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/nextjs-trpc/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/nextjs-trpc/.cursor/mcp.json +34 -0
- package/bundles/nextjs-trpc/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/nextjs-trpc/.cursor/rules.md +95 -0
- package/bundles/nextjs-trpc/.env.example +21 -0
- package/bundles/nextjs-trpc/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/nextjs-trpc/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/nextjs-trpc/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/nextjs-trpc/.github/copilot-instructions.md +37 -0
- package/bundles/nextjs-trpc/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/nextjs-trpc/.github/workflows/ci.yml +26 -0
- package/bundles/nextjs-trpc/.gitmessage +24 -0
- package/bundles/nextjs-trpc/.lattice/config.json +9 -0
- package/bundles/nextjs-trpc/.lattice/dependency-security.json +46 -0
- package/bundles/nextjs-trpc/.lattice/prompts/01-feature.md +36 -0
- package/bundles/nextjs-trpc/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/nextjs-trpc/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/nextjs-trpc/.lattice/prompts/04-test.md +31 -0
- package/bundles/nextjs-trpc/.lattice/prompts/05-polish.md +38 -0
- package/bundles/nextjs-trpc/.lattice/prompts/06-security.md +32 -0
- package/bundles/nextjs-trpc/.lattice/release-workflow.json +55 -0
- package/bundles/nextjs-trpc/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/nextjs-trpc/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/nextjs-trpc/.mcp.json +35 -0
- package/bundles/nextjs-trpc/AGENTS.md +58 -0
- package/bundles/nextjs-trpc/CLAUDE.md +76 -0
- package/bundles/nextjs-trpc/PROJECT_GUIDE.md +187 -0
- package/bundles/nextjs-trpc/README.md +49 -0
- package/bundles/nextjs-trpc/app/api/trpc/[trpc]/route.ts +12 -0
- package/bundles/nextjs-trpc/app/global-error.tsx +17 -0
- package/bundles/nextjs-trpc/app/globals.css +55 -0
- package/bundles/nextjs-trpc/app/layout.tsx +19 -0
- package/bundles/nextjs-trpc/app/page.tsx +16 -0
- package/bundles/nextjs-trpc/app/providers.tsx +24 -0
- package/bundles/nextjs-trpc/components/ui/button.tsx +39 -0
- package/bundles/nextjs-trpc/components/ui/card.tsx +53 -0
- package/bundles/nextjs-trpc/components/ui/index.ts +4 -0
- package/bundles/nextjs-trpc/components/ui/input.tsx +23 -0
- package/bundles/nextjs-trpc/components/ui/spinner.tsx +22 -0
- package/bundles/nextjs-trpc/components/user-list.tsx +17 -0
- package/bundles/nextjs-trpc/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/nextjs-trpc/docs/rules/codex.md +62 -0
- package/bundles/nextjs-trpc/eslint.config.mjs +16 -0
- package/bundles/nextjs-trpc/instrumentation-client.ts +20 -0
- package/bundles/nextjs-trpc/instrumentation.ts +12 -0
- package/bundles/nextjs-trpc/jest.config.mjs +16 -0
- package/bundles/nextjs-trpc/jest.setup.js +1 -0
- package/bundles/nextjs-trpc/lib/db.ts +17 -0
- package/bundles/nextjs-trpc/lib/trpc/react.tsx +7 -0
- package/bundles/nextjs-trpc/lib/utils.ts +6 -0
- package/bundles/nextjs-trpc/next-env.d.ts +5 -0
- package/bundles/nextjs-trpc/next.config.ts +15 -0
- package/bundles/nextjs-trpc/package.json +62 -0
- package/bundles/nextjs-trpc/postcss.config.mjs +7 -0
- package/bundles/nextjs-trpc/prisma/schema.prisma +41 -0
- package/bundles/nextjs-trpc/prisma.config.ts +10 -0
- package/bundles/nextjs-trpc/prompts/01-feature-implementation.md +9 -0
- package/bundles/nextjs-trpc/prompts/02-bug-fix.md +9 -0
- package/bundles/nextjs-trpc/prompts/03-safe-refactor.md +8 -0
- package/bundles/nextjs-trpc/prompts/04-test-first.md +8 -0
- package/bundles/nextjs-trpc/prompts/05-ui-polish.md +7 -0
- package/bundles/nextjs-trpc/prompts/06-security-review.md +7 -0
- package/bundles/nextjs-trpc/prompts/quality.md +269 -0
- package/bundles/nextjs-trpc/sentry.edge.config.ts +15 -0
- package/bundles/nextjs-trpc/sentry.server.config.ts +15 -0
- package/bundles/nextjs-trpc/server/routers/_app.test.ts +60 -0
- package/bundles/nextjs-trpc/server/routers/_app.ts +25 -0
- package/bundles/nextjs-trpc/server/trpc.ts +12 -0
- package/bundles/nextjs-trpc/tsconfig.json +42 -0
- package/bundles/nextjs-trpc/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/dist/agent-install-session.d.ts +17 -0
- package/dist/agent-install-session.d.ts.map +1 -0
- package/dist/agent-install-session.js +106 -0
- package/dist/agent-install-session.js.map +1 -0
- package/dist/agent-install-session.test.d.ts +2 -0
- package/dist/agent-install-session.test.d.ts.map +1 -0
- package/dist/agent-install-session.test.js +62 -0
- package/dist/agent-install-session.test.js.map +1 -0
- package/dist/agent-install.d.ts +41 -0
- package/dist/agent-install.d.ts.map +1 -0
- package/dist/agent-install.js +42 -0
- package/dist/agent-install.js.map +1 -0
- package/dist/agent-install.test.d.ts +2 -0
- package/dist/agent-install.test.d.ts.map +1 -0
- package/dist/agent-install.test.js +58 -0
- package/dist/agent-install.test.js.map +1 -0
- package/dist/apply-merge.test.d.ts +2 -0
- package/dist/apply-merge.test.d.ts.map +1 -0
- package/dist/apply-merge.test.js +123 -0
- package/dist/apply-merge.test.js.map +1 -0
- package/dist/bundle-download.d.ts +23 -0
- package/dist/bundle-download.d.ts.map +1 -0
- package/dist/bundle-download.js +180 -0
- package/dist/bundle-download.js.map +1 -0
- package/dist/bundle-download.test.d.ts +2 -0
- package/dist/bundle-download.test.d.ts.map +1 -0
- package/dist/bundle-download.test.js +200 -0
- package/dist/bundle-download.test.js.map +1 -0
- package/dist/bundle-rollback.test.d.ts +2 -0
- package/dist/bundle-rollback.test.d.ts.map +1 -0
- package/dist/bundle-rollback.test.js +127 -0
- package/dist/bundle-rollback.test.js.map +1 -0
- package/dist/bundle-scaffold.test.d.ts +2 -0
- package/dist/bundle-scaffold.test.d.ts.map +1 -0
- package/dist/bundle-scaffold.test.js +105 -0
- package/dist/bundle-scaffold.test.js.map +1 -0
- package/dist/commands.d.ts +132 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +2012 -0
- package/dist/commands.js.map +1 -0
- package/dist/credential-store.d.ts +27 -0
- package/dist/credential-store.d.ts.map +1 -0
- package/dist/credential-store.js +48 -0
- package/dist/credential-store.js.map +1 -0
- package/dist/credential-store.test.d.ts +2 -0
- package/dist/credential-store.test.d.ts.map +1 -0
- package/dist/credential-store.test.js +80 -0
- package/dist/credential-store.test.js.map +1 -0
- package/dist/delivered-project-files.d.ts +31 -0
- package/dist/delivered-project-files.d.ts.map +1 -0
- package/dist/delivered-project-files.js +158 -0
- package/dist/delivered-project-files.js.map +1 -0
- package/dist/delivered-project-files.test.d.ts +2 -0
- package/dist/delivered-project-files.test.d.ts.map +1 -0
- package/dist/delivered-project-files.test.js +107 -0
- package/dist/delivered-project-files.test.js.map +1 -0
- package/dist/doctor-agents.test.d.ts +2 -0
- package/dist/doctor-agents.test.d.ts.map +1 -0
- package/dist/doctor-agents.test.js +65 -0
- package/dist/doctor-agents.test.js.map +1 -0
- package/dist/hermes-install.d.ts +102 -0
- package/dist/hermes-install.d.ts.map +1 -0
- package/dist/hermes-install.js +641 -0
- package/dist/hermes-install.js.map +1 -0
- package/dist/hermes-install.test.d.ts +2 -0
- package/dist/hermes-install.test.d.ts.map +1 -0
- package/dist/hermes-install.test.js +866 -0
- package/dist/hermes-install.test.js.map +1 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5311 -0
- package/dist/index.js.map +1 -0
- package/dist/openclaw-install.d.ts +67 -0
- package/dist/openclaw-install.d.ts.map +1 -0
- package/dist/openclaw-install.js +358 -0
- package/dist/openclaw-install.js.map +1 -0
- package/dist/openclaw-install.test.d.ts +2 -0
- package/dist/openclaw-install.test.d.ts.map +1 -0
- package/dist/openclaw-install.test.js +296 -0
- package/dist/openclaw-install.test.js.map +1 -0
- package/dist/password-recovery-bundle.test.d.ts +2 -0
- package/dist/password-recovery-bundle.test.d.ts.map +1 -0
- package/dist/password-recovery-bundle.test.js +132 -0
- package/dist/password-recovery-bundle.test.js.map +1 -0
- package/dist/project-bundle-env.d.ts +19 -0
- package/dist/project-bundle-env.d.ts.map +1 -0
- package/dist/project-bundle-env.js +111 -0
- package/dist/project-bundle-env.js.map +1 -0
- package/dist/project-bundle-env.test.d.ts +2 -0
- package/dist/project-bundle-env.test.d.ts.map +1 -0
- package/dist/project-bundle-env.test.js +93 -0
- package/dist/project-bundle-env.test.js.map +1 -0
- package/dist/project-path-confinement.test.d.ts +2 -0
- package/dist/project-path-confinement.test.d.ts.map +1 -0
- package/dist/project-path-confinement.test.js +222 -0
- package/dist/project-path-confinement.test.js.map +1 -0
- package/dist/security/api.d.ts +18 -0
- package/dist/security/api.d.ts.map +1 -0
- package/dist/security/api.js +238 -0
- package/dist/security/api.js.map +1 -0
- package/dist/security/api.test.d.ts +2 -0
- package/dist/security/api.test.d.ts.map +1 -0
- package/dist/security/api.test.js +220 -0
- package/dist/security/api.test.js.map +1 -0
- package/dist/security/artifact.d.ts +10 -0
- package/dist/security/artifact.d.ts.map +1 -0
- package/dist/security/artifact.js +283 -0
- package/dist/security/artifact.js.map +1 -0
- package/dist/security/artifact.test.d.ts +2 -0
- package/dist/security/artifact.test.d.ts.map +1 -0
- package/dist/security/artifact.test.js +229 -0
- package/dist/security/artifact.test.js.map +1 -0
- package/dist/security/commands.d.ts +42 -0
- package/dist/security/commands.d.ts.map +1 -0
- package/dist/security/commands.js +825 -0
- package/dist/security/commands.js.map +1 -0
- package/dist/security/commands.test.d.ts +2 -0
- package/dist/security/commands.test.d.ts.map +1 -0
- package/dist/security/commands.test.js +1051 -0
- package/dist/security/commands.test.js.map +1 -0
- package/dist/security/connection.d.ts +19 -0
- package/dist/security/connection.d.ts.map +1 -0
- package/dist/security/connection.js +145 -0
- package/dist/security/connection.js.map +1 -0
- package/dist/security/connection.test.d.ts +2 -0
- package/dist/security/connection.test.d.ts.map +1 -0
- package/dist/security/connection.test.js +62 -0
- package/dist/security/connection.test.js.map +1 -0
- package/dist/security/index-routing.test.d.ts +2 -0
- package/dist/security/index-routing.test.d.ts.map +1 -0
- package/dist/security/index-routing.test.js +169 -0
- package/dist/security/index-routing.test.js.map +1 -0
- package/dist/security/journal.d.ts +26 -0
- package/dist/security/journal.d.ts.map +1 -0
- package/dist/security/journal.js +441 -0
- package/dist/security/journal.js.map +1 -0
- package/dist/security/journal.test.d.ts +2 -0
- package/dist/security/journal.test.d.ts.map +1 -0
- package/dist/security/journal.test.js +226 -0
- package/dist/security/journal.test.js.map +1 -0
- package/dist/security/package-verifier.d.ts +15 -0
- package/dist/security/package-verifier.d.ts.map +1 -0
- package/dist/security/package-verifier.js +65 -0
- package/dist/security/package-verifier.js.map +1 -0
- package/dist/security/package-verifier.test.d.ts +2 -0
- package/dist/security/package-verifier.test.d.ts.map +1 -0
- package/dist/security/package-verifier.test.js +57 -0
- package/dist/security/package-verifier.test.js.map +1 -0
- package/dist/security/runtime.d.ts +43 -0
- package/dist/security/runtime.d.ts.map +1 -0
- package/dist/security/runtime.js +81 -0
- package/dist/security/runtime.js.map +1 -0
- package/dist/security/runtime.test.d.ts +2 -0
- package/dist/security/runtime.test.d.ts.map +1 -0
- package/dist/security/runtime.test.js +88 -0
- package/dist/security/runtime.test.js.map +1 -0
- package/dist/security/types.d.ts +169 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security/types.js +3 -0
- package/dist/security/types.js.map +1 -0
- package/dist/security-runtime/LICENSE +202 -0
- package/dist/security-runtime/NOTICE +16 -0
- package/dist/security-runtime/artifact-manifest.json +960 -0
- package/dist/security-runtime/bin/vsg.mjs +5 -0
- package/dist/security-runtime/lattice-catalog.json +176585 -0
- package/dist/security-runtime/lattice-runtime.mjs +90 -0
- package/dist/security-runtime/licensing/candidates/apache-source-license-evidence.json +478 -0
- package/dist/security-runtime/licensing/candidates/betterleaks-generic-api-key.json +76 -0
- package/dist/security-runtime/licensing/candidates/betterleaks-jwt.json +74 -0
- package/dist/security-runtime/licensing/candidates/blackberry-additional-source-declaration-evidence.json +276 -0
- package/dist/security-runtime/licensing/candidates/blackberry-source-declaration-evidence.json +369 -0
- package/dist/security-runtime/licensing/candidates/cado-source-declaration-evidence.json +283 -0
- package/dist/security-runtime/licensing/candidates/commercial-rule-candidate.json +280 -0
- package/dist/security-runtime/licensing/candidates/commercial-runtime-release.json +389 -0
- package/dist/security-runtime/licensing/candidates/data-feed-evidence.json +259 -0
- package/dist/security-runtime/licensing/candidates/gitlab-sast-rule-identities.json +178 -0
- package/dist/security-runtime/licensing/candidates/gitlab-sast-source-evidence.json +1824 -0
- package/dist/security-runtime/licensing/candidates/gitlab-sast-source-notice-bundle.json +45 -0
- package/dist/security-runtime/licensing/candidates/malpedia-source-license-evidence.json +41 -0
- package/dist/security-runtime/licensing/candidates/rule-license-evidence.json +376 -0
- package/dist/security-runtime/licensing/candidates/runner-image-composition.json +1500 -0
- package/dist/security-runtime/licensing/candidates/selected-runtime-coverage.json +91 -0
- package/dist/security-runtime/licensing/candidates/yara-forge-20260927.json +58502 -0
- package/dist/security-runtime/licensing/commercial-components.json +379 -0
- package/dist/security-runtime/licensing/notices/betterleaks-1.9.0-MIT.txt +21 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/28a9529c7d0bb4dc51f4bf5c116a3d16ef247a052f7591466768ddf563fd1cf5.txt +428 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/33acf42afdd03163d75fe856cb431e70710e0b5deede3362e6d1c4697cfe1c6a.txt +28 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986.txt +674 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/4fecb8fcaf80fd9a6315bca5225ee43377015b46a0459c2925495be885cab19a.txt +21 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/811515f8723aa4007de2fbb75a364127fdec51cda534b87a7ea8f2fef2f96dd7.txt +24 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/814442c3987a2c0411171ad8ad43118e8c08f85c9c5d1dcdecc9787c7ca8689b.txt +664 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/8a959b2413d0c83ca1b3ce9153076c649abf2e4bbfebbb493fb24c2074a1eb8a.txt +38 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/995182e185856186fbbe49f150409ae026aa4cda7e94f83c6ed737cbb77fa9c7.txt +23 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/9b8e1305e46d1dd4011b6ed933c9f6223e08c9c4625001c5fd75a34fa930f616.txt +202 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a01f1a28424bfe7f49a23d339a01847cfa9ace3864d5e7295051772a4bae2271.txt +38 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a5681bf9b05db14d86776930017c647ad9e6e56ff6bbcfdf21e5848288dfaf1b.txt +165 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a5b8bd9ae96c630bbf229d61c5c5def20274e00be39abfd2fab4d7879ceab462.txt +24 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/ada32b3747ce108d5e8bab7918af94abbd03f2fe2a46304f74c6333bbfd914ed.txt +8 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4.txt +201 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/c738ae413cf561f174e34f6961f8ca458aae2369a73640dda6234c629b98bcc4.txt +2732 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/ce41c9a2c986277f7f9ab146a2195c1888a5d8da062e22d7c9c88666bb7c38cd.txt +17 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/d5f4c36a0528e01cfdd9e4fc60879201d3af49b41c520200643d321fe53da5f9.txt +17 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/e08c368ccd455f85c7fb9e04830e730f26e9dbb5c0f77bc88e09a5b0cb37df5b.txt +38 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/e9efb2f7daf11bb6379c06c16b870dddcecc4a10f8704bb8aad807529c8b3c97.txt +8 -0
- package/dist/security-runtime/licensing/notices/gitleaks-generic-origin-782f3104-MIT.txt +21 -0
- package/dist/security-runtime/package.json +17 -0
- package/dist/security-runtime/playbook/README.md +76 -0
- package/dist/security-runtime/playbook/access-control.md +56 -0
- package/dist/security-runtime/playbook/ai-features.md +53 -0
- package/dist/security-runtime/playbook/desktop-python.md +59 -0
- package/dist/security-runtime/playbook/input-handling.md +59 -0
- package/dist/security-runtime/playbook/mobile-expo.md +59 -0
- package/dist/security-runtime/playbook/payments.md +53 -0
- package/dist/security-runtime/playbook/privacy-account-recovery.md +59 -0
- package/dist/security-runtime/playbook/secrets-config.md +47 -0
- package/dist/security-runtime/playbook/supabase.md +48 -0
- package/dist/security-runtime/playbook/supply-chain.md +62 -0
- package/dist/security-runtime/playbook/webhooks-integrations.md +52 -0
- package/dist/security-runtime/rules/vsg-ai-key-in-client.yaml +41 -0
- package/dist/security-runtime/rules/vsg-ai-output-raw-html.yaml +35 -0
- package/dist/security-runtime/rules/vsg-angular-sanitizer-bypass.yaml +23 -0
- package/dist/security-runtime/rules/vsg-expo-token-in-asyncstorage.yaml +22 -0
- package/dist/security-runtime/rules/vsg-flask-public-bind.yaml +32 -0
- package/dist/security-runtime/rules/vsg-nextjs-action-write-no-auth.yaml +144 -0
- package/dist/security-runtime/rules/vsg-nextjs-inline-action-write-no-auth.yaml +161 -0
- package/dist/security-runtime/rules/vsg-nextjs-route-write-no-auth.yaml +193 -0
- package/dist/security-runtime/rules/vsg-nosql-where-injection.yaml +36 -0
- package/dist/security-runtime/rules/vsg-python-pickle-load.yaml +26 -0
- package/dist/security-runtime/rules/vsg-python-requests-verify-false.yaml +34 -0
- package/dist/security-runtime/rules/vsg-python-subprocess-shell.yaml +23 -0
- package/dist/security-runtime/rules/vsg-stripe-client-price.yaml +51 -0
- package/dist/security-runtime/rules/vsg-stripe-webhook-no-signature.yaml +31 -0
- package/dist/security-runtime/rules/vsg-stripe-webhook-pages-no-signature.yaml +51 -0
- package/dist/security-runtime/rules/vsg-supabase-service-role-in-client.yaml +27 -0
- package/dist/security-runtime/rules/vsg-supabase-service-role-public-env.yaml +24 -0
- package/dist/security-runtime/rules/vsg-xml-external-entities.yaml +19 -0
- package/dist/security-runtime/src/agent/brief.mjs +95 -0
- package/dist/security-runtime/src/agent/discover.mjs +108 -0
- package/dist/security-runtime/src/agent/exec.mjs +64 -0
- package/dist/security-runtime/src/agent/ingest.mjs +213 -0
- package/dist/security-runtime/src/agent/report.mjs +94 -0
- package/dist/security-runtime/src/agent/schema.mjs +62 -0
- package/dist/security-runtime/src/agent/tokenize.mjs +38 -0
- package/dist/security-runtime/src/checks/README.md +8 -0
- package/dist/security-runtime/src/checks/_lib/gitfiles.mjs +35 -0
- package/dist/security-runtime/src/checks/_lib/lockfiles.mjs +135 -0
- package/dist/security-runtime/src/checks/_lib/osv.mjs +6 -0
- package/dist/security-runtime/src/checks/_lib/secrets.mjs +381 -0
- package/dist/security-runtime/src/checks/_lib/targets.mjs +79 -0
- package/dist/security-runtime/src/checks/agent-review/index.mjs +52 -0
- package/dist/security-runtime/src/checks/agent-surface/config.mjs +223 -0
- package/dist/security-runtime/src/checks/agent-surface/index.mjs +113 -0
- package/dist/security-runtime/src/checks/agent-surface/instructions.mjs +87 -0
- package/dist/security-runtime/src/checks/agent-surface/patterns.mjs +68 -0
- package/dist/security-runtime/src/checks/agent-surface/unicode.mjs +88 -0
- package/dist/security-runtime/src/checks/code/auth-helpers.mjs +659 -0
- package/dist/security-runtime/src/checks/code/index.mjs +617 -0
- package/dist/security-runtime/src/checks/code/opengrep-container.mjs +85 -0
- package/dist/security-runtime/src/checks/code/opengrep-runner.mjs +86 -0
- package/dist/security-runtime/src/checks/code/selected-opengrep-runner.mjs +112 -0
- package/dist/security-runtime/src/checks/dep-change/guarddog.mjs +142 -0
- package/dist/security-runtime/src/checks/dep-change/index.mjs +316 -0
- package/dist/security-runtime/src/checks/dep-change/registries.mjs +199 -0
- package/dist/security-runtime/src/checks/deps-vuln/index.mjs +305 -0
- package/dist/security-runtime/src/checks/desktop-config/index.mjs +112 -0
- package/dist/security-runtime/src/checks/dns-mail/index.mjs +71 -0
- package/dist/security-runtime/src/checks/iac/index.mjs +83 -0
- package/dist/security-runtime/src/checks/install-posture/index.mjs +123 -0
- package/dist/security-runtime/src/checks/licenses/index.mjs +143 -0
- package/dist/security-runtime/src/checks/live-basic/index.mjs +77 -0
- package/dist/security-runtime/src/checks/live-js-secrets/index.mjs +100 -0
- package/dist/security-runtime/src/checks/malware/clamav.mjs +17 -0
- package/dist/security-runtime/src/checks/malware/defender.mjs +43 -0
- package/dist/security-runtime/src/checks/malware/index.mjs +186 -0
- package/dist/security-runtime/src/checks/malware/targets.mjs +147 -0
- package/dist/security-runtime/src/checks/malware/yara-runner.mjs +201 -0
- package/dist/security-runtime/src/checks/malware/yara.mjs +124 -0
- package/dist/security-runtime/src/checks/mobile-config/index.mjs +173 -0
- package/dist/security-runtime/src/checks/mobile-config/mobsf.mjs +105 -0
- package/dist/security-runtime/src/checks/posture/index.mjs +133 -0
- package/dist/security-runtime/src/checks/project-scripts/index.mjs +187 -0
- package/dist/security-runtime/src/checks/sbom/index.mjs +98 -0
- package/dist/security-runtime/src/checks/secrets-build/index.mjs +63 -0
- package/dist/security-runtime/src/checks/secrets-history/index.mjs +112 -0
- package/dist/security-runtime/src/checks/secrets-tree/env.mjs +120 -0
- package/dist/security-runtime/src/checks/secrets-tree/index.mjs +73 -0
- package/dist/security-runtime/src/checks/supabase-static/index.mjs +240 -0
- package/dist/security-runtime/src/checks/supabase-static/sql.mjs +557 -0
- package/dist/security-runtime/src/checks/workflows/index.mjs +93 -0
- package/dist/security-runtime/src/cli/deep.mjs +13 -0
- package/dist/security-runtime/src/cli/doctor.mjs +82 -0
- package/dist/security-runtime/src/cli/index.mjs +60 -0
- package/dist/security-runtime/src/cli/live-common.mjs +27 -0
- package/dist/security-runtime/src/cli/parse.mjs +56 -0
- package/dist/security-runtime/src/cli/portfolio.mjs +34 -0
- package/dist/security-runtime/src/cli/release.mjs +28 -0
- package/dist/security-runtime/src/cli/report.mjs +18 -0
- package/dist/security-runtime/src/cli/run-common.mjs +54 -0
- package/dist/security-runtime/src/cli/scan.mjs +12 -0
- package/dist/security-runtime/src/cli/setup.mjs +59 -0
- package/dist/security-runtime/src/cli/url.mjs +38 -0
- package/dist/security-runtime/src/core/accept.mjs +141 -0
- package/dist/security-runtime/src/core/archive.mjs +114 -0
- package/dist/security-runtime/src/core/base-ref.mjs +43 -0
- package/dist/security-runtime/src/core/checks.mjs +39 -0
- package/dist/security-runtime/src/core/detect.mjs +90 -0
- package/dist/security-runtime/src/core/errors.mjs +5 -0
- package/dist/security-runtime/src/core/fetch.mjs +139 -0
- package/dist/security-runtime/src/core/findings.mjs +177 -0
- package/dist/security-runtime/src/core/gate.mjs +131 -0
- package/dist/security-runtime/src/core/installer.mjs +255 -0
- package/dist/security-runtime/src/core/lock.mjs +38 -0
- package/dist/security-runtime/src/core/nested.mjs +107 -0
- package/dist/security-runtime/src/core/paths.mjs +20 -0
- package/dist/security-runtime/src/core/pipeline.mjs +196 -0
- package/dist/security-runtime/src/core/registry.mjs +81 -0
- package/dist/security-runtime/src/core/report.mjs +128 -0
- package/dist/security-runtime/src/core/runner.mjs +86 -0
- package/dist/security-runtime/src/core/sarif.mjs +141 -0
- package/dist/security-runtime/src/lattice/artifact.mjs +1201 -0
- package/dist/security-runtime/src/lattice/execution.mjs +45 -0
- package/dist/security-runtime/src/lattice/export.mjs +215 -0
- package/dist/security-runtime/src/lattice/provenance.mjs +70 -0
- package/dist/security-runtime/src/lattice/runtime.mjs +158 -0
- package/dist/security-runtime/src/lattice/selected-runtime.mjs +189 -0
- package/dist/security-runtime/src/lattice/source-acquirer.mjs +51 -0
- package/dist/security-runtime/src/lattice/source-fetcher.mjs +88 -0
- package/dist/security-runtime/src/lattice/transport.mjs +232 -0
- package/dist/security-runtime/src/live/analyze.mjs +101 -0
- package/dist/security-runtime/src/live/exposure.mjs +79 -0
- package/dist/security-runtime/src/live/secrets.mjs +31 -0
- package/dist/security-runtime/src/live/session.mjs +98 -0
- package/dist/security-runtime/src/live/targets.mjs +81 -0
- package/dist/security-runtime/src/portfolio/index.mjs +116 -0
- package/dist/security-runtime/tools/registry/betterleaks.json +29 -0
- package/dist/security-runtime/tools/registry/guarddog.json +9 -0
- package/dist/security-runtime/tools/registry/mobsf.json +10 -0
- package/dist/security-runtime/tools/registry/opengrep.json +28 -0
- package/dist/security-runtime/tools/registry/osv-scanner.json +14 -0
- package/dist/security-runtime/tools/registry/semgrep-rules.json +21 -0
- package/dist/security-runtime/tools/registry/trivy.json +30 -0
- package/dist/security-runtime/tools/registry/yara-forge-exclusions.json +14 -0
- package/dist/security-runtime/tools/registry/yara-forge.json +22 -0
- package/dist/security-runtime/tools/registry/yara-runner.json +10 -0
- package/dist/security-runtime/tools/registry/yara-x.json +24 -0
- package/dist/security-runtime/tools/registry/zizmor.json +30 -0
- package/dist/spinner.d.ts +16 -0
- package/dist/spinner.d.ts.map +1 -0
- package/dist/spinner.js +72 -0
- package/dist/spinner.js.map +1 -0
- package/dist/verify.test.d.ts +2 -0
- package/dist/verify.test.d.ts.map +1 -0
- package/dist/verify.test.js +98 -0
- package/dist/verify.test.js.map +1 -0
- package/package.json +73 -4
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// dep-change (REQ-010): signals on packages added or changed against the base ref, from registry metadata, plus GuardDog on the changed set.
|
|
2
|
+
// Reads lockfiles and registry metadata only. Never installs anything and never runs repository code.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { resolveBase as resolveBaseOf } from '../../core/base-ref.mjs';
|
|
6
|
+
import { git } from '../_lib/secrets.mjs';
|
|
7
|
+
import { keyOf, lockfileKind, NPM_NAME, normPyName, parseLockfile, PYPI_NAME, VERSION_SAFE } from '../_lib/lockfiles.mjs';
|
|
8
|
+
import { runGuardDog } from './guarddog.mjs';
|
|
9
|
+
import { analyzeNpm, analyzePyPI, RegistryClient } from './registries.mjs';
|
|
10
|
+
|
|
11
|
+
export const id = 'dep-change';
|
|
12
|
+
export const title = 'Dependency-change signals (age, provenance, publisher, install scripts, removals, GuardDog)';
|
|
13
|
+
export const modes = ['scan', 'deep', 'release'];
|
|
14
|
+
export const requiredIn = ['scan', 'deep', 'release'];
|
|
15
|
+
export const timeoutSec = 1800;
|
|
16
|
+
|
|
17
|
+
const CONCURRENCY = 8;
|
|
18
|
+
/**
|
|
19
|
+
* Wall-clock budgets. Registry lookups get REGISTRY_BUDGET_MS (packages not looked up in time count as unchecked; over 10% of them makes
|
|
20
|
+
* the check not_run). GuardDog gets what is left of TOTAL_BUDGET_MS, but at least MIN_GUARDDOG_BUDGET_MS. Without caps, one working copy
|
|
21
|
+
* with 31 changed packages ran for 256 s.
|
|
22
|
+
*/
|
|
23
|
+
export const REGISTRY_BUDGET_MS = 30_000;
|
|
24
|
+
export const TOTAL_BUDGET_MS = 60_000;
|
|
25
|
+
const MIN_GUARDDOG_BUDGET_MS = 15_000;
|
|
26
|
+
const MAX_PACKAGES = 300;
|
|
27
|
+
const MAX_UNCHECKED = 0.1;
|
|
28
|
+
const base = (f) => f.slice(f.lastIndexOf('/') + 1);
|
|
29
|
+
const dirOf = (f) => (f.includes('/') ? f.slice(0, f.lastIndexOf('/')) : '');
|
|
30
|
+
const supportedFiles = (d) => [...d.lockfiles.map((l) => l.file), ...(d.manifests ?? [])].filter((f) => lockfileKind(f));
|
|
31
|
+
|
|
32
|
+
export function applies(ctx) {
|
|
33
|
+
const d = ctx.detection;
|
|
34
|
+
return supportedFiles(d).length > 0 || d.lockfiles.length > 0
|
|
35
|
+
? true
|
|
36
|
+
: { applies: false, reason: 'no lockfile or pinned requirements file was found' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Runs fn over items with `limit` workers and stops starting new items once stop() is true. Returns the items that were never started. */
|
|
40
|
+
async function pool(items, limit, fn, stop = () => false) {
|
|
41
|
+
const queue = [...items];
|
|
42
|
+
await Promise.all(Array.from({ length: Math.min(limit, queue.length) }, async () => {
|
|
43
|
+
while (queue.length && !stop()) await fn(queue.shift());
|
|
44
|
+
}));
|
|
45
|
+
return queue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/** { sha, ref, how } of the comparison base, or { sha: null, why }: the resolver shared with the acceptance review (core/base-ref.mjs). */
|
|
50
|
+
export async function resolveBase(ctx) {
|
|
51
|
+
return resolveBaseOf(ctx.repo, { flag: ctx.flags?.base, config: ctx.config });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const readText = (repo, rel) => { try { return fs.readFileSync(path.join(repo, rel), 'utf8'); } catch { return null; } };
|
|
55
|
+
|
|
56
|
+
function directNames(repo, file) {
|
|
57
|
+
const pj = readText(repo, path.posix.join(dirOf(file), 'package.json'));
|
|
58
|
+
if (pj === null) return null;
|
|
59
|
+
try {
|
|
60
|
+
const j = JSON.parse(pj);
|
|
61
|
+
return new Set(['dependencies', 'devDependencies', 'optionalDependencies'].flatMap((k) => Object.keys(j[k] ?? {})));
|
|
62
|
+
} catch { return null; }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Packages in one lockfile that are new or changed. Returns { added: [...], changed: [...], mode, note }. */
|
|
66
|
+
function changesFor(ctx, file, current, baseParsed) {
|
|
67
|
+
const kind = lockfileKind(file);
|
|
68
|
+
const usable = current.filter((p) => !p.skip);
|
|
69
|
+
const skipped = current.filter((p) => p.skip);
|
|
70
|
+
if (baseParsed) {
|
|
71
|
+
const baseKeys = new Set(baseParsed.map(keyOf));
|
|
72
|
+
const baseVersions = new Map();
|
|
73
|
+
for (const b of baseParsed) {
|
|
74
|
+
const k = `${b.ecosystem}:${b.ecosystem === 'PyPI' ? normPyName(b.name) : b.name}`;
|
|
75
|
+
if (!baseVersions.has(k)) baseVersions.set(k, []);
|
|
76
|
+
baseVersions.get(k).push(b.version);
|
|
77
|
+
}
|
|
78
|
+
const out = [];
|
|
79
|
+
for (const p of usable) {
|
|
80
|
+
if (baseKeys.has(keyOf(p))) continue;
|
|
81
|
+
const prev = baseVersions.get(`${p.ecosystem}:${p.ecosystem === 'PyPI' ? normPyName(p.name) : p.name}`);
|
|
82
|
+
out.push({ ...p, file, prevVersion: prev ? prev[prev.length - 1] : null, added: !prev });
|
|
83
|
+
}
|
|
84
|
+
return { packages: out, skipped, mode: 'diff against base' };
|
|
85
|
+
}
|
|
86
|
+
// No base: direct dependencies only.
|
|
87
|
+
let direct;
|
|
88
|
+
let note = '';
|
|
89
|
+
if (kind === 'requirements') direct = usable;
|
|
90
|
+
else if (kind === 'npm-lock' || kind === 'pnpm-lock') {
|
|
91
|
+
const names = directNames(ctx.repo, file);
|
|
92
|
+
if (names) direct = usable.filter((p) => names.has(p.name) && (kind === 'pnpm-lock' || p.top));
|
|
93
|
+
else { direct = []; note = `no readable package.json beside ${file}, so its direct dependencies could not be identified`; }
|
|
94
|
+
} else { direct = usable; note = `${file}: direct dependencies are not derived for ${kind}; every locked package is considered (capped at ${MAX_PACKAGES})`; }
|
|
95
|
+
return { packages: direct.map((p) => ({ ...p, file, prevVersion: null, added: true })), skipped, mode: 'direct dependencies only (no base)', note };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const label = (p) => `${p.name}${p.ecosystem === 'PyPI' ? '==' : '@'}${p.version}`;
|
|
99
|
+
const rawFinding = (p, o) => ({
|
|
100
|
+
category: 'dependency-change', tool: 'vsg', ruleId: `dep-change/${o.key}`, confidence: 'medium',
|
|
101
|
+
location: { file: p.file, package: p.name, version: p.version, ecosystem: p.ecosystem }, refs: [], ...o,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/** Turns one package's signals into findings. Blocking rows follow the policy table; single other signals are medium warnings. */
|
|
105
|
+
export function findingsFor(p, res) {
|
|
106
|
+
const out = [];
|
|
107
|
+
const byId = Object.fromEntries(res.signals.map((s) => [s.id, s]));
|
|
108
|
+
const where = `${label(p)} in ${p.file}`;
|
|
109
|
+
const cmp = res.comparison ? ` Compared with ${res.comparison.version} (${res.comparison.source}).` : '';
|
|
110
|
+
const consumed = new Set();
|
|
111
|
+
const detail = (ids) => ids.filter((i) => byId[i]).map((i) => byId[i].detail).join(' ');
|
|
112
|
+
|
|
113
|
+
if (byId.removed) {
|
|
114
|
+
out.push(rawFinding(p, {
|
|
115
|
+
key: 'version-removed', severity: 'high', confidence: 'high',
|
|
116
|
+
title: `Version removed from the registry: ${label(p)}`,
|
|
117
|
+
description: `${byId.removed.detail} ${where} still depends on it.`,
|
|
118
|
+
evidence: byId.removed.detail,
|
|
119
|
+
fix: `Treat ${label(p)} as untrusted. Check the package's security advisories, pin a version that still exists, and rotate secrets on any machine or CI run that installed it.`,
|
|
120
|
+
}));
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
if (byId['provenance-dropped']) {
|
|
124
|
+
consumed.add('provenance-dropped');
|
|
125
|
+
out.push(rawFinding(p, {
|
|
126
|
+
key: 'provenance-dropped', severity: 'high', confidence: 'high',
|
|
127
|
+
title: `Provenance dropped: ${label(p)}`,
|
|
128
|
+
description: `${byId['provenance-dropped'].detail} A release that stops carrying build provenance may have been published from a stolen token rather than the project's CI.${cmp}`,
|
|
129
|
+
evidence: [detail(['provenance-dropped', 'publisher-changed', 'new-install-script', 'new-dependencies', 'fresh'])].join('\n'),
|
|
130
|
+
fix: `Do not ship ${where}. Pin the version that had provenance (${res.comparison?.version ?? 'the previous release'}) until the maintainers explain the change.`,
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
const risky = byId['new-install-script'] || res.signals.some((s) => s.id === 'new-dependencies' && s.recent?.length);
|
|
134
|
+
if (byId.fresh && risky) {
|
|
135
|
+
for (const i of ['fresh', 'new-install-script']) consumed.add(i);
|
|
136
|
+
if (byId['new-dependencies']?.recent?.length) consumed.add('new-dependencies');
|
|
137
|
+
out.push(rawFinding(p, {
|
|
138
|
+
key: 'fresh-risky-change', severity: 'high', confidence: 'high',
|
|
139
|
+
title: `Brand-new release with an install script or very new dependency: ${label(p)}`,
|
|
140
|
+
description: `${detail(['fresh', 'new-install-script', 'new-dependencies'])}${cmp} A version under 72 hours old that also adds an install-time script or a dependency created within 30 days matches the pattern of recent npm supply-chain attacks.`,
|
|
141
|
+
evidence: detail(['fresh', 'publisher-changed', 'new-install-script', 'new-dependencies']),
|
|
142
|
+
fix: `Wait for the release to age and be reviewed, or pin the previous version (${res.comparison?.version ?? 'the last one you reviewed'}). Install scripts should run only for packages you trust.`,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
const warnings = {
|
|
146
|
+
fresh: ['published-recently', 'Published less than 72 hours ago', 'Let new releases age a few days before adopting them, or review the diff against the previous version first.'],
|
|
147
|
+
'publisher-changed': ['publisher-changed', 'Publisher changed', 'Confirm the new publisher is a legitimate maintainer. A publisher change is a common sign of an account takeover.'],
|
|
148
|
+
'new-install-script': ['new-install-script', 'New install script', 'Review the script before installing, and consider ignore-scripts for this package.'],
|
|
149
|
+
'new-dependencies': ['new-dependencies', 'New dependencies', 'Review each new dependency, especially recently created or unknown ones.'],
|
|
150
|
+
'low-reputation': ['low-reputation', 'Low-reputation new package', 'Confirm you need this package and that it is the one you meant to install (typosquats are often new and rarely downloaded).'],
|
|
151
|
+
yanked: ['yanked-release', 'Yanked release', 'Upgrade to a release that has not been yanked.'],
|
|
152
|
+
};
|
|
153
|
+
for (const s of res.signals) {
|
|
154
|
+
if (consumed.has(s.id) || !warnings[s.id]) continue;
|
|
155
|
+
const [key, head, fix] = warnings[s.id];
|
|
156
|
+
out.push(rawFinding(p, { key, severity: 'medium', title: `${head}: ${label(p)}`, description: `${s.detail}${cmp}`, evidence: s.detail, fix }));
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Registry signals that corroborate a GuardDog high_risk verdict. GuardDog's heuristics also fire on large legitimate packages
|
|
162
|
+
// (for example bundled webpack code in next), so a GuardDog-only verdict is a review item, not a Critical block.
|
|
163
|
+
const CORROBORATING = new Set(['provenance-dropped', 'publisher-changed', 'new-install-script', 'fresh', 'removed', 'low-reputation']);
|
|
164
|
+
|
|
165
|
+
function guardDogFindings(p, gd, signals = []) {
|
|
166
|
+
const r = gd.result;
|
|
167
|
+
if (!r || (!r.malicious && !r.rules.length)) return [];
|
|
168
|
+
const rules = r.rules.map((x) => `${x.rule}${x.where ? ` at ${x.where}` : ''}${x.message ? `: ${x.message}` : ''}`);
|
|
169
|
+
const evidence = `GuardDog risk rating: ${r.label}. ${rules.slice(0, 8).join('\n')}`;
|
|
170
|
+
if (r.malicious && !signals.some((s) => CORROBORATING.has(s.id))) {
|
|
171
|
+
return [{
|
|
172
|
+
category: 'dependency-change', severity: 'medium', confidence: 'medium', key: 'guarddog-high-risk-uncorroborated', tool: 'guarddog', ruleId: 'guarddog/high_risk',
|
|
173
|
+
location: { file: p.file, package: p.name, version: p.version, ecosystem: p.ecosystem }, refs: [],
|
|
174
|
+
title: `GuardDog rates ${label(p)} as high risk, with no registry warning sign`,
|
|
175
|
+
description: `GuardDog's heuristics matched (${r.rules.map((x) => x.rule).join(', ')}), but the registry shows no sign of compromise: provenance, publisher, install scripts, release age and reputation are all normal. These rules also fire on large legitimate packages, so review the flagged code before shipping.`,
|
|
176
|
+
evidence,
|
|
177
|
+
fix: `Review the flagged code in ${label(p)}. It blocks automatically only when a registry warning sign agrees.`,
|
|
178
|
+
}];
|
|
179
|
+
}
|
|
180
|
+
if (r.malicious) {
|
|
181
|
+
return [{
|
|
182
|
+
category: 'malicious-package', severity: 'critical', confidence: 'high', key: 'guarddog-malicious', tool: 'guarddog', ruleId: 'guarddog/high_risk',
|
|
183
|
+
location: { file: p.file, package: p.name, version: p.version, ecosystem: p.ecosystem }, refs: [],
|
|
184
|
+
title: `GuardDog rates ${label(p)} as high risk (likely malicious)`,
|
|
185
|
+
description: `GuardDog's malicious-behavior rules matched this package version: ${r.rules.map((x) => x.rule).join(', ')}. Treat any machine or CI run that installed it as compromised.`,
|
|
186
|
+
evidence,
|
|
187
|
+
fix: `Remove ${label(p)} from ${p.file}, reinstall from a clean state, and rotate secrets available to machines that installed it.`,
|
|
188
|
+
}];
|
|
189
|
+
}
|
|
190
|
+
return [{
|
|
191
|
+
category: 'dependency-change', severity: 'medium', confidence: 'medium', key: 'guarddog-issues', tool: 'guarddog', ruleId: 'guarddog/issues',
|
|
192
|
+
location: { file: p.file, package: p.name, version: p.version, ecosystem: p.ecosystem }, refs: [],
|
|
193
|
+
title: `GuardDog flagged ${r.rules.length} issue${r.rules.length === 1 ? '' : 's'} in ${label(p)}`,
|
|
194
|
+
description: `GuardDog rated this version "${r.label}": ${r.rules.map((x) => x.rule).join(', ')}. These rules also fire on legitimate packages, so review the code.`,
|
|
195
|
+
evidence, fix: `Review the flagged code in ${label(p)} before shipping this change.`,
|
|
196
|
+
}];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export async function run(ctx) {
|
|
200
|
+
const d = ctx.detection;
|
|
201
|
+
const files = supportedFiles(d);
|
|
202
|
+
const unsupported = d.lockfiles.map((l) => l.file).filter((f) => !lockfileKind(f));
|
|
203
|
+
if (!files.length) {
|
|
204
|
+
return { status: 'not_run', reason: `dependency-change analysis does not support ${unsupported.join(', ')} yet (supported: package-lock.json, pnpm-lock.yaml, requirements.txt, uv.lock, poetry.lock)` };
|
|
205
|
+
}
|
|
206
|
+
const nowMs = ctx.nowMs ?? Date.now();
|
|
207
|
+
const startedAt = Date.now();
|
|
208
|
+
const notes = [];
|
|
209
|
+
const baseInfo = await resolveBase(ctx);
|
|
210
|
+
notes.push(baseInfo.sha ? `base: ${baseInfo.ref} (${baseInfo.sha.slice(0, 12)}, ${baseInfo.how})` : `no base available: ${baseInfo.why}; only direct dependencies were considered`);
|
|
211
|
+
if (unsupported.length) notes.push(`not analyzed (unsupported lockfile type): ${unsupported.join(', ')}`);
|
|
212
|
+
|
|
213
|
+
const changes = new Map();
|
|
214
|
+
const baseKeysAll = new Set(); // name@version of every package in the base version of any lockfile
|
|
215
|
+
const skipped = [];
|
|
216
|
+
const unreadable = [];
|
|
217
|
+
let considered = 0;
|
|
218
|
+
for (const file of files) {
|
|
219
|
+
const text = readText(ctx.repo, file);
|
|
220
|
+
let current;
|
|
221
|
+
try { current = text === null ? null : parseLockfile(file, text); } catch { current = null; }
|
|
222
|
+
if (!current) { unreadable.push(file); continue; }
|
|
223
|
+
let baseParsed = null;
|
|
224
|
+
if (baseInfo.sha) {
|
|
225
|
+
const r = await git(ctx, ['show', `${baseInfo.sha}:./${file}`]);
|
|
226
|
+
if (r.status === 'ran' && r.exitCode === 0) { try { baseParsed = parseLockfile(file, r.stdout); } catch { baseParsed = null; } }
|
|
227
|
+
if (!baseParsed) notes.push(`${file} does not exist (or is unreadable) at the base, so only its direct dependencies were considered`);
|
|
228
|
+
}
|
|
229
|
+
const c = changesFor(ctx, file, current, baseParsed);
|
|
230
|
+
if (c.note) notes.push(c.note);
|
|
231
|
+
skipped.push(...c.skipped.map((p) => ({ file, name: p.name, version: p.version, why: p.skip })));
|
|
232
|
+
if (baseParsed) for (const b of baseParsed) baseKeysAll.add(keyOf(b));
|
|
233
|
+
for (const p of c.packages) {
|
|
234
|
+
const k = keyOf(p);
|
|
235
|
+
const entry = baseParsed ? p : { ...p, noBase: true };
|
|
236
|
+
if (!changes.has(k) || (changes.get(k).noBase && !entry.noBase)) changes.set(k, entry);
|
|
237
|
+
}
|
|
238
|
+
considered += current.length;
|
|
239
|
+
}
|
|
240
|
+
// A lockfile with no counterpart at the base (an untracked or new project folder) lists direct dependencies as "changed", but a
|
|
241
|
+
// name@version that the base lockfiles already hold is not new to the repository: it was released before and is not looked up again.
|
|
242
|
+
let alreadyInBase = 0;
|
|
243
|
+
for (const [k, p] of changes) if (p.noBase && baseKeysAll.has(k)) { changes.delete(k); alreadyInBase++; }
|
|
244
|
+
if (alreadyInBase) notes.push(`${alreadyInBase} direct dependenc${alreadyInBase === 1 ? 'y' : 'ies'} of a lockfile that is new since the base are already in the base lockfiles at the same version and were not looked up again`);
|
|
245
|
+
if (unreadable.length) notes.push(`could not read or parse: ${unreadable.join(', ')}`);
|
|
246
|
+
if (unreadable.length === files.length) return { status: 'not_run', reason: `no lockfile could be read: ${unreadable.join(', ')}` };
|
|
247
|
+
|
|
248
|
+
let todo = [...changes.values()].filter((p) => (p.ecosystem === 'npm' ? NPM_NAME.test(p.name) : PYPI_NAME.test(p.name)) && VERSION_SAFE.test(p.version));
|
|
249
|
+
const invalid = changes.size - todo.length;
|
|
250
|
+
if (invalid) notes.push(`${invalid} package name(s) or version(s) were not valid registry identifiers and were not looked up`);
|
|
251
|
+
todo.sort((a, b) => Number(b.added) - Number(a.added) || a.name.localeCompare(b.name) || a.version.localeCompare(b.version));
|
|
252
|
+
if (todo.length > MAX_PACKAGES) {
|
|
253
|
+
notes.push(`${todo.length} packages changed; only the first ${MAX_PACKAGES} (new packages first) were analyzed and ${todo.length - MAX_PACKAGES} were not`);
|
|
254
|
+
todo = todo.slice(0, MAX_PACKAGES);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const reg = new RegistryClient(ctx.fetch, nowMs);
|
|
258
|
+
const results = [];
|
|
259
|
+
const registryBudgetMs = ctx.registryBudgetMs ?? REGISTRY_BUDGET_MS;
|
|
260
|
+
const registryDeadline = Date.now() + registryBudgetMs;
|
|
261
|
+
const notStarted = await pool(todo, CONCURRENCY, async (p) => {
|
|
262
|
+
const res = await (p.ecosystem === 'PyPI' ? analyzePyPI : analyzeNpm)(reg, p);
|
|
263
|
+
results.push({ p, res });
|
|
264
|
+
}, () => Date.now() >= registryDeadline);
|
|
265
|
+
for (const p of notStarted) results.push({ p, res: { checked: false, reason: `not looked up: the ${Math.round(registryBudgetMs / 1000)}-second registry time budget ran out` } });
|
|
266
|
+
results.sort((a, b) => keyOf(a.p).localeCompare(keyOf(b.p)));
|
|
267
|
+
const unchecked = results.filter((r) => !r.res.checked);
|
|
268
|
+
const total = todo.length + invalid;
|
|
269
|
+
if (total > 0 && (unchecked.length + invalid) / total > MAX_UNCHECKED) {
|
|
270
|
+
return {
|
|
271
|
+
status: 'not_run',
|
|
272
|
+
reason: `${unchecked.length + invalid} of ${total} changed packages could not be checked (limit 10%): ${unchecked.slice(0, 3).map((r) => `${label(r.p)} (${r.res.reason})`).join('; ')}`,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (unchecked.length) notes.push(`${unchecked.length} of ${total} packages could not be checked: ${unchecked.map((r) => `${label(r.p)} (${r.res.reason})`).join('; ')}`);
|
|
276
|
+
for (const r of results) for (const n of r.res.notes ?? []) if (!notes.includes(`${r.p.name}: ${n}`) && !/^PyPI provenance/.test(n)) notes.push(`${label(r.p)}: ${n}`);
|
|
277
|
+
if (results.some((r) => r.p.ecosystem === 'PyPI')) notes.push('PyPI provenance and publisher are not exposed in the PyPI JSON API, so provenance-drop and publisher-change were not evaluated for PyPI packages');
|
|
278
|
+
|
|
279
|
+
const findings = [];
|
|
280
|
+
for (const { p, res } of results) if (res.checked) findings.push(...findingsFor(p, res));
|
|
281
|
+
|
|
282
|
+
// GuardDog on the changed packages, most suspicious first.
|
|
283
|
+
const suspicion = new Map(results.map((r) => [keyOf(r.p), r.res.signals?.length ?? 0]));
|
|
284
|
+
const gdCandidates = results.filter((r) => r.res.checked && !r.res.signals.some((s) => s.id === 'removed')).map((r) => r.p)
|
|
285
|
+
.sort((a, b) => suspicion.get(keyOf(b)) - suspicion.get(keyOf(a)));
|
|
286
|
+
const gd = gdCandidates.length ? await runGuardDog({ ...ctx, guarddogBudgetMs: ctx.guarddogBudgetMs ?? Math.max(MIN_GUARDDOG_BUDGET_MS, TOTAL_BUDGET_MS - (Date.now() - startedAt)) }, gdCandidates) : { ran: false, reason: 'GuardDog skipped: no changed package to scan', results: [] };
|
|
287
|
+
if (!gd.ran) notes.push(gd.reason);
|
|
288
|
+
else {
|
|
289
|
+
if (gd.skipped) notes.push(`GuardDog scanned the first 25 of ${gdCandidates.length} changed packages; ${gd.skipped} were not scanned`);
|
|
290
|
+
if (gd.budgetSkipped) notes.push(`GuardDog's ${Math.round(gd.budgetMs / 1000)}-second time budget ran out: ${gd.budgetSkipped} of ${gdCandidates.length} changed packages were not scanned (the most suspicious were scanned first)`);
|
|
291
|
+
if (gd.cached) notes.push(`${gd.cached} GuardDog result(s) came from the local cache (same image digest, package version scanned within 14 days)`);
|
|
292
|
+
for (const r of gd.results) {
|
|
293
|
+
const p = gdCandidates.find((x) => `${x.name}@${x.version}` === r.package);
|
|
294
|
+
if (!r.ok) notes.push(`GuardDog could not scan ${r.package}: ${r.reason}`);
|
|
295
|
+
else findings.push(...guardDogFindings(p, r, results.find((x) => keyOf(x.p) === keyOf(p))?.res.signals ?? []));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const summary = results.map(({ p, res }) => ({
|
|
300
|
+
package: label(p), file: p.file, ecosystem: p.ecosystem, added: p.added, comparison: res.comparison ?? null,
|
|
301
|
+
checked: res.checked, reason: res.reason, signals: (res.signals ?? []).map((s) => ({ id: s.id, detail: s.detail })), notes: res.notes ?? [], facts: res.facts ?? null,
|
|
302
|
+
}));
|
|
303
|
+
return {
|
|
304
|
+
status: 'ran',
|
|
305
|
+
reason: [unchecked.length ? `${unchecked.length} package(s) could not be checked` : '', !gd.ran ? gd.reason : '', gd.ran && gd.budgetSkipped ? `GuardDog did not scan ${gd.budgetSkipped} package(s): time budget` : ''].filter(Boolean).join('; '),
|
|
306
|
+
findings,
|
|
307
|
+
toolVersion: gd.ran ? `guarddog ${gd.version}` : null,
|
|
308
|
+
dataVersions: { registries: 'registry.npmjs.org and pypi.org (live)', ...(baseInfo.sha ? { base: baseInfo.sha.slice(0, 12) } : {}), ...(gd.ran ? { guarddogImage: gd.image } : {}) },
|
|
309
|
+
evidence: {
|
|
310
|
+
'summary.json': JSON.stringify({ base: baseInfo.sha ? { ref: baseInfo.ref, sha: baseInfo.sha } : null, packagesInLockfiles: considered, analyzed: results.length, notes, skippedNonRegistry: skipped, guarddog: gd.ran ? { image: gd.image, scanned: gd.results.length, cached: gd.cached, notScannedOverCap: gd.skipped, notScannedTimeBudget: gd.budgetSkipped, budgetMs: gd.budgetMs, durationMs: gd.durationMs } : { skipped: gd.reason }, packages: summary }, null, 2),
|
|
311
|
+
...(gd.ran ? { 'guarddog.json': JSON.stringify(gd.results, null, 2) } : {}),
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export default { id, title, modes, requiredIn, timeoutSec, applies, run };
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Registry metadata and the per-package signals for dep-change (REQ-010). All requests go through ctx.fetch (the core fetch wrapper).
|
|
2
|
+
// Each analyze function returns { checked: true, signals, notes, comparison } or { checked: false, reason }.
|
|
3
|
+
const HOUR = 3_600_000;
|
|
4
|
+
const DAY = 24 * HOUR;
|
|
5
|
+
export const FRESH_MS = 72 * HOUR;
|
|
6
|
+
export const NEW_PACKAGE_MS = 30 * DAY;
|
|
7
|
+
export const LOW_WEEKLY_DOWNLOADS = 100;
|
|
8
|
+
const INSTALL_SCRIPTS = ['preinstall', 'install', 'postinstall'];
|
|
9
|
+
const MAX_NEW_DEPS_LOOKED_UP = 10;
|
|
10
|
+
const isPrerelease = (v) => /[-+a-z]/i.test(v.replace(/^v/, '')) && !/^\d+(\.\d+)*$/.test(v);
|
|
11
|
+
const pyReleaseLike = (v) => /^\d+(\.\d+)*$/.test(v);
|
|
12
|
+
const ms = (s) => { const t = Date.parse(s); return Number.isFinite(t) ? t : null; };
|
|
13
|
+
const days = (n) => `${Math.round((n / DAY) * 10) / 10} days`;
|
|
14
|
+
const hours = (n) => `${Math.round((n / HOUR) * 10) / 10} hours`;
|
|
15
|
+
|
|
16
|
+
export class RegistryClient {
|
|
17
|
+
constructor(fetchFn, nowMs) {
|
|
18
|
+
this.fetchFn = fetchFn;
|
|
19
|
+
this.nowMs = nowMs;
|
|
20
|
+
this.cache = new Map();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** null when the registry answers 404; throws on any other failure. */
|
|
24
|
+
getJson(url) {
|
|
25
|
+
if (!this.cache.has(url)) {
|
|
26
|
+
this.cache.set(url, (async () => {
|
|
27
|
+
const res = await this.fetchFn(url, { timeoutMs: 20_000, retries: 2, headers: { accept: 'application/json' }, maxBytes: 128 * 1024 * 1024 });
|
|
28
|
+
if (res.status === 404) return null;
|
|
29
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} from ${new URL(url).hostname}`);
|
|
30
|
+
try { return JSON.parse(res.body.toString('utf8')); } catch { throw new Error(`invalid JSON from ${new URL(url).hostname}`); }
|
|
31
|
+
})());
|
|
32
|
+
}
|
|
33
|
+
return this.cache.get(url);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
npmPackument(name) { return this.getJson(`https://registry.npmjs.org/${name.startsWith('@') ? `@${encodeURIComponent(name.slice(1))}` : name}`); }
|
|
37
|
+
npmDownloads(name) { return this.getJson(`https://api.npmjs.org/downloads/point/last-week/${name}`); }
|
|
38
|
+
pypiProject(name) { return this.getJson(`https://pypi.org/pypi/${name}/json`); }
|
|
39
|
+
pypiVersion(name, version) { return this.getJson(`https://pypi.org/pypi/${name}/${version}/json`); }
|
|
40
|
+
|
|
41
|
+
/** Creation time (ms) of an npm package, or null when it cannot be determined. */
|
|
42
|
+
async npmCreated(name) { try { const p = await this.npmPackument(name); return ms(p?.time?.created); } catch { return null; } }
|
|
43
|
+
async pypiCreated(name) {
|
|
44
|
+
try { const p = await this.pypiProject(name); return earliestUpload(Object.values(p?.releases ?? {}).flat()); } catch { return null; }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function earliestUpload(files) {
|
|
49
|
+
const times = (files ?? []).map((f) => ms(f.upload_time_iso_8601 ?? f.upload_time)).filter((t) => t !== null);
|
|
50
|
+
return times.length ? Math.min(...times) : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The latest release published before `version`, by publish time (prereleases only when `version` is one). */
|
|
54
|
+
function previousByTime(versions, timeOf, version, isPre) {
|
|
55
|
+
const mine = timeOf(version);
|
|
56
|
+
if (mine === null) return null;
|
|
57
|
+
const want = isPre(version);
|
|
58
|
+
let best = null;
|
|
59
|
+
for (const v of versions) {
|
|
60
|
+
if (v === version) continue;
|
|
61
|
+
const t = timeOf(v);
|
|
62
|
+
if (t === null || t >= mine || (!want && isPre(v))) continue;
|
|
63
|
+
if (!best || t > best.t) best = { v, t };
|
|
64
|
+
}
|
|
65
|
+
return best?.v ?? null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const SIGNAL_FIX_PIN = 'Do not release this change. Pin the comparison version (or the last version you reviewed) and have the dependency change reviewed by a person before it ships.';
|
|
69
|
+
|
|
70
|
+
async function newDependencySignal(reg, names, now, notes, lookup) {
|
|
71
|
+
const looked = names.slice(0, MAX_NEW_DEPS_LOOKED_UP);
|
|
72
|
+
const recent = [];
|
|
73
|
+
for (const d of looked) {
|
|
74
|
+
const created = await lookup(d);
|
|
75
|
+
if (created === null) { notes.push(`could not determine when new dependency ${d} was created`); continue; }
|
|
76
|
+
if (now - created < NEW_PACKAGE_MS) recent.push({ name: d, ageDays: Math.round(((now - created) / DAY) * 10) / 10 });
|
|
77
|
+
}
|
|
78
|
+
if (names.length > looked.length) notes.push(`only the first ${looked.length} of ${names.length} new dependencies were looked up for age`);
|
|
79
|
+
return recent;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function analyzeNpm(reg, { name, version, prevVersion, added }) {
|
|
83
|
+
const now = reg.nowMs;
|
|
84
|
+
let pack;
|
|
85
|
+
try { pack = await reg.npmPackument(name); } catch (e) { return { checked: false, reason: `npm registry lookup failed: ${e.message}` }; }
|
|
86
|
+
const signals = [];
|
|
87
|
+
const notes = [];
|
|
88
|
+
if (pack === null) {
|
|
89
|
+
return { checked: true, signals: [{ id: 'removed', detail: `${name} is no longer on the npm registry (HTTP 404). The whole package was unpublished or taken down.` }], notes, comparison: null };
|
|
90
|
+
}
|
|
91
|
+
const times = pack.time ?? {};
|
|
92
|
+
const v = pack.versions?.[version];
|
|
93
|
+
if (!v) {
|
|
94
|
+
const when = times[version] ? ` It was published ${times[version]} and has since been removed.` : '';
|
|
95
|
+
return { checked: true, signals: [{ id: 'removed', detail: `${name}@${version} is not on the npm registry.${when} Registries remove versions mainly after malware or a security takedown.` }], notes, comparison: null };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const published = ms(times[version]);
|
|
99
|
+
const age = published === null ? null : now - published;
|
|
100
|
+
const prevLock = prevVersion && pack.versions[prevVersion] ? prevVersion : null;
|
|
101
|
+
const prev = prevLock ?? previousByTime(Object.keys(pack.versions), (x) => ms(times[x]), version, isPrerelease);
|
|
102
|
+
const comparison = prev ? { version: prev, source: prevLock ? 'base lockfile' : 'previous registry release' } : null;
|
|
103
|
+
const pv = prev ? pack.versions[prev] : null;
|
|
104
|
+
|
|
105
|
+
const fresh = age !== null && age >= 0 && age < FRESH_MS;
|
|
106
|
+
if (fresh) signals.push({ id: 'fresh', detail: `${name}@${version} was published ${hours(age)} ago (under 72 hours).` });
|
|
107
|
+
|
|
108
|
+
let newScripts = [];
|
|
109
|
+
let recentNewDeps = [];
|
|
110
|
+
if (pv) {
|
|
111
|
+
if (pv.dist?.attestations && !v.dist?.attestations) {
|
|
112
|
+
signals.push({ id: 'provenance-dropped', detail: `${name}@${prev} (${comparison.source}) has npm provenance attestations, but ${name}@${version} does not.` });
|
|
113
|
+
}
|
|
114
|
+
const was = pv._npmUser?.name;
|
|
115
|
+
const now_ = v._npmUser?.name;
|
|
116
|
+
if (was && now_ && was !== now_) signals.push({ id: 'publisher-changed', detail: `Published by "${now_}" instead of "${was}" (the publisher of ${name}@${prev}).` });
|
|
117
|
+
newScripts = INSTALL_SCRIPTS.filter((s) => v.scripts?.[s] && !pv.scripts?.[s]);
|
|
118
|
+
if (newScripts.length) signals.push({ id: 'new-install-script', detail: `New install-time script(s): ${newScripts.map((s) => `${s}: ${String(v.scripts[s]).slice(0, 120)}`).join('; ')}.`, scripts: newScripts });
|
|
119
|
+
const newDeps = Object.keys(v.dependencies ?? {}).filter((d) => !(d in (pv.dependencies ?? {})));
|
|
120
|
+
if (newDeps.length) {
|
|
121
|
+
recentNewDeps = await newDependencySignal(reg, newDeps, now, notes, (d) => reg.npmCreated(d));
|
|
122
|
+
signals.push({ id: 'new-dependencies', detail: `New dependencies compared with ${name}@${prev}: ${newDeps.join(', ')}.${recentNewDeps.length ? ` Created under 30 days ago: ${recentNewDeps.map((d) => `${d.name} (${d.ageDays} days old)`).join(', ')}.` : ''}`, names: newDeps, recent: recentNewDeps });
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
// A first release has nothing to compare with, but an install-time script in it is still new to everyone who installs it.
|
|
126
|
+
newScripts = INSTALL_SCRIPTS.filter((s) => v.scripts?.[s]);
|
|
127
|
+
if (newScripts.length) signals.push({ id: 'new-install-script', detail: `First release of ${name}, and it has install-time script(s): ${newScripts.map((s) => `${s}: ${String(v.scripts[s]).slice(0, 120)}`).join('; ')}.`, scripts: newScripts });
|
|
128
|
+
notes.push('no earlier release to compare with, so provenance, publisher and dependency changes were not evaluated');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (added) {
|
|
132
|
+
const created = ms(times.created);
|
|
133
|
+
const reasons = [];
|
|
134
|
+
if (created !== null && now - created < NEW_PACKAGE_MS) reasons.push(`the package was created ${days(now - created)} ago`);
|
|
135
|
+
try {
|
|
136
|
+
const dl = await reg.npmDownloads(name);
|
|
137
|
+
if (dl && Number.isFinite(dl.downloads) && dl.downloads < LOW_WEEKLY_DOWNLOADS) reasons.push(`it had ${dl.downloads} downloads in the last week`);
|
|
138
|
+
if (dl === null) notes.push('npm download counts are not available for this package');
|
|
139
|
+
} catch (e) { notes.push(`weekly downloads could not be read: ${e.message}`); }
|
|
140
|
+
if (reasons.length) signals.push({ id: 'low-reputation', detail: `New to this project, and ${reasons.join(' and ')}.` });
|
|
141
|
+
}
|
|
142
|
+
return { checked: true, signals, notes, comparison, facts: { published: times[version] ?? null, newScripts, recentNewDeps: recentNewDeps.map((d) => d.name) } };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const requiresNames = (info) => [...new Set((info?.requires_dist ?? [])
|
|
146
|
+
.filter((r) => !/;\s*.*\bextra\s*==/.test(r))
|
|
147
|
+
.map((r) => /^([A-Za-z0-9][A-Za-z0-9._-]*)/.exec(r)?.[1])
|
|
148
|
+
.filter(Boolean)
|
|
149
|
+
.map((n) => n.toLowerCase().replace(/[-_.]+/g, '-')))];
|
|
150
|
+
|
|
151
|
+
export async function analyzePyPI(reg, { name, version, prevVersion, added }) {
|
|
152
|
+
const now = reg.nowMs;
|
|
153
|
+
let vj; let project;
|
|
154
|
+
try {
|
|
155
|
+
vj = await reg.pypiVersion(name, version);
|
|
156
|
+
project = await reg.pypiProject(name);
|
|
157
|
+
} catch (e) { return { checked: false, reason: `PyPI lookup failed: ${e.message}` }; }
|
|
158
|
+
const notes = ['PyPI provenance and publisher are not exposed in the PyPI JSON API, so provenance-drop and publisher-change were not evaluated'];
|
|
159
|
+
if (vj === null || project === null) {
|
|
160
|
+
return { checked: true, signals: [{ id: 'removed', detail: `${name}==${version} is not on PyPI (HTTP 404). Removed releases are often malware takedowns.` }], notes, comparison: null };
|
|
161
|
+
}
|
|
162
|
+
const signals = [];
|
|
163
|
+
const files = vj.urls ?? [];
|
|
164
|
+
if (files.length && files.every((f) => f.yanked)) {
|
|
165
|
+
signals.push({ id: 'yanked', detail: `${name}==${version} was yanked by its maintainers${files[0].yanked_reason ? `: ${files[0].yanked_reason}` : ''}.` });
|
|
166
|
+
}
|
|
167
|
+
const published = earliestUpload(files);
|
|
168
|
+
const age = published === null ? null : now - published;
|
|
169
|
+
const fresh = age !== null && age >= 0 && age < FRESH_MS;
|
|
170
|
+
if (fresh) signals.push({ id: 'fresh', detail: `${name}==${version} was uploaded ${hours(age)} ago (under 72 hours).` });
|
|
171
|
+
|
|
172
|
+
const releases = project.releases ?? {};
|
|
173
|
+
const timeOf = (x) => earliestUpload(releases[x]);
|
|
174
|
+
const prevLock = prevVersion && releases[prevVersion] ? prevVersion : null;
|
|
175
|
+
const prev = prevLock ?? previousByTime(Object.keys(releases), timeOf, version, (x) => !pyReleaseLike(x));
|
|
176
|
+
const comparison = prev ? { version: prev, source: prevLock ? 'base lockfile' : 'previous registry release' } : null;
|
|
177
|
+
let recentNewDeps = [];
|
|
178
|
+
if (prev) {
|
|
179
|
+
let pj = null;
|
|
180
|
+
try { pj = await reg.pypiVersion(name, prev); } catch (e) { notes.push(`comparison release metadata could not be read: ${e.message}`); }
|
|
181
|
+
if (pj) {
|
|
182
|
+
const had = new Set(requiresNames(pj.info));
|
|
183
|
+
const newDeps = requiresNames(vj.info).filter((d) => !had.has(d));
|
|
184
|
+
if (newDeps.length) {
|
|
185
|
+
recentNewDeps = await newDependencySignal(reg, newDeps, now, notes, (d) => reg.pypiCreated(d));
|
|
186
|
+
signals.push({ id: 'new-dependencies', detail: `New dependencies compared with ${name}==${prev}: ${newDeps.join(', ')}.${recentNewDeps.length ? ` Created under 30 days ago: ${recentNewDeps.map((d) => `${d.name} (${d.ageDays} days old)`).join(', ')}.` : ''}`, names: newDeps, recent: recentNewDeps });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
notes.push('no earlier release to compare with, so dependency changes were not evaluated');
|
|
191
|
+
}
|
|
192
|
+
if (added) {
|
|
193
|
+
const created = earliestUpload(Object.values(releases).flat());
|
|
194
|
+
if (created !== null && now - created < NEW_PACKAGE_MS) signals.push({ id: 'low-reputation', detail: `New to this project, and the package was created ${days(now - created)} ago. PyPI offers no download counts, so popularity was not checked.` });
|
|
195
|
+
}
|
|
196
|
+
return { checked: true, signals, notes, comparison, facts: { published: published === null ? null : new Date(published).toISOString(), newScripts: [], recentNewDeps: recentNewDeps.map((d) => d.name) } };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { SIGNAL_FIX_PIN };
|