@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,381 @@
|
|
|
1
|
+
// Shared secret-scanning helpers (REQ-012): a Betterleaks wrapper, git/ignore handling and finding builders.
|
|
2
|
+
// Used by secrets-tree, secrets-history and secrets-build, and reusable by the live stage for served JavaScript
|
|
3
|
+
// (scanSecretsInDirectory, scanSecretsInBuffer). Raw secret values never leave this module: hits carry only a redaction.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { listFiles } from '../../core/detect.mjs';
|
|
8
|
+
import { excludeNested } from '../../core/nested.mjs';
|
|
9
|
+
import { gitFileLists, MAX_LIST_BYTES, prunedSpecs } from './gitfiles.mjs';
|
|
10
|
+
import { redact, scrubText, severityRank, sha256 } from '../../core/findings.mjs';
|
|
11
|
+
|
|
12
|
+
const TOOL = 'betterleaks';
|
|
13
|
+
const posix = (p) => String(p).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
14
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
15
|
+
|
|
16
|
+
/** Directories that never hold project secrets worth scanning (huge, generated or vsg's own output). */
|
|
17
|
+
export const ALWAYS_SKIP = [String.raw`(?:^|/)(?:node_modules|\.git|\.venv|venv|__pycache__|\.pnpm-store)/`, String.raw`^\.vsg/`];
|
|
18
|
+
|
|
19
|
+
// ---- git helpers: argument arrays only, repository-controlled config that can run code is switched off ----
|
|
20
|
+
const GIT = ['-c', 'core.fsmonitor=false', '--no-optional-locks'];
|
|
21
|
+
export const git = (ctx, args, o = {}) => ctx.runProcess({ command: 'git', args: [...GIT, ...args], cwd: o.cwd ?? ctx.repo, timeoutMs: o.timeoutMs ?? 60_000, ...(o.maxOutputBytes ? { maxOutputBytes: o.maxOutputBytes } : {}) });
|
|
22
|
+
const nulList = (s) => s.split('\0').filter(Boolean);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* { usable, hasCommits, shallow }. `usable` means the directory has its own git view and is not itself hidden by a parent's .gitignore
|
|
26
|
+
* (a fixture under a gitignored worktree path must still be scanned).
|
|
27
|
+
*/
|
|
28
|
+
export async function gitState(ctx) {
|
|
29
|
+
const inside = await git(ctx, ['rev-parse', '--is-inside-work-tree']);
|
|
30
|
+
if (inside.status !== 'ran' || inside.exitCode !== 0 || inside.stdout.trim() !== 'true') return { usable: false, hasCommits: false, shallow: false };
|
|
31
|
+
const ignored = await git(ctx, ['check-ignore', '-q', '.']);
|
|
32
|
+
if (ignored.status !== 'ran' || ignored.exitCode !== 1) return { usable: false, hasCommits: false, shallow: false };
|
|
33
|
+
const head = await git(ctx, ['rev-parse', '--verify', '-q', 'HEAD']);
|
|
34
|
+
const shallow = await git(ctx, ['rev-parse', '--is-shallow-repository']);
|
|
35
|
+
return { usable: true, hasCommits: head.exitCode === 0, shallow: shallow.stdout.trim() === 'true' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Untracked paths that .gitignore hides (directories end with "/"). Empty when git is unusable.
|
|
40
|
+
* Dependency and cache directories are not listed (ALWAYS_SKIP covers them), which also keeps git from walking them.
|
|
41
|
+
*/
|
|
42
|
+
export async function ignoredPaths(ctx) {
|
|
43
|
+
const n = excludeNested(ctx.nestedRepos);
|
|
44
|
+
const r = await git(ctx, ['ls-files', '-z', '--others', '--ignored', '--exclude-standard', '--directory', ...(n.length ? n : ['--', '.']), ...prunedSpecs()], { maxOutputBytes: MAX_LIST_BYTES });
|
|
45
|
+
return r.status === 'ran' && r.exitCode === 0 && !r.truncated ? nulList(r.stdout).map(posix) : [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Files to read for static checks: { files, tracked (Set, or null without git), git }. Respects .gitignore. Untracked files inside
|
|
50
|
+
* dependency and cache directories (node_modules and similar) are left out; tracked files are always included.
|
|
51
|
+
* A git listing that cannot be read completely falls back to a directory walk, never to a partial list.
|
|
52
|
+
*/
|
|
53
|
+
export async function repoFiles(ctx, state) {
|
|
54
|
+
const st = state ?? await gitState(ctx);
|
|
55
|
+
if (st.usable) {
|
|
56
|
+
const lists = await gitFileLists(ctx);
|
|
57
|
+
if (lists) {
|
|
58
|
+
const isFile = (f) => { try { return fs.statSync(path.join(ctx.repo, f)).isFile(); } catch { return false; } };
|
|
59
|
+
return { files: [...new Set([...lists.tracked, ...lists.untracked].map(posix))].filter(isFile).sort(), tracked: new Set(lists.tracked.map(posix)), git: true };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { files: listFiles(ctx.repo), tracked: null, git: false };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Regex path allowlist entries (RE2) for everything .gitignore hides plus the always-skipped directories. */
|
|
66
|
+
export const skipPatterns = (ignored, exactFiles = []) => [
|
|
67
|
+
...ALWAYS_SKIP,
|
|
68
|
+
...ignored.filter((p) => !p.includes("'''")).map((p) => (p.endsWith('/') ? `^${escapeRe(p)}` : `^${escapeRe(p)}$`)),
|
|
69
|
+
...exactFiles.filter((p) => !p.includes("'''")).map((p) => `^${escapeRe(p)}$`),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The scan configuration is ours, never the repository's: a repo-supplied .betterleaks.toml, .betterleaksignore or
|
|
74
|
+
* inline allow comment could silence findings. Risk is accepted only through vsg.accept.json.
|
|
75
|
+
*/
|
|
76
|
+
const configToml = (skip) => `[extend]\nuseDefault = true\n\n[[allowlists]]\ndescription = "vsg path exclusions"\npaths = [\n${skip.map((s) => ` '''${s}''',`).join('\n')}\n]\n`;
|
|
77
|
+
|
|
78
|
+
// ---- noise filter: generic rules fire on ordinary words in UI translation files ----
|
|
79
|
+
/** Localization files: a translations directory holding a data file. */
|
|
80
|
+
const LOCALIZATION_FILE = /(?:^|\/)(?:i18n|locales?|l10n|translations?|lang|languages)\/(?:.+\/)?[^/]+\.(?:json|ya?ml|po|properties|xlf|arb)$/i;
|
|
81
|
+
/** Betterleaks catch-all rules (generic-password, generic-api-key ...). Provider rules (stripe, github, aws ...) are never filtered. */
|
|
82
|
+
const GENERIC_RULE = /^generic-/i;
|
|
83
|
+
|
|
84
|
+
function shannonEntropy(s) {
|
|
85
|
+
const counts = new Map();
|
|
86
|
+
for (const c of s) counts.set(c, (counts.get(c) ?? 0) + 1);
|
|
87
|
+
let h = 0;
|
|
88
|
+
for (const n of counts.values()) h -= (n / s.length) * Math.log2(n / s.length);
|
|
89
|
+
return h;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** True for a value that reads as ordinary text rather than a credential: spaces, letters only, non-ASCII text (credentials are ASCII), or low entropy. */
|
|
93
|
+
export const looksLikeWords = (secret) => /\s/.test(secret) || /[^\x00-\x7f]/.test(secret) || /^[\p{L}\p{M}'’]+$/u.test(secret) || shannonEntropy(secret) < 3;
|
|
94
|
+
|
|
95
|
+
/** A generic-rule hit in a localization file whose value is ordinary words (a translated "Password" label, not a password). */
|
|
96
|
+
export const isLocalizationNoise = (ruleId, file, secret) => GENERIC_RULE.test(ruleId) && LOCALIZATION_FILE.test(file) && looksLikeWords(secret);
|
|
97
|
+
|
|
98
|
+
// ---- false-positive suppression (REQ-037) ----
|
|
99
|
+
// Every rule below runs where the raw value is still in hand (toHit); nothing here keeps the value. Provider-specific rules (AWS, Stripe,
|
|
100
|
+
// GitHub, OpenAI, Anthropic, Supabase service role, Slack ...) are never touched by the generic-only rules. The one rule that applies to
|
|
101
|
+
// every rule is the long-base64-run test, and it can only drop a value that is itself plain base64 inside a 200+ character base64 run.
|
|
102
|
+
|
|
103
|
+
/** Common placeholder passwords, compared by the value's hash prefix (lower-cased) so no list of plaintext values is used. */
|
|
104
|
+
const PLACEHOLDER_HASHES = new Set(['postgres', 'password', 'secret', 'pass', 'changeme', 'example', 'test', 'admin', 'root'].map((w) => sha256(w).slice(0, 12)));
|
|
105
|
+
export const isPlaceholderValue = (secret) => PLACEHOLDER_HASHES.has(sha256(String(secret).toLowerCase()).slice(0, 12));
|
|
106
|
+
|
|
107
|
+
/** Hosts that mean "this machine or its container network": a credential URI to one of them is a local development URI. */
|
|
108
|
+
export function isLocalHost(host) {
|
|
109
|
+
const h = String(host ?? '').trim().toLowerCase().replace(/^\[|\]$/g, '');
|
|
110
|
+
return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === 'host.docker.internal' || h.endsWith('.local');
|
|
111
|
+
}
|
|
112
|
+
const URI_HOST = /^[a-z][a-z0-9+.-]*:\/\/[^/@\s]*@(\[[^\]]+\]|[^:/?#\s]+)/i;
|
|
113
|
+
const uriHostOf = (r) => String(r.CaptureGroups?.host ?? URI_HOST.exec(String(r.Match ?? ''))?.[1] ?? '');
|
|
114
|
+
|
|
115
|
+
const b64urlJson = (part) => { try { return JSON.parse(Buffer.from(part, 'base64url').toString('utf8')); } catch { return null; } };
|
|
116
|
+
/** Claims of a JWT-shaped value, or null. The signature is not checked: this only reads which public key kind a token is. */
|
|
117
|
+
export function jwtClaims(value) {
|
|
118
|
+
const parts = String(value).split('.');
|
|
119
|
+
if (parts.length !== 3 || !parts.every((p) => /^[A-Za-z0-9_-]+$/.test(p))) return null;
|
|
120
|
+
const header = b64urlJson(parts[0]);
|
|
121
|
+
const claims = b64urlJson(parts[1]);
|
|
122
|
+
return header && typeof header === 'object' && claims && typeof claims === 'object' && !Array.isArray(claims) ? claims : null;
|
|
123
|
+
}
|
|
124
|
+
const SUPABASE_PUBLISHABLE = /^sb_publishable_[A-Za-z0-9_-]{8,}$/;
|
|
125
|
+
const SUPABASE_ANON_HEADER = '{"alg":"HS256","typ":"JWT"}';
|
|
126
|
+
const SUPABASE_ANON_CLAIMS = ['iss', 'role', 'iat', 'exp'];
|
|
127
|
+
// Hosted Supabase includes the project ref between iss and role; the local key omits it.
|
|
128
|
+
const SUPABASE_ANON_CLAIMS_WITH_REF = ['iss', 'ref', 'role', 'iat', 'exp'];
|
|
129
|
+
const decodedJwtPart = (part) => Buffer.from(part, 'base64url').toString('utf8');
|
|
130
|
+
/** Strict legacy anon shape. Signature validity is intentionally not checked. */
|
|
131
|
+
function isCanonicalSupabaseAnonJwt(value) {
|
|
132
|
+
const parts = String(value ?? '').split('.');
|
|
133
|
+
if (parts.length !== 3 || !parts.every((p) => /^[A-Za-z0-9_-]+$/.test(p))) return false;
|
|
134
|
+
let header;
|
|
135
|
+
let claims;
|
|
136
|
+
let headerText;
|
|
137
|
+
let claimsText;
|
|
138
|
+
try {
|
|
139
|
+
headerText = decodedJwtPart(parts[0]);
|
|
140
|
+
claimsText = decodedJwtPart(parts[1]);
|
|
141
|
+
header = JSON.parse(headerText);
|
|
142
|
+
claims = JSON.parse(claimsText);
|
|
143
|
+
} catch { return false; }
|
|
144
|
+
if (headerText !== SUPABASE_ANON_HEADER || JSON.stringify(header) !== headerText) return false;
|
|
145
|
+
if (!claims || typeof claims !== 'object' || Array.isArray(claims) || JSON.stringify(claims) !== claimsText) return false;
|
|
146
|
+
const keys = Object.keys(claims);
|
|
147
|
+
if (keys.join('\0') !== SUPABASE_ANON_CLAIMS.join('\0') && keys.join('\0') !== SUPABASE_ANON_CLAIMS_WITH_REF.join('\0')) return false;
|
|
148
|
+
if (claims.iss !== 'supabase' || claims.role !== 'anon') return false;
|
|
149
|
+
if (!Number.isSafeInteger(claims.iat) || claims.iat <= 0 || !Number.isSafeInteger(claims.exp) || claims.exp <= claims.iat) return false;
|
|
150
|
+
return claims.ref === undefined || (typeof claims.ref === 'string' && /^[A-Za-z0-9_-]{1,64}$/.test(claims.ref));
|
|
151
|
+
}
|
|
152
|
+
/** Classifies only Supabase's low-privilege public API key shapes; ambiguous JWTs remain secrets. */
|
|
153
|
+
export function supabasePublicKeyKind(value) {
|
|
154
|
+
const text = String(value ?? '');
|
|
155
|
+
if (SUPABASE_PUBLISHABLE.test(text)) return 'publishable key';
|
|
156
|
+
return isCanonicalSupabaseAnonJwt(text) ? 'anon key' : '';
|
|
157
|
+
}
|
|
158
|
+
/** The public Supabase local-development keys (issuer "supabase-demo") ship with every local stack and unlock nothing real. */
|
|
159
|
+
export const isSupabaseDemoJwt = (value) => jwtClaims(value)?.iss === 'supabase-demo';
|
|
160
|
+
|
|
161
|
+
const B64_CHAR = /[A-Za-z0-9+/=]/;
|
|
162
|
+
export const BASE64_RUN_MIN = 200;
|
|
163
|
+
/** True when every occurrence of `secret` in `line` is plain base64 with at least `min` base64 characters on each side (embedded WASM, images, fonts). */
|
|
164
|
+
export function insideBase64Run(line, secret, min = BASE64_RUN_MIN) {
|
|
165
|
+
if (!secret || !/^[A-Za-z0-9+/=]+$/.test(secret)) return false;
|
|
166
|
+
let found = false;
|
|
167
|
+
for (let i = line.indexOf(secret); i >= 0; i = line.indexOf(secret, i + 1)) {
|
|
168
|
+
found = true;
|
|
169
|
+
let left = 0;
|
|
170
|
+
while (left < min && i - 1 - left >= 0 && B64_CHAR.test(line[i - 1 - left])) left++;
|
|
171
|
+
const end = i + secret.length;
|
|
172
|
+
let right = 0;
|
|
173
|
+
while (right < min && end + right < line.length && B64_CHAR.test(line[end + right])) right++;
|
|
174
|
+
if (left < min || right < min) return false;
|
|
175
|
+
}
|
|
176
|
+
return found;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Why a hit is not a finding ('' when it is): demo-jwt, base64-run, placeholder, local-uri or localization. */
|
|
180
|
+
function suppressionOf(r, file, secret, readLine) {
|
|
181
|
+
if (isSupabaseDemoJwt(secret)) return 'demo-jwt';
|
|
182
|
+
const rule = r.RuleID ?? 'unknown';
|
|
183
|
+
// Betterleaks provider rules retain their guardrail; only generic/JWT-shaped detections get this shape classification.
|
|
184
|
+
if ((GENERIC_RULE.test(rule) || /^jwt(?:-|$)/i.test(rule)) && supabasePublicKeyKind(secret)) return 'supabase-public-key';
|
|
185
|
+
if (/^[A-Za-z0-9+/=]+$/.test(secret) && readLine) {
|
|
186
|
+
const line = readLine(file, r.StartLine);
|
|
187
|
+
if (line !== null && insideBase64Run(line, secret)) return 'base64-run';
|
|
188
|
+
}
|
|
189
|
+
if (isLocalizationNoise(rule, file, secret)) return 'localization';
|
|
190
|
+
if (GENERIC_RULE.test(rule)) {
|
|
191
|
+
if (isPlaceholderValue(secret)) return 'placeholder';
|
|
192
|
+
const host = uriHostOf(r);
|
|
193
|
+
if (host && isLocalHost(host)) return 'local-uri';
|
|
194
|
+
}
|
|
195
|
+
return '';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Reads one line of a file under root (cached per file); null when unreadable. Used only to look around a hit. */
|
|
199
|
+
function lineReader(root) {
|
|
200
|
+
const cache = new Map();
|
|
201
|
+
return (file, line) => {
|
|
202
|
+
if (!cache.has(file)) {
|
|
203
|
+
try { cache.set(file, fs.readFileSync(path.join(root, file), 'utf8').split(/\r?\n/)); } catch { cache.set(file, null); }
|
|
204
|
+
}
|
|
205
|
+
const lines = cache.get(file);
|
|
206
|
+
return lines ? (Number.isInteger(line) && line > 0 ? lines[line - 1] ?? null : lines.join('\n')) : null;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** One normalized hit: the secret itself is reduced to its redaction and a short hash. `suppressed` names why it is not a finding. */
|
|
211
|
+
function toHit(r, cwd, readLine) {
|
|
212
|
+
const secret = String(r.Secret ?? '');
|
|
213
|
+
const publicKeyKind = supabasePublicKeyKind(secret);
|
|
214
|
+
let file = posix(r.File ?? r.Attributes?.path ?? '');
|
|
215
|
+
const root = posix(path.resolve(cwd)).replace(/\/$/, '');
|
|
216
|
+
if (file.toLowerCase().startsWith(`${root.toLowerCase()}/`)) file = file.slice(root.length + 1);
|
|
217
|
+
return {
|
|
218
|
+
ruleId: r.RuleID ?? 'unknown',
|
|
219
|
+
description: String(r.Description ?? ''),
|
|
220
|
+
file,
|
|
221
|
+
line: Number.isInteger(r.StartLine) && r.StartLine > 0 ? r.StartLine : undefined,
|
|
222
|
+
redacted: redact(secret),
|
|
223
|
+
hash: sha256(secret).slice(0, 12),
|
|
224
|
+
suppressed: suppressionOf(r, file, secret, readLine),
|
|
225
|
+
entropy: typeof r.Entropy === 'number' ? Math.round(r.Entropy * 100) / 100 : undefined,
|
|
226
|
+
confidence: ['high', 'medium', 'low'].includes(r.Attributes?.confidence) ? r.Attributes.confidence : 'medium',
|
|
227
|
+
validation: validationOf(r.ValidationStatus, r.ValidationReason),
|
|
228
|
+
...(publicKeyKind ? { publicKeyKind } : {}),
|
|
229
|
+
commit: r.Commit ? String(r.Commit) : undefined,
|
|
230
|
+
date: r.Date ? String(r.Date) : undefined,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** valid = live credential; inactive = checked and invalid or revoked; unvalidated = not checked or inconclusive. */
|
|
235
|
+
export function validationOf(status, reason) {
|
|
236
|
+
const s = String(status ?? '').toLowerCase();
|
|
237
|
+
const state = s === 'valid' ? 'valid' : s === 'invalid' || s === 'revoked' ? 'inactive' : 'unvalidated';
|
|
238
|
+
return { state, status: s || 'none', reason: String(reason ?? '') };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Same secret, rule and file reported repeatedly (for example in several commits) becomes one hit with its commits. */
|
|
242
|
+
function mergeHits(hits) {
|
|
243
|
+
const byKey = new Map();
|
|
244
|
+
for (const h of hits) {
|
|
245
|
+
const k = `${h.ruleId}\0${h.file}\0${h.hash}`;
|
|
246
|
+
const seen = byKey.get(k);
|
|
247
|
+
if (!seen) { byKey.set(k, { ...h, commits: h.commit ? [h.commit] : [] }); continue; }
|
|
248
|
+
if (h.commit && !seen.commits.includes(h.commit)) seen.commits.push(h.commit);
|
|
249
|
+
if (h.validation.state === 'valid') seen.validation = h.validation;
|
|
250
|
+
}
|
|
251
|
+
return [...byKey.values()].map(({ commit: _c, ...h }) => h);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Drops suppressed hits. Returns { hits, filtered (localization noise, the original counter), suppressed (counts per reason, distinct hits),
|
|
256
|
+
* demoJwts ([{ file, line }] for the Supabase local demo key, reported once as info), and publicKeys }.
|
|
257
|
+
*/
|
|
258
|
+
function dropNoise(hits) {
|
|
259
|
+
const suppressed = { placeholder: 0, localUri: 0, base64Run: 0, demoJwt: 0, publicKey: 0 };
|
|
260
|
+
const key = { placeholder: 'placeholder', 'local-uri': 'localUri', 'base64-run': 'base64Run', 'demo-jwt': 'demoJwt', 'supabase-public-key': 'publicKey' };
|
|
261
|
+
const demoJwts = [];
|
|
262
|
+
const publicKeys = [];
|
|
263
|
+
let filtered = 0;
|
|
264
|
+
const kept = [];
|
|
265
|
+
for (const { suppressed: why, ...h } of hits) {
|
|
266
|
+
if (!why) kept.push(h);
|
|
267
|
+
else if (why === 'localization') filtered++;
|
|
268
|
+
else {
|
|
269
|
+
suppressed[key[why]]++;
|
|
270
|
+
if (why === 'demo-jwt') demoJwts.push({ file: h.file, line: h.line });
|
|
271
|
+
if (why === 'supabase-public-key') publicKeys.push({ file: h.file, line: h.line, kind: h.publicKeyKind });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return { hits: kept, filtered, suppressed, demoJwts, publicKeys };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Runs Betterleaks. kind: 'dir' (paths relative to cwd), 'git' (full history of cwd) or 'stdin' (input).
|
|
279
|
+
* Returns { status: 'ran', hits, filtered, suppressed, demoJwts, publicKeys } (see dropNoise) or { status: 'not_run', reason }. Exit 0 and 1 (leaks found) are success; anything else is a tool failure.
|
|
280
|
+
*/
|
|
281
|
+
export async function runBetterleaks(ctx, { kind, paths = ['.'], cwd = ctx.repo, input, validate = false, skip = ALWAYS_SKIP, timeoutSec }) {
|
|
282
|
+
const work = fs.mkdtempSync(path.join(os.tmpdir(), 'vsg-bl-'));
|
|
283
|
+
try {
|
|
284
|
+
const cfg = path.join(work, 'betterleaks.toml');
|
|
285
|
+
// other checkouts inside the scanned tree (worktrees, clones) are not this repository; a history scan never reads them by path
|
|
286
|
+
const nested = kind === 'dir' && cwd === ctx.repo ? (ctx.nestedRepos ?? []).map((n) => `^${escapeRe(n.path)}/`) : [];
|
|
287
|
+
fs.writeFileSync(cfg, configToml([...skip, ...nested.filter((p) => !p.includes("'''"))]));
|
|
288
|
+
const args = [kind, '--no-banner', '--no-color', '-l', 'error', '-c', cfg, '-i', work, '--ignore-gitleaks-allow',
|
|
289
|
+
'--max-target-megabytes', '10', '-f', 'json', '-r', '-', ...(validate ? ['--validation'] : [])];
|
|
290
|
+
if (kind === 'dir') args.push(...paths);
|
|
291
|
+
if (kind === 'git') args.push('.');
|
|
292
|
+
const res = await ctx.runTool(TOOL, args, { cwd, input, timeoutSec });
|
|
293
|
+
if (res.status !== 'ran') return { status: 'not_run', reason: res.reason };
|
|
294
|
+
if (res.truncated) return { status: 'not_run', reason: 'Betterleaks output was too large to read completely.' };
|
|
295
|
+
if (res.exitCode !== 0 && res.exitCode !== 1) {
|
|
296
|
+
const tail = scrubText(res.stderr.trim().split(/\r?\n/).slice(-2).join(' | '));
|
|
297
|
+
return { status: 'not_run', reason: `Betterleaks failed with exit code ${res.exitCode}${tail ? `: ${tail}` : ''}.` };
|
|
298
|
+
}
|
|
299
|
+
let parsed;
|
|
300
|
+
try { parsed = res.stdout.trim() ? JSON.parse(res.stdout) : []; } catch { return { status: 'not_run', reason: 'Betterleaks did not return valid JSON.' }; }
|
|
301
|
+
if (!Array.isArray(parsed)) return { status: 'not_run', reason: 'Betterleaks returned an unexpected report shape.' };
|
|
302
|
+
const readLine = kind === 'dir' ? lineReader(cwd) : null;
|
|
303
|
+
return { status: 'ran', ...dropNoise(mergeHits(parsed.map((r) => toHit(r, cwd, readLine)))) };
|
|
304
|
+
} finally {
|
|
305
|
+
fs.rmSync(work, { recursive: true, force: true });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Reusable for the live stage: scans a directory (for example downloaded JavaScript). Paths in hits are relative to dir. */
|
|
310
|
+
export const scanSecretsInDirectory = (ctx, dir, { validate = false, skip = ALWAYS_SKIP } = {}) =>
|
|
311
|
+
runBetterleaks(ctx, { kind: 'dir', paths: ['.'], cwd: path.resolve(dir), validate, skip });
|
|
312
|
+
|
|
313
|
+
/** Reusable for the live stage: scans one in-memory document. `name` is reported as the file (for example the script URL path). */
|
|
314
|
+
export async function scanSecretsInBuffer(ctx, buffer, { name = 'stdin', validate = false } = {}) {
|
|
315
|
+
const res = await runBetterleaks(ctx, { kind: 'stdin', input: buffer, validate });
|
|
316
|
+
if (res.status === 'ran') for (const h of res.hits) h.file = name;
|
|
317
|
+
return res;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const CONFIDENCE = { high: 'high', medium: 'medium', low: 'low' };
|
|
321
|
+
|
|
322
|
+
export const isGenericRule = (ruleId) => GENERIC_RULE.test(ruleId);
|
|
323
|
+
export const LOW_ENTROPY = 3.5;
|
|
324
|
+
/** A generic catch-all hit that is not a validated live credential and is low confidence or low entropy: reported, but it cannot block. */
|
|
325
|
+
export const isLowSignalGeneric = (hit) => GENERIC_RULE.test(hit.ruleId) && hit.validation.state !== 'valid'
|
|
326
|
+
&& (hit.confidence === 'low' || (hit.entropy !== undefined && hit.entropy < LOW_ENTROPY));
|
|
327
|
+
|
|
328
|
+
/** The severity a hit is reported at: low-signal generic hits are capped at Medium. */
|
|
329
|
+
export const effectiveSeverity = (hit, severity) => (isLowSignalGeneric(hit) && severityRank(severity) > severityRank('medium') ? 'medium' : severity);
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Raw finding (input for makeFinding) from a hit. Nothing here can carry the secret: evidence is the redaction.
|
|
333
|
+
* opts: { category, severity, where (words), fix }
|
|
334
|
+
*/
|
|
335
|
+
export function hitToFinding(hit, { category, severity, where, fix, extra = '' }) {
|
|
336
|
+
const state = hit.validation.state;
|
|
337
|
+
const confidence = state === 'valid' ? 'confirmed' : CONFIDENCE[hit.confidence] ?? 'medium';
|
|
338
|
+
const verdict = state === 'valid' ? ' Betterleaks confirmed this credential is live.'
|
|
339
|
+
: state === 'inactive' ? ` Betterleaks checked it and the issuer reports it inactive (${hit.validation.status}).` : '';
|
|
340
|
+
const low = isLowSignalGeneric(hit);
|
|
341
|
+
const lowNote = low ? ` Low-signal generic match (${hit.confidence === 'low' ? 'confidence low' : `entropy ${hit.entropy}, under ${LOW_ENTROPY}`}): reported as Medium and does not block. Check whether it is a real credential.` : '';
|
|
342
|
+
return {
|
|
343
|
+
category, severity: effectiveSeverity(hit, severity), confidence, tool: TOOL, ruleId: hit.ruleId, key: `${hit.ruleId}:${hit.hash}`,
|
|
344
|
+
location: { file: hit.file, line: hit.line },
|
|
345
|
+
title: `Secret ${where}: ${hit.ruleId}`,
|
|
346
|
+
description: `${hit.description || `Betterleaks rule ${hit.ruleId} matched.`}${verdict}${lowNote}${extra}`,
|
|
347
|
+
evidence: `${hit.redacted} (rule ${hit.ruleId}${hit.entropy !== undefined ? `, entropy ${hit.entropy}` : ''})`,
|
|
348
|
+
refs: [],
|
|
349
|
+
fix,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** One info finding for the Supabase local demo key (public, ships with every local stack), or null when the scan saw none. */
|
|
354
|
+
export function demoJwtFinding(demoJwts, { category = 'secret', where }) {
|
|
355
|
+
if (!demoJwts?.length) return null;
|
|
356
|
+
const shown = demoJwts.slice(0, 10).map((d) => `${d.file}${d.line ? `:${d.line}` : ''}`);
|
|
357
|
+
return {
|
|
358
|
+
category, severity: 'info', confidence: 'high', tool: TOOL, ruleId: 'supabase-demo-jwt', key: 'supabase-demo-jwt',
|
|
359
|
+
location: { file: demoJwts[0].file, line: demoJwts[0].line },
|
|
360
|
+
title: `Supabase local demo key ${where}`,
|
|
361
|
+
description: 'A JWT whose issuer is "supabase-demo" is the public development key every local Supabase stack ships with. It unlocks nothing outside a local stack, so it is not a secret. Reported once.',
|
|
362
|
+
evidence: `${demoJwts.length} occurrence(s): ${shown.join(', ')}${demoJwts.length > shown.length ? `, and ${demoJwts.length - shown.length} more` : ''}`,
|
|
363
|
+
refs: [], fix: 'No action needed. Make sure no real project key sits next to it.',
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** One info finding for Supabase anon/publishable keys, which are intended for public clients. */
|
|
368
|
+
export function supabasePublicKeyFinding(publicKeys, { category = 'secret', where }) {
|
|
369
|
+
if (!publicKeys?.length) return null;
|
|
370
|
+
const shown = publicKeys.slice(0, 10).map((k) => `${k.kind ?? 'public key'} at ${k.file}${k.line ? `:${k.line}` : ''}`);
|
|
371
|
+
return {
|
|
372
|
+
category, severity: 'info', confidence: 'high', tool: TOOL, ruleId: 'supabase-public-key', key: 'supabase-public-key',
|
|
373
|
+
location: { file: publicKeys[0].file, line: publicKeys[0].line },
|
|
374
|
+
title: `Supabase public key ${where}`,
|
|
375
|
+
description: 'A Supabase anon or publishable API key is intended for public clients and does not grant elevated access. Keep Row Level Security and least-privilege grants enabled.',
|
|
376
|
+
evidence: `${publicKeys.length} occurrence(s): ${shown.join(', ')}${publicKeys.length > shown.length ? `, and ${publicKeys.length - shown.length} more` : ''}`,
|
|
377
|
+
refs: [], fix: 'No secret rotation is needed for this public key. Confirm the project uses Row Level Security and least-privilege grants.',
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export const ROTATE = 'Treat the value as exposed: revoke or rotate it at the issuer first, then remove it from the code and load it from an environment variable or a secret manager.';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Explicit scan targets for tools that walk "." themselves. Opengrep (with --no-git-ignore) and Betterleaks still traverse a directory they
|
|
2
|
+
// are told to exclude, so a tree with 100 agent worktrees (millions of node_modules files) takes tens of minutes even though every file in
|
|
3
|
+
// them is excluded. Handing the tool the directory's entries instead, and leaving the dropped paths out, avoids that traversal.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { ignoredUntracked } from '../../core/nested.mjs';
|
|
7
|
+
|
|
8
|
+
const MAX_TARGETS = 600; // keep the command line short; past this the plain "." scan (with its exclusions) is the safer choice
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Same as scanTargets, and when `skipIgnored` is set also leaves out the directories among the targets that git ignores and the repository
|
|
12
|
+
* does not track (build output, browser profiles). Returns { targets, ignoredDirs, prunedDirs } (prunedDirs: pruned-name directories below
|
|
13
|
+
* the top level that were left out, for the coverage evidence).
|
|
14
|
+
*/
|
|
15
|
+
export function scanTargetsWithIgnored(repo, dropPaths, pruneNames, skipIgnored) {
|
|
16
|
+
const { targets, prunedDirs } = planTargets(repo, dropPaths, pruneNames);
|
|
17
|
+
if (!skipIgnored || targets[0] === '.') return { targets, ignoredDirs: [], prunedDirs };
|
|
18
|
+
const isDir = (t) => { try { return fs.statSync(path.join(repo, t)).isDirectory(); } catch { return false; } };
|
|
19
|
+
const rel = (t) => (t.startsWith('./') ? t.slice(2) : t);
|
|
20
|
+
const ignoredDirs = ignoredUntracked(repo, targets.filter(isDir).map(rel));
|
|
21
|
+
const out = targets.filter((t) => !ignoredDirs.includes(rel(t)));
|
|
22
|
+
return out.length ? { targets: out, ignoredDirs, prunedDirs } : { targets, ignoredDirs: [], prunedDirs };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const MAX_DEPTH = 10;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Directories named in `pruneNames` that sit below the top level (for example an untracked project's own node_modules), as root-relative
|
|
29
|
+
* posix paths. Opengrep still walks a directory it is told to exclude (40,000 files of one nested node_modules cost 40 seconds), so these
|
|
30
|
+
* are left out of the targets as well. Does not enter them, nested checkouts (`skip`) or symbolic links.
|
|
31
|
+
*/
|
|
32
|
+
function innerPruned(repo, skip, prune) {
|
|
33
|
+
const found = [];
|
|
34
|
+
const walk = (rel, depth) => {
|
|
35
|
+
if (depth > MAX_DEPTH) return;
|
|
36
|
+
let entries;
|
|
37
|
+
try { entries = fs.readdirSync(path.join(repo, rel), { withFileTypes: true }); } catch { return; }
|
|
38
|
+
for (const e of entries) {
|
|
39
|
+
if (e.isSymbolicLink() || !e.isDirectory()) continue;
|
|
40
|
+
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
41
|
+
if (skip.has(r)) continue;
|
|
42
|
+
if (prune.has(e.name)) { if (rel) found.push(r); continue; }
|
|
43
|
+
walk(r, depth + 1);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
walk('', 0);
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Root-relative targets ("./a/b") that cover everything under `repo` except `dropPaths` (root-relative posix paths), any directory
|
|
52
|
+
* named in `pruneNames` at the top level, and any such directory deeper down (see innerPruned). Only directories that contain a dropped
|
|
53
|
+
* path are expanded; every other entry is handed over whole.
|
|
54
|
+
* Returns ["."] when nothing is dropped, so an ordinary repository is scanned exactly as before.
|
|
55
|
+
*/
|
|
56
|
+
export const scanTargets = (repo, dropPaths = [], pruneNames = []) => planTargets(repo, dropPaths, pruneNames).targets;
|
|
57
|
+
|
|
58
|
+
/** scanTargets plus the pruned directories found below the top level: { targets, prunedDirs }. */
|
|
59
|
+
export function planTargets(repo, dropPaths = [], pruneNames = []) {
|
|
60
|
+
const prune = new Set(pruneNames);
|
|
61
|
+
const prunedDirs = prune.size ? innerPruned(repo, new Set(dropPaths), prune) : [];
|
|
62
|
+
const drop = new Set([...dropPaths, ...prunedDirs]);
|
|
63
|
+
if (!drop.size) return { targets: ['.'], prunedDirs: [] };
|
|
64
|
+
const ancestors = new Set();
|
|
65
|
+
for (const p of drop) for (let i = p.indexOf('/'); i > 0; i = p.indexOf('/', i + 1)) ancestors.add(p.slice(0, i));
|
|
66
|
+
const targets = [];
|
|
67
|
+
const expand = (rel) => {
|
|
68
|
+
let entries;
|
|
69
|
+
try { entries = fs.readdirSync(path.join(repo, rel), { withFileTypes: true }); } catch { return; }
|
|
70
|
+
for (const e of entries) {
|
|
71
|
+
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
72
|
+
if (e.isSymbolicLink() || drop.has(r) || (e.isDirectory() && prune.has(e.name))) continue;
|
|
73
|
+
if (e.isDirectory() && ancestors.has(r)) expand(r);
|
|
74
|
+
else targets.push(r.startsWith('-') ? `./${r}` : r); // plain relative paths: a path allowlist is anchored on them, so "./" would defeat it
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
expand('');
|
|
78
|
+
return targets.length && targets.length <= MAX_TARGETS ? { targets, prunedDirs } : { targets: ['.'], prunedDirs: [] };
|
|
79
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// agent-review (REQ-027, REQ-028, REQ-029): writes the agent brief, optionally runs the configured headless reviewer, and ingests
|
|
2
|
+
// <out>/agent/findings.json. Until that file exists the check is not_run, so `deep` is INCOMPLETE until the review is done.
|
|
3
|
+
// vsg never calls an LLM itself. `agentReview.command` in vsg.config.json is run exactly as configured, without a shell.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { renderBrief } from '../../agent/brief.mjs';
|
|
6
|
+
import { runCommandString } from '../../agent/exec.mjs';
|
|
7
|
+
import { agentDir, awaitingReason, briefPath, findingsPath, ingestAgentFindings, reviewIdentity, reviewIdentityError, reviewMetaPath, rotateFindings } from '../../agent/ingest.mjs';
|
|
8
|
+
import { packageRoot } from '../../core/paths.mjs';
|
|
9
|
+
import { scrubText } from '../../core/findings.mjs';
|
|
10
|
+
|
|
11
|
+
export const id = 'agent-review';
|
|
12
|
+
export const title = 'Agent review (brief and findings ingestion)';
|
|
13
|
+
export const modes = ['deep', 'release'];
|
|
14
|
+
export const requiredIn = ['deep', 'release'];
|
|
15
|
+
export const HOOK_TIMEOUT_MS = 30 * 60 * 1000;
|
|
16
|
+
// the hook, then up to 20 reproduction commands of up to 2 minutes each, plus slack
|
|
17
|
+
export const timeoutSec = HOOK_TIMEOUT_MS / 1000 + 20 * 120 + 300;
|
|
18
|
+
/** Runs after the other checks, so the brief can list this run's scanner findings. */
|
|
19
|
+
export const last = true;
|
|
20
|
+
export const applies = () => true;
|
|
21
|
+
|
|
22
|
+
const rank = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
23
|
+
|
|
24
|
+
export async function run(ctx) {
|
|
25
|
+
const dir = agentDir(ctx.out);
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
// Every deep/release run starts a fresh review. Keep any manually supplied findings recoverable, even when no hook is configured.
|
|
28
|
+
rotateFindings(ctx.out);
|
|
29
|
+
const identity = reviewIdentity(ctx.repo, ctx.runId, ctx.out);
|
|
30
|
+
fs.writeFileSync(reviewMetaPath(ctx.out), `${JSON.stringify(identity, null, 2)}\n`);
|
|
31
|
+
const prior = (ctx.priorFindings?.() ?? []).sort((a, b) => Number(b.blocking) - Number(a.blocking) || rank[a.severity] - rank[b.severity] || a.fingerprint.localeCompare(b.fingerprint));
|
|
32
|
+
fs.writeFileSync(briefPath(ctx.out), renderBrief({ repo: ctx.repo, out: ctx.out, mode: ctx.mode, detection: ctx.detection, findings: prior, vsgVersion: ctx.vsgVersion ?? '0.0.0', vsgRoot: packageRoot }));
|
|
33
|
+
ctx.log(`agent brief written to ${briefPath(ctx.out)}`);
|
|
34
|
+
|
|
35
|
+
const command = ctx.config?.agentReview?.command;
|
|
36
|
+
if (command) {
|
|
37
|
+
const extra = (process.env.VSG_AGENT_ENV ?? '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
38
|
+
const r = await runCommandString(ctx.runProcess, command, {
|
|
39
|
+
repo: ctx.repo, timeoutMs: HOOK_TIMEOUT_MS, envNames: extra,
|
|
40
|
+
extraEnv: { VSG_AGENT_BRIEF: briefPath(ctx.out), VSG_AGENT_FINDINGS: findingsPath(ctx.out) },
|
|
41
|
+
});
|
|
42
|
+
if (r.status !== 'ran') return { status: 'not_run', reason: `agentReview.command did not run to completion: ${scrubText(r.reason)}. ${awaitingReason(ctx.out)}` };
|
|
43
|
+
if (r.exitCode !== 0) return { status: 'not_run', reason: `agentReview.command exited with code ${r.exitCode}. ${awaitingReason(ctx.out)}` };
|
|
44
|
+
if (!fs.existsSync(findingsPath(ctx.out))) return { status: 'not_run', reason: `agentReview.command finished but wrote no findings file. ${awaitingReason(ctx.out)}` };
|
|
45
|
+
}
|
|
46
|
+
const identityProblem = reviewIdentityError({ repo: ctx.repo, out: ctx.out });
|
|
47
|
+
if (identityProblem) return { status: 'not_run', reason: identityProblem };
|
|
48
|
+
const res = await ingestAgentFindings({ repo: ctx.repo, out: ctx.out, runProcess: ctx.runProcess, note: ctx.log });
|
|
49
|
+
return res;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default { id, title, modes, requiredIn, timeoutSec, last, applies, run };
|