@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,617 @@
|
|
|
1
|
+
// code (REQ-013): Opengrep with a pinned general ruleset (semgrep-rules, fetched into the tools cache, never committed)
|
|
2
|
+
// plus the vsg stack rules in rules/. Reads source only; never runs the scanned project's code.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { listFiles } from '../../core/detect.mjs';
|
|
6
|
+
import { resolveTool, resolveVerifiedArchive } from '../../core/installer.mjs';
|
|
7
|
+
import { packageRoot, toolsDir } from '../../core/paths.mjs';
|
|
8
|
+
import { normalizeSarif } from '../../core/sarif.mjs';
|
|
9
|
+
import { callsUnconditionally, discoverAuthHelpers, MAX_DEPTH as MAX_AUTH_DEPTH, parseDeclarations, parseImports, regexNames, resolveModule, rpcPrimitive, stripComments, stripLiterals, writeRulesWithHelpers } from './auth-helpers.mjs';
|
|
10
|
+
import { scanTargetsWithIgnored } from '../_lib/targets.mjs';
|
|
11
|
+
import { runOpengrepContainer, runSelectedOpengrepContainer } from './opengrep-container.mjs';
|
|
12
|
+
import { assertStandaloneExecution } from '../../lattice/execution.mjs';
|
|
13
|
+
import { withTransientDockerSource } from '../../lattice/source-acquirer.mjs';
|
|
14
|
+
|
|
15
|
+
export const id = 'code';
|
|
16
|
+
export const title = 'Insecure code and stack rules (Opengrep)';
|
|
17
|
+
export const modes = ['scan', 'deep', 'release'];
|
|
18
|
+
export const requiredIn = ['scan', 'deep', 'release'];
|
|
19
|
+
export const timeoutSec = 900;
|
|
20
|
+
|
|
21
|
+
export const VSG_RULES_DIR = path.join(packageRoot, 'rules');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Rule directories taken from the pinned semgrep-rules package (paths relative to the package root).
|
|
25
|
+
* Security directories only. Left out on purpose: best-practice, correctness, performance and portability packs
|
|
26
|
+
* (style, not exploitability), generic/secrets (Betterleaks owns secrets), GitHub Actions rules (zizmor owns workflows),
|
|
27
|
+
* and stacks Richard does not ship. Changing the pin means re-checking this list against the new package.
|
|
28
|
+
*/
|
|
29
|
+
export const RULE_DIRS = [
|
|
30
|
+
'javascript/lang/security', 'javascript/browser/security', 'javascript/express/security',
|
|
31
|
+
'javascript/jsonwebtoken', 'javascript/jose', 'javascript/jwt-simple', 'javascript/node-crypto', 'javascript/passport-jwt',
|
|
32
|
+
'javascript/serialize-javascript', 'javascript/shelljs', 'javascript/vm2', 'javascript/sequelize',
|
|
33
|
+
'typescript/lang/security', 'typescript/react/security', 'typescript/nestjs',
|
|
34
|
+
'python/lang/security', 'python/requests/security', 'python/flask/security', 'python/django/security', 'python/fastapi',
|
|
35
|
+
'python/jinja2', 'python/jwt', 'python/cryptography', 'python/sqlalchemy/security', 'python/pymongo', 'python/docker', 'python/boto3', 'python/sh',
|
|
36
|
+
'dockerfile/security', 'yaml/docker-compose/security',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// Directories that are not the product's own code. Test fixtures are excluded so planted examples never become findings.
|
|
40
|
+
export const EXCLUDES = ['node_modules', '.next', 'dist', 'build', '.vsg', '.venv', 'venv', '.git', '__pycache__', '.pnpm-store', 'fixtures', '__fixtures__'];
|
|
41
|
+
|
|
42
|
+
const SOURCE = /\.(?:[cm]?[jt]sx?|py)$|(?:^|\/)(?:Dockerfile(?:\..+)?|[^/]+\.dockerfile|(?:docker-)?compose(?:[.-][^/]+)?\.ya?ml)$/i;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Hostile-input guard for YAML. Opengrep's --timeout and --max-memory do not bound the YAML parser: a few-hundred-byte alias-expansion
|
|
46
|
+
* file ("billion laughs", for example Juice Shop test/files/yamlBomb.yml) makes it run until the whole check times out (verified on 1.30.0:
|
|
47
|
+
* with --timeout 15 and with --max-memory 1000 it still ran past 100 s). Restricting YAML to compose files would not help (a bomb can be named
|
|
48
|
+
* docker-compose.yml) and Opengrep's --exclude globs misbehave on Windows (the runtime expands "*.yml" against the working directory),
|
|
49
|
+
* so every YAML file is screened here by a cheap expansion estimate and an offender is excluded by its exact path and reported as degraded.
|
|
50
|
+
*/
|
|
51
|
+
export const YAML_EXPANSION_LIMIT = 100_000;
|
|
52
|
+
const YAML_FILE = /\.ya?ml$/i;
|
|
53
|
+
const ALIAS = /(?:^|[\s[{,:-])\*[^\s,\]}]+/g;
|
|
54
|
+
const ANCHOR = /(?:^|[\s[{,:-])&([^\s,\]}]+)/g;
|
|
55
|
+
const MAX_SCREEN_BYTES = 1_000_000; // Opengrep ignores bigger files itself (--max-target-bytes default)
|
|
56
|
+
|
|
57
|
+
/** Estimated node count after alias expansion, line by line. Over-estimates are fine; a bomb grows exponentially so it cannot hide. */
|
|
58
|
+
export function yamlExpansionEstimate(text) {
|
|
59
|
+
const anchors = new Map(); // anchor name -> estimated size
|
|
60
|
+
const open = []; // anchors whose block is still open: { name, indent }
|
|
61
|
+
let total = 0;
|
|
62
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
63
|
+
const line = raw.replace(/\s#.*$/, '');
|
|
64
|
+
if (!line.trim() || line.trim().startsWith('#')) continue;
|
|
65
|
+
const indent = line.length - line.trimStart().length;
|
|
66
|
+
while (open.length && indent <= open.at(-1).indent) open.pop();
|
|
67
|
+
let cost = 1;
|
|
68
|
+
for (const m of line.matchAll(ALIAS)) cost += anchors.get(m[0].replace(/^[^*]*\*/, '')) ?? 1;
|
|
69
|
+
total += cost;
|
|
70
|
+
for (const o of open) anchors.set(o.name, anchors.get(o.name) + cost);
|
|
71
|
+
for (const m of line.matchAll(ANCHOR)) { anchors.set(m[1], cost); open.push({ name: m[1], indent }); }
|
|
72
|
+
if (total > YAML_EXPANSION_LIMIT * 1000) return Infinity;
|
|
73
|
+
}
|
|
74
|
+
return total;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** YAML files (relative posix paths) whose alias expansion is too large to hand to Opengrep. Honors the same directory exclusions as the scan. */
|
|
78
|
+
export function screenYaml(repo, nested = []) {
|
|
79
|
+
const skipped = [];
|
|
80
|
+
const skipNested = new Set(nested.map((n) => n.path));
|
|
81
|
+
const walk = (dir, rel) => {
|
|
82
|
+
let entries;
|
|
83
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
84
|
+
for (const e of entries) {
|
|
85
|
+
if (e.isSymbolicLink()) continue;
|
|
86
|
+
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
87
|
+
if (e.isDirectory()) { if (!EXCLUDES.includes(e.name) && !skipNested.has(r)) walk(path.join(dir, e.name), r); continue; }
|
|
88
|
+
if (!e.isFile() || !YAML_FILE.test(e.name)) continue;
|
|
89
|
+
try {
|
|
90
|
+
const full = path.join(dir, e.name);
|
|
91
|
+
if (fs.statSync(full).size > MAX_SCREEN_BYTES) continue;
|
|
92
|
+
const text = fs.readFileSync(full, 'utf8');
|
|
93
|
+
if (!text.includes('*')) continue;
|
|
94
|
+
const est = yamlExpansionEstimate(text);
|
|
95
|
+
if (est > YAML_EXPANSION_LIMIT) skipped.push({ file: r, estimate: est });
|
|
96
|
+
} catch { /* unreadable: Opengrep reports it itself */ }
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
walk(repo, '');
|
|
100
|
+
return skipped;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function applies(ctx) {
|
|
104
|
+
const files = listFiles(ctx.repo);
|
|
105
|
+
return files.some((f) => SOURCE.test(f))
|
|
106
|
+
? true
|
|
107
|
+
: { applies: false, reason: 'no JavaScript, TypeScript, Python or Docker files were found' };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Reads the vsg rules. SARIF carries no custom metadata, so blocking, fix and OWASP labels come from the rule files.
|
|
112
|
+
* Returns Map<ruleId, { blocking, fix, owasp }>.
|
|
113
|
+
*/
|
|
114
|
+
export function loadVsgRules(dir = VSG_RULES_DIR) {
|
|
115
|
+
const rules = new Map();
|
|
116
|
+
let files = [];
|
|
117
|
+
try { files = fs.readdirSync(dir).filter((f) => /\.ya?ml$/.test(f)).sort(); } catch { return rules; }
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
const text = fs.readFileSync(path.join(dir, file), 'utf8');
|
|
120
|
+
const starts = [...text.matchAll(/^\s*- id:\s*(\S+)\s*$/gm)];
|
|
121
|
+
starts.forEach((m, i) => {
|
|
122
|
+
const block = text.slice(m.index, starts[i + 1]?.index ?? text.length);
|
|
123
|
+
const meta = (key) => new RegExp(`^\\s*${key}:\\s*(.+?)\\s*$`, 'm').exec(block)?.[1]?.replace(/^["']|["']$/g, '');
|
|
124
|
+
rules.set(m[1], {
|
|
125
|
+
blocking: meta('vsg-blocking') === 'true',
|
|
126
|
+
fix: meta('vsg-fix') ?? '',
|
|
127
|
+
title: meta('vsg-title'),
|
|
128
|
+
severity: meta('vsg-severity'),
|
|
129
|
+
owasp: meta('owasp')?.replace(/\s+-\s+/, ' '),
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return rules;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Opengrep rewrites rule ids to dotted config paths; this returns the stable id (vsg-... or the registry rule id). */
|
|
137
|
+
export function cleanRuleId(raw, selectedRuleIds = []) {
|
|
138
|
+
const s = String(raw);
|
|
139
|
+
const vsg = /(?:^|\.)(vsg-[a-z0-9-]+)$/.exec(s);
|
|
140
|
+
if (vsg) return vsg[1];
|
|
141
|
+
const selected = selectedRuleIds.filter((ruleId) => s === ruleId || s.endsWith(`.${ruleId}`));
|
|
142
|
+
if (selected.length === 1) return selected[0];
|
|
143
|
+
return s.replace(/^.*?semgrep-rules-[0-9a-f]{40}\./, '');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const firstSentence = (text) => {
|
|
147
|
+
const t = String(text ?? '').replace(/\s+/g, ' ').trim();
|
|
148
|
+
const end = t.search(/[.!?](\s|$)/);
|
|
149
|
+
const s = end > 0 ? t.slice(0, end + 1) : t;
|
|
150
|
+
return s.length > 140 ? `${s.slice(0, 137)}...` : s;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const CONFIDENCE_TAG = /^(HIGH|MEDIUM|LOW) CONFIDENCE$/i;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Maps Opengrep SARIF to raw findings through the core normalizer.
|
|
157
|
+
* vsg rules marked blocking become High with "confirmed" confidence (the gate blocks them) and other vsg rules stay below High;
|
|
158
|
+
* registry rules keep their level, and only ERROR with HIGH confidence reaches the gate's blocking threshold.
|
|
159
|
+
*/
|
|
160
|
+
export function mapSarif(sarif, { repo, vsgRules = loadVsgRules(), selectedRuleIds = [] } = {}) {
|
|
161
|
+
const clone = structuredClone(sarif);
|
|
162
|
+
const tagsOf = new Map();
|
|
163
|
+
const messageOf = new Map();
|
|
164
|
+
for (const run of clone.runs ?? []) {
|
|
165
|
+
for (const rule of run.tool?.driver?.rules ?? []) {
|
|
166
|
+
const tags = rule.properties?.tags ?? [];
|
|
167
|
+
rule.id = cleanRuleId(rule.id, selectedRuleIds);
|
|
168
|
+
// SARIF precision from Opengrep says "very-high" for every rule, so confidence comes from the rule's own CONFIDENCE tag.
|
|
169
|
+
const conf = tags.map((t) => CONFIDENCE_TAG.exec(t)?.[1]?.toLowerCase()).find(Boolean) ?? 'medium';
|
|
170
|
+
rule.properties = { ...rule.properties, precision: conf };
|
|
171
|
+
const text = rule.fullDescription?.text ?? rule.help?.text ?? '';
|
|
172
|
+
rule.shortDescription = { text: firstSentence(text) || rule.id };
|
|
173
|
+
tagsOf.set(rule.id, tags);
|
|
174
|
+
messageOf.set(rule.id, text);
|
|
175
|
+
}
|
|
176
|
+
for (const result of run.results ?? []) {
|
|
177
|
+
result.ruleId = cleanRuleId(result.ruleId, selectedRuleIds);
|
|
178
|
+
delete result.ruleIndex;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const mapped = normalizeSarif(clone, { tool: 'opengrep', category: 'code', repo }).map((f) => {
|
|
182
|
+
const vsg = vsgRules.get(f.ruleId);
|
|
183
|
+
const out = { ...f };
|
|
184
|
+
if (vsg) {
|
|
185
|
+
// A non-blocking vsg rule is capped below High unless the rule file says vsg-severity: high (it still cannot block: its confidence stays below high).
|
|
186
|
+
out.severity = vsg.blocking ? 'high' : vsg.severity === 'high' ? 'high' : (f.severity === 'high' || f.severity === 'critical' ? 'medium' : f.severity);
|
|
187
|
+
out.confidence = vsg.blocking ? 'confirmed' : (f.confidence === 'high' ? 'medium' : f.confidence);
|
|
188
|
+
out.fix = vsg.fix;
|
|
189
|
+
if (vsg.title) out.title = vsg.title;
|
|
190
|
+
out.owasp = vsg.owasp ? [vsg.owasp] : [];
|
|
191
|
+
} else {
|
|
192
|
+
out.fix = `Review the flagged code and apply the safer pattern described in the finding. Rule: ${f.ruleId}.`;
|
|
193
|
+
}
|
|
194
|
+
if (!out.title || out.title.length < 4) out.title = f.ruleId;
|
|
195
|
+
return out;
|
|
196
|
+
});
|
|
197
|
+
return dedupeWrites(dropRegistryDuplicates(mapped, vsgRules), repo);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* A registry rule that flags the same line and the same weakness (CWE) as a vsg rule is the same issue: the vsg finding carries the
|
|
202
|
+
* stack-specific fix, so the registry copy is dropped. It is kept when it would block and the vsg finding would not.
|
|
203
|
+
*/
|
|
204
|
+
function dropRegistryDuplicates(findings, vsgRules) {
|
|
205
|
+
const vsg = findings.filter((f) => vsgRules.has(f.ruleId));
|
|
206
|
+
return findings.filter((f) => {
|
|
207
|
+
if (vsgRules.has(f.ruleId)) return true;
|
|
208
|
+
const twin = vsg.find((v) => v.location.file === f.location.file && v.location.line === f.location.line && v.cwe.some((c) => f.cwe.includes(c)));
|
|
209
|
+
if (!twin) return true;
|
|
210
|
+
const blocksAlone = f.severity === 'high' && f.confidence === 'high';
|
|
211
|
+
return blocksAlone && twin.confidence !== 'confirmed';
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** The "writes without checking the caller" rules match every write call; a handler or action is the unit a person fixes. */
|
|
216
|
+
export const WRITE_RULES = new Set(['vsg-nextjs-route-write-no-auth', 'vsg-nextjs-action-write-no-auth', 'vsg-nextjs-inline-action-write-no-auth']);
|
|
217
|
+
// A function begins at a line that declares one: `function f(`, or `const f = async (...) =>` / `const f = function`.
|
|
218
|
+
const FUNCTION_START = /^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\b|^\s*(?:export\s+)?(?:const|let|var)\s+[\w$]+\s*(?::[^=]+)?=\s*(?:async\s*)?(?:function\b|\([^)]*\)\s*(?::[^=]+?)?\s*=>|[\w$]+\s*=>)/;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* One finding per handler or action: of the write-rule findings in the same function, keeps the first write and says how many more there are.
|
|
222
|
+
* The enclosing function is the nearest declaration line above the write, found by reading the file as text. A file that cannot be read keeps its findings as they are.
|
|
223
|
+
*/
|
|
224
|
+
export function dedupeWrites(findings, repo) {
|
|
225
|
+
if (!repo) return findings;
|
|
226
|
+
const starts = new Map(); // file -> sorted line numbers where a function declaration begins
|
|
227
|
+
const startsOf = (file) => {
|
|
228
|
+
if (!starts.has(file)) {
|
|
229
|
+
let lines = [];
|
|
230
|
+
try { lines = fs.readFileSync(path.join(repo, file), 'utf8').split(/\r?\n/); } catch { /* unreadable: keep findings */ }
|
|
231
|
+
starts.set(file, lines.length ? lines.flatMap((l, i) => (FUNCTION_START.test(l) ? [i + 1] : [])) : null);
|
|
232
|
+
}
|
|
233
|
+
return starts.get(file);
|
|
234
|
+
};
|
|
235
|
+
const groups = new Map(); // `${rule}|${file}|${handlerStart}` -> findings
|
|
236
|
+
const out = [];
|
|
237
|
+
for (const f of findings) {
|
|
238
|
+
const file = f.location?.file;
|
|
239
|
+
const line = f.location?.line;
|
|
240
|
+
const s = WRITE_RULES.has(f.ruleId) && file && line ? startsOf(file) : null;
|
|
241
|
+
if (!s) { out.push(f); continue; }
|
|
242
|
+
const start = s.filter((n) => n <= line).at(-1) ?? 0;
|
|
243
|
+
const key = `${f.ruleId}|${file}|${start}`;
|
|
244
|
+
if (!groups.has(key)) { groups.set(key, []); out.push(groups.get(key)); }
|
|
245
|
+
groups.get(key).push(f);
|
|
246
|
+
}
|
|
247
|
+
return out.map((g) => {
|
|
248
|
+
if (!Array.isArray(g)) return g;
|
|
249
|
+
g.sort((a, b) => a.location.line - b.location.line);
|
|
250
|
+
const [first, ...rest] = g;
|
|
251
|
+
if (!rest.length) return first;
|
|
252
|
+
return { ...first, description: `${first.description} This handler makes ${g.length} write calls in all, first at line ${first.location.line} (others at lines ${rest.map((r) => r.location.line).join(', ')}); fixing the check at the top fixes all of them.` };
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Names in one file that reach an auth primitive within MAX_DEPTH calls through functions of the same file, exported or not.
|
|
258
|
+
*/
|
|
259
|
+
function localAuthDetails(text, authRpcs = []) {
|
|
260
|
+
const rpcRe = rpcPrimitive(authRpcs);
|
|
261
|
+
const decls = parseDeclarations(text, { extraPrimitives: rpcRe ? [rpcRe] : [] });
|
|
262
|
+
const dist = new Map([...decls.values()].filter((d) => d.primitive).map((d) => [d.name, 1]));
|
|
263
|
+
const failsClosed = new Set([...decls.values()].filter((d) => d.failsClosed).map((d) => d.name));
|
|
264
|
+
for (let pass = 0; pass < MAX_AUTH_DEPTH; pass++) {
|
|
265
|
+
let changed = false;
|
|
266
|
+
for (const d of decls.values()) {
|
|
267
|
+
for (const c of d.calls) {
|
|
268
|
+
if (dist.has(c) && dist.get(c) < MAX_AUTH_DEPTH && !(dist.get(d.name) <= dist.get(c) + 1)) { dist.set(d.name, dist.get(c) + 1); changed = true; }
|
|
269
|
+
if (failsClosed.has(c) && callsUnconditionally(d.body, c)) {
|
|
270
|
+
if (!failsClosed.has(d.name)) { failsClosed.add(d.name); changed = true; }
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (!changed) break;
|
|
275
|
+
}
|
|
276
|
+
return new Map([...dist.keys()].filter((n) => /^[A-Za-z_$][\w$]*$/.test(n)).map((n) => {
|
|
277
|
+
const declaration = decls.get(n);
|
|
278
|
+
return [n, { failsClosed: failsClosed.has(n), returnsAuthValue: declaration?.returnsAuthValue === true, denialKeys: declaration?.denialKeys ?? new Set() }];
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function localAuthNames(text, authRpcs = []) { return new Set(localAuthDetails(text, authRpcs).keys()); }
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Automatic discovery is definition-aware: a custom guard only excuses a write when the file
|
|
286
|
+
* actually calls a local helper or imports the discovered helper from its defining module. The
|
|
287
|
+
* shipped `authHelpers` configuration remains an explicit policy and is handled in the rules.
|
|
288
|
+
*/
|
|
289
|
+
function importedAuthDetails(repo, file, text, helpers) {
|
|
290
|
+
if (!helpers.length) return new Map();
|
|
291
|
+
const localDeclarations = parseDeclarations(text);
|
|
292
|
+
const imports = parseImports(text);
|
|
293
|
+
const out = new Map();
|
|
294
|
+
for (const [local, binding] of imports) {
|
|
295
|
+
if (localDeclarations.has(local)) continue; // a local declaration shadows the import
|
|
296
|
+
const target = resolveModule(repo, file, binding.source);
|
|
297
|
+
if (!target) continue;
|
|
298
|
+
const helper = helpers.find((h) => h.file === target && (h.name === binding.imported || (h.exports ?? []).includes(binding.imported) || (binding.imported === 'default' && (h.exports ?? []).includes('default'))));
|
|
299
|
+
if (helper) out.set(local, { failsClosed: helper.failsClosed === true, returnsAuthValue: helper.returnsAuthValue === true, denialKeys: new Set(helper.denialKeys ?? []) });
|
|
300
|
+
}
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const escapedName = (name) => name.replace(/\$/g, '\\$');
|
|
305
|
+
|
|
306
|
+
function resultBindings(statement, helper) {
|
|
307
|
+
const name = escapedName(helper);
|
|
308
|
+
const m = new RegExp(`^\\s*(?:const|let|var)\\s+(\\{[^}]+\\}|[A-Za-z_$][\\w$]*)(?:\\s*:[^=]+)?\\s*=\\s*(?:await\\s+)?${name}\\s*\\(\\s*\\)\\s*;?\\s*$`).exec(stripLiterals(stripComments(statement)));
|
|
309
|
+
if (!m) return [];
|
|
310
|
+
if (!m[1].startsWith('{')) return [{ key: m[1], local: m[1] }];
|
|
311
|
+
return m[1].slice(1, -1).split(',').map((part) => part.trim()).filter(Boolean).map((part) => {
|
|
312
|
+
const pair = part.split(/\s*:\s*/);
|
|
313
|
+
const key = pair[0].replace(/\?$/, '').trim();
|
|
314
|
+
const local = (pair[1] ?? key).trim();
|
|
315
|
+
return /^[A-Za-z_$][\w$]*$/.test(key) && /^[A-Za-z_$][\w$]*$/.test(local) ? { key, local } : null;
|
|
316
|
+
}).filter(Boolean);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function callerEnforcesResult(lines, fromIndex, toIndex, bindings, helper) {
|
|
320
|
+
if (!bindings.length) return false;
|
|
321
|
+
const after = stripLiterals(stripComments(lines.slice(fromIndex + 1, toIndex).join('\n')));
|
|
322
|
+
if (/\bcatch\b|=>|\bfunction\b/.test(after)) return false;
|
|
323
|
+
let depth = 0;
|
|
324
|
+
const flags = stripLiterals(stripComments(lines.join('\n'))).split(/\r?\n/).map((code) => {
|
|
325
|
+
const atTop = depth === 1;
|
|
326
|
+
depth += (code.match(/{/g) ?? []).length - (code.match(/}/g) ?? []).length;
|
|
327
|
+
return { code, atTop };
|
|
328
|
+
});
|
|
329
|
+
if (!flags[fromIndex]?.atTop) return false;
|
|
330
|
+
let guardIndex = fromIndex + 1;
|
|
331
|
+
while (guardIndex < toIndex && !flags[guardIndex].code.trim()) guardIndex++;
|
|
332
|
+
if (guardIndex >= toIndex || !flags[guardIndex].atTop) return false;
|
|
333
|
+
const guardLine = flags[guardIndex].code;
|
|
334
|
+
for (const { key, local } of bindings) {
|
|
335
|
+
const variable = escapedName(local);
|
|
336
|
+
const deniedName = helper.denialKeys?.has(key) === true;
|
|
337
|
+
const authValue = helper.returnsAuthValue === true && /^(?:user|session|claims|context|identity|access|result|auth)$/i.test(key);
|
|
338
|
+
const negated = authValue && new RegExp(`^\\s*if\\s*\\(\\s*!\\s*${variable}(?:\\.(?:allowed|authorized|authenticated|user|session))?\\s*\\)\\s*(?:\\{\\s*)?(?:return|throw)\\b`);
|
|
339
|
+
const successFlag = /^(?:allowed|authorized|authenticated)$/i.test(key);
|
|
340
|
+
const positive = deniedName && new RegExp(`^\\s*if\\s*\\(\\s*${successFlag ? '!' : ''}${variable}\\s*\\)\\s*(?:\\{\\s*)?(?:return|throw)\\b`);
|
|
341
|
+
if ((negated && negated.test(guardLine)) || (positive && positive.test(guardLine))) return true;
|
|
342
|
+
}
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Drops write-without-auth findings whose handler calls a file-local auth wrapper before the write (see localAuthNames).
|
|
348
|
+
* Returns { findings, dropped: [{ file, line, helper }] }. A file that cannot be read keeps its findings.
|
|
349
|
+
*/
|
|
350
|
+
export function dropLocalAuthWrites(findings, repo, authRpcs = [], discoveredHelpers = []) {
|
|
351
|
+
const cache = new Map();
|
|
352
|
+
const load = (file) => {
|
|
353
|
+
if (!cache.has(file)) {
|
|
354
|
+
try {
|
|
355
|
+
const text = fs.readFileSync(path.join(repo, file), 'utf8');
|
|
356
|
+
const lines = text.split(/\r?\n/);
|
|
357
|
+
const helpers = localAuthDetails(text, authRpcs);
|
|
358
|
+
for (const [name, detail] of importedAuthDetails(repo, file, text, discoveredHelpers)) helpers.set(name, detail);
|
|
359
|
+
cache.set(file, { lines, starts: lines.flatMap((line, index) => FUNCTION_START.test(line) ? [index + 1] : []), helpers });
|
|
360
|
+
} catch { cache.set(file, null); }
|
|
361
|
+
}
|
|
362
|
+
return cache.get(file);
|
|
363
|
+
};
|
|
364
|
+
const dropped = [];
|
|
365
|
+
const kept = findings.filter((finding) => {
|
|
366
|
+
const file = finding.location?.file;
|
|
367
|
+
const line = finding.location?.line;
|
|
368
|
+
const info = WRITE_RULES.has(finding.ruleId) && file && line ? load(file) : null;
|
|
369
|
+
if (!info || !info.helpers.size) return true;
|
|
370
|
+
const start = info.starts.filter((number) => number <= line).at(-1) ?? 1;
|
|
371
|
+
const lines = info.lines.slice(start - 1, line - 1);
|
|
372
|
+
let depth = 0;
|
|
373
|
+
const flags = stripLiterals(stripComments(lines.join('\n'))).split(/\r?\n/).map((code) => {
|
|
374
|
+
const atTop = depth === 1;
|
|
375
|
+
depth += (code.match(/{/g) ?? []).length - (code.match(/}/g) ?? []).length;
|
|
376
|
+
return { code, atTop };
|
|
377
|
+
});
|
|
378
|
+
if (!/\{\s*$/.test(flags[0]?.code ?? '')) return true;
|
|
379
|
+
const calls = [];
|
|
380
|
+
for (const name of info.helpers.keys()) {
|
|
381
|
+
const re = new RegExp(`(?<![\\w$.])${escapedName(name)}\\s*\\(`);
|
|
382
|
+
for (let index = 1; index < flags.length; index++) if (re.test(flags[index].code)) calls.push({ name, index });
|
|
383
|
+
}
|
|
384
|
+
calls.sort((left, right) => left.index - right.index);
|
|
385
|
+
const first = calls[0];
|
|
386
|
+
if (!first || !flags[first.index].atTop) return true;
|
|
387
|
+
const { name: helper, index: callAt } = first;
|
|
388
|
+
const detail = info.helpers.get(helper);
|
|
389
|
+
const callCode = flags[callAt].code;
|
|
390
|
+
const call = new RegExp(`^\\s*(?:(?:(?:await|return\\s+await)\\s+|return\\s+)|(?:const|let|var)\\s+(?:\\{[^}]+\\}|[A-Za-z_$][\\w$]*)(?:\\s*:[^=]+)?\\s*=\\s*await\\s+)${escapedName(helper)}\\s*\\(\\s*\\)\\s*;?\\s*$`);
|
|
391
|
+
const prefix = flags.slice(1, callAt).map((entry) => entry.code).join('\n');
|
|
392
|
+
if (!call.test(callCode) || /\b(?:if|else|switch|for|while|try|catch|return|throw)\b|=>/.test(prefix)) return true;
|
|
393
|
+
const beforeWrite = stripLiterals(stripComments(lines.join('\n')));
|
|
394
|
+
const caught = /\b(?:try|catch)\b/.test(beforeWrite);
|
|
395
|
+
const binding = resultBindings(lines[callAt], helper);
|
|
396
|
+
const enforced = detail.failsClosed && !caught || callerEnforcesResult(lines, callAt, lines.length, binding, detail);
|
|
397
|
+
if (!enforced) return true;
|
|
398
|
+
dropped.push({ file, line, helper });
|
|
399
|
+
return false;
|
|
400
|
+
});
|
|
401
|
+
return { findings: kept, dropped };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Opengrep's parser (tree-sitter) rejects some valid TypeScript and JavaScript: `new?: string` in a type, `typeof fn<T>` (an instantiation
|
|
406
|
+
* expression) and `import("pkg").Type` in a type position, and some files for reasons that could not be narrowed (a script with two regex
|
|
407
|
+
* literals containing `<`). The file is then skipped apart from a warning, so every such file is named here: the report lists them under
|
|
408
|
+
* "Not fully tested" because the code rules may have missed findings in them (Opengrep recovers part of some of these files, none of the rest). Verified on 1.30.0: forcing a language or another file extension does not help.
|
|
409
|
+
* Returns { files: [relative posix path], other: [note] }.
|
|
410
|
+
*/
|
|
411
|
+
export function splitScanNotes(notes) {
|
|
412
|
+
const files = [];
|
|
413
|
+
const other = [];
|
|
414
|
+
for (const n of notes) {
|
|
415
|
+
const m = /^Syntax error at line (.+?):\d+:?\s*$/.exec(n);
|
|
416
|
+
if (m) files.push(m[1].replace(/\\/g, '/').replace(/^\.\//, ''));
|
|
417
|
+
else other.push(n);
|
|
418
|
+
}
|
|
419
|
+
return { files: [...new Set(files)].sort(), other };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function sarifNotes(sarif) {
|
|
423
|
+
const notes = [];
|
|
424
|
+
for (const run of sarif?.runs ?? []) {
|
|
425
|
+
for (const inv of run.invocations ?? []) {
|
|
426
|
+
if (inv.executionSuccessful === false) notes.push('Opengrep reported an unsuccessful execution.');
|
|
427
|
+
for (const n of inv.toolExecutionNotifications ?? []) {
|
|
428
|
+
if (n.level === 'error' || n.level === 'warning') notes.push(String(n.message?.text ?? n.descriptor?.id ?? 'a scan error').split('\n')[0].slice(0, 200));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return notes;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Docker normally receives relative artifact paths because its working directory is /scan.
|
|
437
|
+
* Normalize an absolute mount path too, so findings, parser diagnostics and auth-helper
|
|
438
|
+
* post-processing always use paths relative to the Windows repository.
|
|
439
|
+
*/
|
|
440
|
+
export function normalizeContainerSarifPaths(sarif, mount = '/scan') {
|
|
441
|
+
const prefix = `${mount.replace(/\/$/, '')}/`;
|
|
442
|
+
const filePrefix = `file://${prefix}`;
|
|
443
|
+
const relative = (value) => {
|
|
444
|
+
if (typeof value !== 'string') return value;
|
|
445
|
+
if (value.startsWith(filePrefix)) return value.slice(filePrefix.length);
|
|
446
|
+
if (value.startsWith(prefix)) return value.slice(prefix.length);
|
|
447
|
+
return value;
|
|
448
|
+
};
|
|
449
|
+
const inText = (value) => typeof value === 'string'
|
|
450
|
+
? value.replaceAll(filePrefix, '').replaceAll(prefix, '')
|
|
451
|
+
: value;
|
|
452
|
+
for (const run of sarif?.runs ?? []) {
|
|
453
|
+
for (const result of run.results ?? []) {
|
|
454
|
+
for (const location of result.locations ?? []) {
|
|
455
|
+
const artifact = location?.physicalLocation?.artifactLocation;
|
|
456
|
+
if (artifact?.uri) artifact.uri = relative(artifact.uri);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
for (const invocation of run.invocations ?? []) {
|
|
460
|
+
for (const note of invocation.toolExecutionNotifications ?? []) {
|
|
461
|
+
if (note?.message?.text) note.message.text = inText(note.message.text);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return sarif;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Opengrep on Windows expands %NAME% in its arguments the way cmd.exe does. A folder named literally "%SystemDrive%" (a script that
|
|
470
|
+
* created it from an unexpanded variable) became the target "C:", and the scan walked the whole drive until the 15 minute timeout
|
|
471
|
+
* (verified on 1.30.0, Windows 10). Doubling every percent sign keeps the literal name; verified that "%%" reaches Opengrep as "%",
|
|
472
|
+
* and that a name with an undefined variable such as "x%FOO%y.py" is unchanged either way.
|
|
473
|
+
*/
|
|
474
|
+
export const literalArg = (arg, platform = process.platform) => (platform === 'win32' ? String(arg).replace(/%/g, '%%') : String(arg));
|
|
475
|
+
|
|
476
|
+
/** Opengrep arguments (verified against `opengrep scan --help` for 1.30.0). Run with the repository as the working directory. */
|
|
477
|
+
export function scanArgs(root, skipFiles = [], rulesDir = VSG_RULES_DIR, nested = [], targets = ['.'], platform = process.platform, ruleDirs = RULE_DIRS) {
|
|
478
|
+
const lit = (a) => literalArg(a, platform);
|
|
479
|
+
const join = platform === 'win32' ? path.win32.join : path.posix.join;
|
|
480
|
+
return [
|
|
481
|
+
'scan', '--sarif', '--quiet', '--disable-version-check',
|
|
482
|
+
// --disable-nosem: a "nosem" comment in the scanned repository must not silence a finding; only vsg.accept.json (approved by Richard) can.
|
|
483
|
+
'--disable-nosem',
|
|
484
|
+
// --no-git-ignore: scan files even when .gitignore hides them (for example a worktree under an ignored path), so a clean result is never silent.
|
|
485
|
+
'--no-git-ignore',
|
|
486
|
+
// --x-ignore-semgrepignore-files: a .semgrepignore in the scanned repository can hide directories silently. This flag is marked internal
|
|
487
|
+
// in --help; if a future Opengrep drops it the scan fails loudly (not_run), never silently.
|
|
488
|
+
'--x-ignore-semgrepignore-files',
|
|
489
|
+
'--timeout', '15',
|
|
490
|
+
...EXCLUDES.flatMap((e) => ['--exclude', e]),
|
|
491
|
+
// exact paths only (never globs, see screenYaml)
|
|
492
|
+
...skipFiles.flatMap((f) => ['--exclude', lit(f)]),
|
|
493
|
+
// nested repositories and worktrees: other checkouts, not this repository. Verified on 1.30.0: a leading slash anchors the exact path
|
|
494
|
+
// (the same path without it also matched; either way there is no glob, so Windows has nothing to expand).
|
|
495
|
+
...nested.flatMap((n) => ['--exclude', lit(`/${n.path}`)]),
|
|
496
|
+
'-c', rulesDir,
|
|
497
|
+
...ruleDirs.flatMap((d) => ['-c', join(root, d)]),
|
|
498
|
+
...targets.map(lit),
|
|
499
|
+
];
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export async function run(ctx) {
|
|
503
|
+
assertStandaloneExecution({ allowQualification: true });
|
|
504
|
+
const artifactMode = ctx.selectedRuntime?.kind === 'lattice-artifact';
|
|
505
|
+
const selected = artifactMode ? ctx.selectedRuntime.selectedRuntime?.sast : null;
|
|
506
|
+
if (artifactMode && !selected) return { status: 'not_run', reason: 'The selected SAST runtime contract is missing; restricted fallback is refused.' };
|
|
507
|
+
const rulesTool = artifactMode ? null : ctx.registry?.get('semgrep-rules');
|
|
508
|
+
const containerMode = artifactMode || (process.platform === 'win32' && typeof ctx.runProcess === 'function');
|
|
509
|
+
const resolved = !containerMode && rulesTool ? resolveTool(rulesTool) : null;
|
|
510
|
+
const archive = containerMode && rulesTool ? resolveVerifiedArchive(rulesTool) : null;
|
|
511
|
+
if (!artifactMode && !resolved && !archive) return { status: 'not_run', reason: 'The semgrep-rules ruleset is not installed. Run vsg setup.' };
|
|
512
|
+
const root = artifactMode
|
|
513
|
+
? '/tmp/vsg-selected-opengrep/source'
|
|
514
|
+
: containerMode
|
|
515
|
+
? path.posix.join('/tmp/vsg-opengrep/rules', path.posix.dirname(rulesTool.platforms['win32-x64'].bin.replaceAll('\\', '/')))
|
|
516
|
+
: path.dirname(resolved.path);
|
|
517
|
+
const missing = artifactMode
|
|
518
|
+
? []
|
|
519
|
+
: containerMode
|
|
520
|
+
? RULE_DIRS.filter((d) => !Object.keys(archive.files).some((file) => file.startsWith(`${path.posix.dirname(rulesTool.platforms['win32-x64'].bin.replaceAll('\\', '/'))}/${d}/`)))
|
|
521
|
+
: RULE_DIRS.filter((d) => !fs.existsSync(path.join(root, d)));
|
|
522
|
+
if (missing.length) return { status: 'not_run', reason: `The pinned ruleset is missing expected rule directories (${missing.slice(0, 3).join(', ')}). Re-run vsg setup --repair or update RULE_DIRS for the new pin.` };
|
|
523
|
+
|
|
524
|
+
const hostile = screenYaml(ctx.repo, ctx.nestedRepos);
|
|
525
|
+
// Explicit authHelpers are added to a generated copy of the rules. Automatically discovered
|
|
526
|
+
// helpers stay out of the global rule regex: a name can be exported by unrelated modules, and a
|
|
527
|
+
// regex cannot tell which definition a call resolves to. They are checked after SARIF against
|
|
528
|
+
// the actual local/imported definition instead.
|
|
529
|
+
const found = discoverAuthHelpers(ctx.repo, { excludes: EXCLUDES });
|
|
530
|
+
const configured = ctx.config?.authHelpers ?? [];
|
|
531
|
+
const extra = regexNames(configured);
|
|
532
|
+
let rulesDir = VSG_RULES_DIR;
|
|
533
|
+
let tempRules = null;
|
|
534
|
+
if (extra.length) {
|
|
535
|
+
fs.mkdirSync(toolsDir(), { recursive: true });
|
|
536
|
+
tempRules = fs.mkdtempSync(path.join(toolsDir(), 'rules-'));
|
|
537
|
+
writeRulesWithHelpers(VSG_RULES_DIR, tempRules, extra);
|
|
538
|
+
rulesDir = tempRules;
|
|
539
|
+
}
|
|
540
|
+
// A working copy that holds agent worktrees also tends to hold ignored build output and browser profiles (a 4.6 GB .next-e2e took Opengrep
|
|
541
|
+
// past its time limit on its own). Git never ignores tracked files, so these directories hold only untracked, uncommitted files. They are
|
|
542
|
+
// skipped only in that situation (nested checkouts found), and counted in the reason so the coverage never claims them.
|
|
543
|
+
const nestedPaths = (ctx.nestedRepos ?? []).map((n) => n.path);
|
|
544
|
+
const { targets, ignoredDirs, prunedDirs } = scanTargetsWithIgnored(ctx.repo, nestedPaths, EXCLUDES, nestedPaths.length > 0);
|
|
545
|
+
let res;
|
|
546
|
+
try {
|
|
547
|
+
if (artifactMode) {
|
|
548
|
+
const opengrepTool = ctx.registry?.get('opengrep');
|
|
549
|
+
const linux = opengrepTool ? resolveTool(opengrepTool, 'linux-x64') : null;
|
|
550
|
+
const runner = ctx.registry?.get('yara-runner');
|
|
551
|
+
if (!linux || !runner?.image) return { status: 'not_run', reason: 'The verified Linux Opengrep binary or runner image is unavailable. Run vsg setup --repair.' };
|
|
552
|
+
try {
|
|
553
|
+
res = await withTransientDockerSource(ctx, { image: runner.image, source: selected.source, label: 'sast' }, ({ volume }) => runSelectedOpengrepContainer(ctx, {
|
|
554
|
+
args: scanArgs(root, hostile.map((h) => h.file), '/vsg/rules', ctx.nestedRepos, targets, 'linux', selected.rulesRoots),
|
|
555
|
+
opengrep: { ...linux, sha256: opengrepTool.platforms['linux-x64'].sha256 },
|
|
556
|
+
source: selected.source,
|
|
557
|
+
sourceFiles: selected.sourceFiles,
|
|
558
|
+
ruleIdsSha256: selected.ruleIdsSha256,
|
|
559
|
+
vsgRulesDir: rulesDir,
|
|
560
|
+
timeoutMs: (opengrepTool.timeoutSec ?? timeoutSec) * 1000,
|
|
561
|
+
volume,
|
|
562
|
+
}));
|
|
563
|
+
} catch (error) {
|
|
564
|
+
return { status: 'not_run', reason: `Selected SAST ${error.message}` };
|
|
565
|
+
}
|
|
566
|
+
} else if (containerMode) {
|
|
567
|
+
const opengrepTool = ctx.registry?.get('opengrep');
|
|
568
|
+
const linux = opengrepTool ? resolveTool(opengrepTool, 'linux-x64') : null;
|
|
569
|
+
if (!linux) return { status: 'not_run', reason: 'The verified Linux Opengrep binary is not installed. Run vsg setup --repair.' };
|
|
570
|
+
res = await runOpengrepContainer(ctx, {
|
|
571
|
+
args: scanArgs(root, hostile.map((h) => h.file), '/vsg/rules', ctx.nestedRepos, targets, 'linux'),
|
|
572
|
+
opengrep: { ...linux, sha256: opengrepTool.platforms['linux-x64'].sha256 },
|
|
573
|
+
rulesArchive: archive,
|
|
574
|
+
rulesRoot: path.posix.dirname(rulesTool.platforms['win32-x64'].bin.replaceAll('\\', '/')),
|
|
575
|
+
vsgRulesDir: rulesDir,
|
|
576
|
+
timeoutMs: (opengrepTool.timeoutSec ?? timeoutSec) * 1000,
|
|
577
|
+
});
|
|
578
|
+
} else {
|
|
579
|
+
res = await ctx.runTool('opengrep', scanArgs(root, hostile.map((h) => h.file), rulesDir, ctx.nestedRepos, targets));
|
|
580
|
+
}
|
|
581
|
+
} finally {
|
|
582
|
+
if (tempRules) fs.rmSync(tempRules, { recursive: true, force: true });
|
|
583
|
+
}
|
|
584
|
+
if (res.status !== 'ran') return { status: 'not_run', reason: res.reason };
|
|
585
|
+
if (res.truncated) return { status: 'not_run', reason: 'Opengrep output was too large to read completely.' };
|
|
586
|
+
if (res.exitCode !== 0) {
|
|
587
|
+
const tail = res.stderr.trim().split(/\r?\n/).slice(-3).join(' | ');
|
|
588
|
+
return { status: 'not_run', reason: `Opengrep failed with exit code ${res.exitCode}${tail ? `: ${tail}` : ''}.` };
|
|
589
|
+
}
|
|
590
|
+
let sarif;
|
|
591
|
+
try { sarif = JSON.parse(res.stdout); } catch { return { status: 'not_run', reason: 'Opengrep did not return valid SARIF.' }; }
|
|
592
|
+
if (containerMode) normalizeContainerSarifPaths(sarif);
|
|
593
|
+
|
|
594
|
+
const local = dropLocalAuthWrites(mapSarif(sarif, { repo: ctx.repo, selectedRuleIds: selected?.selectedRuleIds ?? [] }), ctx.repo, found.authRpcs, found.helpers);
|
|
595
|
+
const findings = local.findings;
|
|
596
|
+
const { files: unparsed, other: notes } = splitScanNotes(sarifNotes(sarif));
|
|
597
|
+
const reasons = [
|
|
598
|
+
...(hostile.length ? [`skipped ${hostile.length} YAML file(s) whose alias expansion is too large to parse safely (for example ${hostile[0].file})`] : []),
|
|
599
|
+
...(ignoredDirs.length ? [`skipped ${ignoredDirs.length} ignored, untracked director${ignoredDirs.length === 1 ? 'y' : 'ies'} because nested worktrees were present (for example ${ignoredDirs.slice(0, 3).join(', ')})`] : []),
|
|
600
|
+
...(found.capped ? [`auth-helper discovery indexed only the first ${found.indexedFiles} of ${found.candidateFiles} source files, so a wrapper defined later may not be recognized`] : []),
|
|
601
|
+
...(unparsed.length ? [`Opengrep could not parse ${unparsed.length} file(s), so the code rules may have missed findings in them (a parser limitation, not a finding): ${unparsed.slice(0, 20).join(', ')}${unparsed.length > 20 ? `, and ${unparsed.length - 20} more` : ''}`] : []),
|
|
602
|
+
...(notes.length ? [`Opengrep reported ${notes.length} scan problem(s), for example: ${notes[0]}`] : []),
|
|
603
|
+
];
|
|
604
|
+
return {
|
|
605
|
+
status: 'ran',
|
|
606
|
+
reason: reasons.length ? `degraded: ${reasons.join('; ')}` : '',
|
|
607
|
+
findings,
|
|
608
|
+
toolVersion: ctx.registry?.get('opengrep')?.version ?? null,
|
|
609
|
+
dataVersions: artifactMode
|
|
610
|
+
? { 'selected-sast': selected.source.manifestSha256.slice(0, 12), 'selected-sast-rules': String(selected.selectedRuleIds.length), 'vsg-rules': String(loadVsgRules().size) }
|
|
611
|
+
: { 'semgrep-rules': rulesTool.version.slice(0, 12), 'vsg-rules': String(loadVsgRules().size) },
|
|
612
|
+
evidence: { opengrepExitCode: res.exitCode, results: findings.length, ruleDirectories: RULE_DIRS.length, scanProblems: notes.slice(0, 20), unparsedFiles: unparsed.slice(0, 200), skippedIgnoredDirs: ignoredDirs.slice(0, 100), skippedDependencyDirs: prunedDirs.slice(0, 100), droppedLocalAuthWrites: local.dropped.slice(0, 100), skippedYaml: hostile.slice(0, 50).map((h) => ({ file: h.file, estimatedNodes: Number.isFinite(h.estimate) ? h.estimate : 'unbounded' })), durationMs: res.durationMs,
|
|
613
|
+
authHelpers: { discovered: found.helpers.slice(0, 200), configured, authRpcs: found.authRpcs.slice(0, 200), indexedFiles: found.indexedFiles, candidateFiles: found.candidateFiles, capped: found.capped } },
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export default { id, title, modes, requiredIn, timeoutSec, applies, run };
|