@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,196 @@
|
|
|
1
|
+
// The shared run path: detect, select checks, run them, normalize, dedupe, accept, gate, write outputs atomically.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
6
|
+
import { applyAcceptances, loadAcceptances, loadConfig, reviewItems } from './accept.mjs';
|
|
7
|
+
import { resolveBase } from './base-ref.mjs';
|
|
8
|
+
import { UsageError } from './errors.mjs';
|
|
9
|
+
import { cachedGet, vfetch } from './fetch.mjs';
|
|
10
|
+
import { dedupe, makeFinding, scrubText } from './findings.mjs';
|
|
11
|
+
import { applyPolicy, countsOf, requiredFor, verdictFor } from './gate.mjs';
|
|
12
|
+
import { resolveTool } from './installer.mjs';
|
|
13
|
+
import { acquireLock } from './lock.mjs';
|
|
14
|
+
import { packageRoot } from './paths.mjs';
|
|
15
|
+
import { detect } from './detect.mjs';
|
|
16
|
+
import { nestedSummary } from './nested.mjs';
|
|
17
|
+
import { writeOutputs } from './report.mjs';
|
|
18
|
+
import { runProcess } from './runner.mjs';
|
|
19
|
+
import { reviewIdentity } from '../agent/ingest.mjs';
|
|
20
|
+
import { execFileSync } from 'node:child_process';
|
|
21
|
+
import { captureRuntimeProvenance } from '../lattice/provenance.mjs';
|
|
22
|
+
import { assertStandaloneExecution } from '../lattice/execution.mjs';
|
|
23
|
+
import { loadSelectedRuntime } from '../lattice/selected-runtime.mjs';
|
|
24
|
+
|
|
25
|
+
// The running check's AbortController. Processes a check starts through ctx.runProcess / ctx.runTool are killed (whole tree) when it
|
|
26
|
+
// aborts: on timeout, when the check ends, and when the pipeline finishes.
|
|
27
|
+
const currentCheck = new AsyncLocalStorage();
|
|
28
|
+
const MAX_CONCURRENT = 3;
|
|
29
|
+
const DEFAULT_CHECK_TIMEOUT_SEC = 1800;
|
|
30
|
+
const withTimeout = (promise, ms, label) => new Promise((resolve, reject) => {
|
|
31
|
+
const t = setTimeout(() => reject(new Error(`${label} timed out after ${Math.round(ms / 1000)}s`)), ms);
|
|
32
|
+
promise.then((v) => { clearTimeout(t); resolve(v); }, (e) => { clearTimeout(t); reject(e); });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
async function pool(items, limit, fn) {
|
|
36
|
+
const queue = [...items];
|
|
37
|
+
await Promise.all(Array.from({ length: Math.min(limit, queue.length) }, async () => {
|
|
38
|
+
while (queue.length) await fn(queue.shift());
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function vsgVersion() {
|
|
43
|
+
try { return JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')).version; } catch { return '0.0.0'; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Captures the same content identity used by agent review, plus bounded Git state for publication evidence. */
|
|
47
|
+
export function captureReviewContext(repo, runId, out) {
|
|
48
|
+
const identity = reviewIdentity(repo, runId, out);
|
|
49
|
+
let commitSha = null;
|
|
50
|
+
let dirty = true;
|
|
51
|
+
try {
|
|
52
|
+
const options = { encoding: 'utf8', timeout: 10_000, stdio: ['ignore', 'pipe', 'pipe'] };
|
|
53
|
+
const inside = execFileSync('git', ['-C', repo, 'rev-parse', '--is-inside-work-tree'], options).trim();
|
|
54
|
+
if (inside === 'true') {
|
|
55
|
+
const commit = execFileSync('git', ['-C', repo, 'rev-parse', 'HEAD'], options).trim();
|
|
56
|
+
commitSha = /^[0-9a-f]{40}$/.test(commit) ? commit : null;
|
|
57
|
+
dirty = execFileSync('git', ['-C', repo, 'status', '--porcelain=v1', '--untracked-files=all'], { ...options, maxBuffer: 16 * 1024 * 1024 }).trim().length > 0;
|
|
58
|
+
}
|
|
59
|
+
} catch { /* Non-Git inputs retain a null commit and dirty=true. */ }
|
|
60
|
+
return { ...identity, commitSha, dirty };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Runs one mode over a repository and writes the outputs. Returns { verdict, counts, outputs, ... }.
|
|
65
|
+
* opts: { mode, repo, out, checks, registry, flags: { base, compare }, now, progress }
|
|
66
|
+
*/
|
|
67
|
+
export async function runPipeline({ mode, repo, out, checks, registry = new Map(), flags = {}, now = () => new Date(), progress = () => {} }) {
|
|
68
|
+
assertStandaloneExecution({ allowQualification: true });
|
|
69
|
+
const selectedRuntime = loadSelectedRuntime({ runtimeRoot: packageRoot });
|
|
70
|
+
repo = path.resolve(repo);
|
|
71
|
+
if (!fs.existsSync(repo) || !fs.statSync(repo).isDirectory()) throw new UsageError(`"${repo}" is not a directory. Pass the repository path.`);
|
|
72
|
+
out = path.resolve(out ?? path.join(repo, '.vsg'));
|
|
73
|
+
let previous = null;
|
|
74
|
+
if (flags.compare) {
|
|
75
|
+
try { previous = JSON.parse(fs.readFileSync(flags.compare, 'utf8')); } catch (e) { throw new UsageError(`Cannot read --compare file "${flags.compare}": ${e.message}.`); }
|
|
76
|
+
if (!Array.isArray(previous?.findings)) throw new UsageError(`--compare file "${flags.compare}" is not a vsg findings.json.`);
|
|
77
|
+
}
|
|
78
|
+
const lock = acquireLock(out);
|
|
79
|
+
const controllers = new Set(); // one per running check
|
|
80
|
+
try {
|
|
81
|
+
const startedAt = now().toISOString();
|
|
82
|
+
const runId = crypto.randomUUID();
|
|
83
|
+
const sourceStart = captureReviewContext(repo, runId, out);
|
|
84
|
+
const runtimeStart = captureRuntimeProvenance({ runtimeRoot: packageRoot });
|
|
85
|
+
const log = [];
|
|
86
|
+
const note = (line) => log.push(`${new Date().toISOString()} ${line}`);
|
|
87
|
+
note(`vsg ${vsgVersion()} ${mode} ${repo}`);
|
|
88
|
+
|
|
89
|
+
const config = loadConfig(repo);
|
|
90
|
+
const acceptances = loadAcceptances(repo, { now: now().getTime() });
|
|
91
|
+
const detection = detect(repo);
|
|
92
|
+
note(`detected: ${detection.stacks.join(', ') || 'nothing specific'}; lockfiles: ${detection.lockfiles.length}`);
|
|
93
|
+
if (detection.nestedRepos.length) note(nestedSummary(detection.nestedRepos));
|
|
94
|
+
const base = resolveBase(repo, { flag: flags.base, config });
|
|
95
|
+
const baseRef = base.sha;
|
|
96
|
+
const review = flags.noReview ? [] : reviewItems(repo, baseRef, base.why); // a live run with no repository has nothing to compare
|
|
97
|
+
|
|
98
|
+
const { required, removed } = requiredFor(mode, detection, { config });
|
|
99
|
+
const selected = checks.filter((c) => c.modes.includes(mode));
|
|
100
|
+
const requiredSet = new Set([...required, ...selected.filter((c) => c.requiredIn.includes(mode) && !removed.some((r) => r.id === c.id)).map((c) => c.id)]);
|
|
101
|
+
const raw = {};
|
|
102
|
+
const coverage = [];
|
|
103
|
+
const rawFindings = [];
|
|
104
|
+
|
|
105
|
+
const ctx = {
|
|
106
|
+
repo, out, mode, detection, nestedRepos: detection.nestedRepos, config, registry, selectedRuntime, baseRef, flags, live: flags.live ?? null, runId, log: note, fetch: vfetch, cachedGet, makeFinding, vsgVersion: vsgVersion(),
|
|
107
|
+
/** This run's findings so far (normalized, deduped, policy applied). Only meaningful to checks that set `last`, which run after the others. */
|
|
108
|
+
priorFindings() {
|
|
109
|
+
const snapshot = structuredClone(dedupe(rawFindings));
|
|
110
|
+
applyAcceptances(snapshot, acceptances);
|
|
111
|
+
return applyPolicy(snapshot);
|
|
112
|
+
},
|
|
113
|
+
runProcess: (o) => runProcess({ log: note, signal: currentCheck.getStore()?.signal, ...o }),
|
|
114
|
+
/** Runs a pinned, installed registry tool. Returns the runner result, or not_run with a setup hint. */
|
|
115
|
+
async runTool(name, args, o = {}) {
|
|
116
|
+
const tool = registry.get(name);
|
|
117
|
+
if (!tool) return { status: 'not_run', reason: `Tool "${name}" is not in the registry.` };
|
|
118
|
+
const resolved = resolveTool(tool);
|
|
119
|
+
if (!resolved) return { status: 'not_run', reason: `${tool.name} is not installed. Run vsg setup.` };
|
|
120
|
+
return runProcess({ command: resolved.path, args, cwd: o.cwd ?? repo, timeoutMs: (o.timeoutSec ?? tool.timeoutSec ?? 600) * 1000, envNames: [...(tool.env ?? []), ...(o.envNames ?? [])], extraEnv: o.extraEnv, input: o.input, log: note, signal: currentCheck.getStore()?.signal });
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const runCheck = async (check) => {
|
|
125
|
+
const started = Date.now();
|
|
126
|
+
const controller = new AbortController();
|
|
127
|
+
controllers.add(controller);
|
|
128
|
+
const entry = { id: check.id, status: 'not_run', reason: '', required: requiredSet.has(check.id), toolVersion: null, dataVersions: {}, durationMs: 0 };
|
|
129
|
+
try {
|
|
130
|
+
const verdict = await check.applies(ctx);
|
|
131
|
+
const applies = typeof verdict === 'object' && verdict !== null ? verdict.applies : verdict;
|
|
132
|
+
if (!applies) {
|
|
133
|
+
entry.status = 'not_applicable';
|
|
134
|
+
entry.reason = (typeof verdict === 'object' && verdict?.reason) || 'nothing to check in this repository';
|
|
135
|
+
} else {
|
|
136
|
+
// Optional checks become required when their input exists. This keeps a clean repository not_applicable while failing closed
|
|
137
|
+
// if an artifact was produced but its scanner cannot complete.
|
|
138
|
+
entry.required = entry.required || check.requiredIfApplied === true;
|
|
139
|
+
progress(`Running ${check.id}`);
|
|
140
|
+
const res = await withTimeout(Promise.resolve(currentCheck.run(controller, () => check.run(ctx))), (check.timeoutSec ?? DEFAULT_CHECK_TIMEOUT_SEC) * 1000, check.id);
|
|
141
|
+
if (!['ran', 'not_run', 'not_applicable'].includes(res?.status)) throw new Error('returned an invalid status');
|
|
142
|
+
entry.status = res.status;
|
|
143
|
+
entry.reason = res.reason ?? '';
|
|
144
|
+
entry.toolVersion = res.toolVersion ?? null;
|
|
145
|
+
entry.dataVersions = res.dataVersions ?? {};
|
|
146
|
+
if (res.status === 'ran') for (const f of res.findings ?? []) rawFindings.push(makeFinding(f, check.id));
|
|
147
|
+
if (res.evidence && typeof res.evidence === 'object') raw[check.id] = res.evidence;
|
|
148
|
+
}
|
|
149
|
+
} catch (e) {
|
|
150
|
+
entry.status = 'not_run';
|
|
151
|
+
entry.reason = scrubText(e.message);
|
|
152
|
+
// a check that failed halfway contributes no findings
|
|
153
|
+
for (let i = rawFindings.length - 1; i >= 0; i--) if (rawFindings[i].check === check.id) rawFindings.splice(i, 1);
|
|
154
|
+
}
|
|
155
|
+
controller.abort(); // a timed-out check's scanner processes must not outlive it
|
|
156
|
+
controllers.delete(controller);
|
|
157
|
+
entry.durationMs = Date.now() - started;
|
|
158
|
+
note(`check ${check.id}: ${entry.status}${entry.reason ? ` (${entry.reason})` : ''} in ${entry.durationMs}ms`);
|
|
159
|
+
coverage.push(entry);
|
|
160
|
+
};
|
|
161
|
+
// Explicit numeric phases provide the one ordering barrier needed by post-build artifact checks. There is no general dependency graph:
|
|
162
|
+
// checks in a phase remain concurrent, and checks without a phase keep the original phase-0 behavior.
|
|
163
|
+
const phaseOf = (c) => c.last ? Number.MAX_SAFE_INTEGER : (c.phase ?? 0);
|
|
164
|
+
const phases = [...new Set(selected.map(phaseOf))].sort((a, b) => a - b);
|
|
165
|
+
for (const phase of phases) await pool(selected.filter((c) => phaseOf(c) === phase), MAX_CONCURRENT, runCheck);
|
|
166
|
+
|
|
167
|
+
// a required check with no implementation is not_run: the gate fails closed
|
|
168
|
+
for (const id of requiredSet) {
|
|
169
|
+
if (!coverage.some((c) => c.id === id)) {
|
|
170
|
+
coverage.push({ id, status: 'not_run', reason: `Check "${id}" is not implemented in this build.`, required: true, toolVersion: null, dataVersions: {}, durationMs: 0 });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
coverage.sort((a, b) => a.id.localeCompare(b.id));
|
|
174
|
+
|
|
175
|
+
const findings = dedupe(rawFindings);
|
|
176
|
+
applyAcceptances(findings, acceptances);
|
|
177
|
+
applyPolicy(findings);
|
|
178
|
+
if (previous) {
|
|
179
|
+
const known = new Set(previous.findings.map((f) => f.fingerprint));
|
|
180
|
+
for (const f of findings) f.isNew = !known.has(f.fingerprint);
|
|
181
|
+
}
|
|
182
|
+
const counts = countsOf(findings);
|
|
183
|
+
const verdict = verdictFor(findings, coverage);
|
|
184
|
+
const sourceEnd = captureReviewContext(repo, runId, out);
|
|
185
|
+
const runtimeEnd = captureRuntimeProvenance({ runtimeRoot: packageRoot });
|
|
186
|
+
const finishedAt = now().toISOString();
|
|
187
|
+
note(`verdict ${verdict}`);
|
|
188
|
+
|
|
189
|
+
const run = { mode, target: flags.target ?? repo, authorization: flags.authorization ?? null, vsgVersion: vsgVersion(), startedAt, finishedAt, verdict, counts, coverage, findings, detection, acceptances, review, removedChecks: removed, raw, log, sourceSnapshot: { schemaVersion: 1, start: sourceStart, end: sourceEnd }, runtimeSnapshot: { schemaVersion: 1, start: runtimeStart, end: runtimeEnd } };
|
|
190
|
+
const outputs = writeOutputs(out, run);
|
|
191
|
+
return { verdict, counts, coverage, findings, outputs, review };
|
|
192
|
+
} finally {
|
|
193
|
+
for (const c of controllers) c.abort();
|
|
194
|
+
lock.release();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Tool registry: one JSON file per pinned tool in tools/registry/. Declared as data, validated on load.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { UsageError } from './errors.mjs';
|
|
5
|
+
import { defaultRegistryDir } from './paths.mjs';
|
|
6
|
+
|
|
7
|
+
const HEX64 = /^[0-9a-f]{64}$/;
|
|
8
|
+
const IMAGE = /^[a-z0-9][a-z0-9._\/-]*(:[\w.-]+)?@sha256:[0-9a-f]{64}$/;
|
|
9
|
+
const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
10
|
+
|
|
11
|
+
function httpsUrl(value) {
|
|
12
|
+
try {
|
|
13
|
+
const u = new URL(value);
|
|
14
|
+
return u.protocol === 'https:' || (u.protocol === 'http:' && LOCAL_HOSTS.has(u.hostname));
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function safeRelative(p) {
|
|
21
|
+
if (typeof p !== 'string' || !p || path.isAbsolute(p) || /^[A-Za-z]:/.test(p) || p.includes('\0')) return false;
|
|
22
|
+
return !p.replace(/\\/g, '/').split('/').includes('..');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function validateTool(t) {
|
|
26
|
+
const errors = [];
|
|
27
|
+
const need = (cond, msg) => { if (!cond) errors.push(msg); };
|
|
28
|
+
if (!t || typeof t !== 'object') return ['not a JSON object'];
|
|
29
|
+
need(typeof t.name === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(t.name), 'name must be lowercase letters, digits and dashes');
|
|
30
|
+
need(typeof t.version === 'string' && t.version.length > 0, 'version is required');
|
|
31
|
+
need(typeof t.license === 'string' && t.license.length > 0, 'license is required');
|
|
32
|
+
need(httpsUrl(t.homepage), 'homepage must be an https URL');
|
|
33
|
+
need(httpsUrl(t.releaseUrl), 'releaseUrl must be an https URL');
|
|
34
|
+
need(t.checksumsUrl === undefined || httpsUrl(t.checksumsUrl), 'checksumsUrl must be an https URL');
|
|
35
|
+
need(t.timeoutSec === undefined || (Number.isFinite(t.timeoutSec) && t.timeoutSec > 0), 'timeoutSec must be a positive number');
|
|
36
|
+
need(t.env === undefined || (Array.isArray(t.env) && t.env.every((n) => typeof n === 'string')), 'env must be an array of variable names');
|
|
37
|
+
need(!!t.platforms !== !!t.image, 'exactly one of platforms or image is required');
|
|
38
|
+
need(t.containerPlatform === undefined || (typeof t.containerPlatform === 'string' && !!t.platforms?.[t.containerPlatform]), 'containerPlatform must name one of the platforms');
|
|
39
|
+
if (t.image !== undefined) need(typeof t.image === 'string' && IMAGE.test(t.image), 'image must be <name>@sha256:<64 hex>');
|
|
40
|
+
if (t.platforms !== undefined) {
|
|
41
|
+
need(t.platforms && typeof t.platforms === 'object' && Object.keys(t.platforms).length > 0, 'platforms must not be empty');
|
|
42
|
+
for (const [key, p] of Object.entries(t.platforms ?? {})) {
|
|
43
|
+
need(/^[a-z0-9]+-[a-z0-9]+$/.test(key), `platform key "${key}" must look like win32-x64`);
|
|
44
|
+
need(httpsUrl(p?.url), `${key}: url must be an https URL`);
|
|
45
|
+
need(HEX64.test(p?.sha256 ?? ''), `${key}: sha256 must be 64 lowercase hex characters`);
|
|
46
|
+
need(['none', 'zip', 'tar.gz'].includes(p?.archive), `${key}: archive must be none, zip or tar.gz`);
|
|
47
|
+
need(safeRelative(p?.bin), `${key}: bin must be a relative path without ..`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (t.verify !== undefined) {
|
|
51
|
+
need(Array.isArray(t.verify?.args) && t.verify.args.every((a) => typeof a === 'string'), 'verify.args must be an array of strings');
|
|
52
|
+
need(typeof t.verify?.expect === 'string', 'verify.expect must be a string');
|
|
53
|
+
}
|
|
54
|
+
return errors;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Loads and validates every registry file. Throws UsageError listing all problems (configuration error). */
|
|
58
|
+
export function loadRegistry(dir = defaultRegistryDir) {
|
|
59
|
+
const tools = new Map();
|
|
60
|
+
const problems = [];
|
|
61
|
+
let files = [];
|
|
62
|
+
try {
|
|
63
|
+
// <tool>-exclusions.json files are data for a check (for example the YARA Forge exclusion list), not tools
|
|
64
|
+
files = fs.readdirSync(dir).filter((f) => f.endsWith('.json') && !f.endsWith('-exclusions.json')).sort();
|
|
65
|
+
} catch { /* no registry directory: empty registry */ }
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
let data;
|
|
68
|
+
try {
|
|
69
|
+
data = JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8'));
|
|
70
|
+
} catch (e) {
|
|
71
|
+
problems.push(`${file}: invalid JSON (${e.message})`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const errors = validateTool(data);
|
|
75
|
+
if (data?.name && file !== `${data.name}.json`) errors.push(`file name must be ${data.name}.json`);
|
|
76
|
+
if (errors.length) problems.push(`${file}: ${errors.join('; ')}`);
|
|
77
|
+
else tools.set(data.name, data);
|
|
78
|
+
}
|
|
79
|
+
if (problems.length) throw new UsageError(`Invalid tool registry: ${problems.join(' | ')}. Fix the registry file.`);
|
|
80
|
+
return tools;
|
|
81
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Output writer (REQ-006): report.md, findings.json, results.sarif, coverage.json, run.log, raw/.
|
|
2
|
+
// Files are staged inside the output directory and moved into place at the end, so a failed run never leaves half-written outputs.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { headline } from './gate.mjs';
|
|
6
|
+
import { scrubText, scrubUrl } from './findings.mjs';
|
|
7
|
+
import { nestedSummary } from './nested.mjs';
|
|
8
|
+
import { toSarif } from './sarif.mjs';
|
|
9
|
+
|
|
10
|
+
const SEV = { critical: 'Critical', high: 'High', medium: 'Medium', low: 'Low', info: 'Info' };
|
|
11
|
+
const OUTPUTS = ['report.md', 'findings.json', 'results.sarif', 'coverage.json', 'run.log', 'raw'];
|
|
12
|
+
|
|
13
|
+
function retainedSnapshot(outDir, key) {
|
|
14
|
+
try {
|
|
15
|
+
const findings = JSON.parse(fs.readFileSync(path.join(outDir, 'findings.json'), 'utf8'))[key];
|
|
16
|
+
const coverage = JSON.parse(fs.readFileSync(path.join(outDir, 'coverage.json'), 'utf8'))[key];
|
|
17
|
+
return findings && JSON.stringify(findings) === JSON.stringify(coverage) ? findings : null;
|
|
18
|
+
} catch { return null; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function findingBlock(f, n, withReason) {
|
|
22
|
+
const where = f.location.file ? `${f.location.file}${f.location.line ? `:${f.location.line}` : ''}`
|
|
23
|
+
: f.location.url ?? (f.location.package ? `${f.location.package}${f.location.version ? `@${f.location.version}` : ''}` : '');
|
|
24
|
+
const lines = [`${n}. **${SEV[f.severity]}**: ${f.title} (${f.id})`];
|
|
25
|
+
if (where) lines.push(` - Where: ${where}`);
|
|
26
|
+
if (withReason && f.blockReason) lines.push(` - Why it blocks: ${f.blockReason}`);
|
|
27
|
+
if (f.description) lines.push(` - What: ${f.description}`);
|
|
28
|
+
if (f.exploited.kev) lines.push(' - Known to be exploited (CISA KEV).');
|
|
29
|
+
if (f.exploited.epss != null) lines.push(` - EPSS score: ${f.exploited.epss}`);
|
|
30
|
+
if (f.evidence) lines.push(` - Evidence: ${f.evidence.replace(/\s+/g, ' ').slice(0, 300)}`);
|
|
31
|
+
if (f.fix) lines.push(` - Fix: ${f.fix}`);
|
|
32
|
+
lines.push(` - Found by: ${f.sources.map((s) => (s.ruleId ? `${s.tool} (${s.ruleId})` : s.tool)).join(', ')}`);
|
|
33
|
+
if (f.isNew === true) lines.push(' - New since the comparison run.');
|
|
34
|
+
return lines.join('\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function renderReport(run) {
|
|
38
|
+
const { verdict, counts, coverage, findings, mode, target, review, acceptances } = run;
|
|
39
|
+
const shownTarget = /^https?:\/\//i.test(String(target)) ? scrubUrl(target) : String(target);
|
|
40
|
+
const out = [`# ${headline(verdict, counts, coverage)}`, '', `Mode: ${mode}. Target: ${shownTarget}. Finished: ${run.finishedAt}.`, ...(run.authorization ? [`Authorized by: ${scrubText(run.authorization)}`] : []), ...(run.detection.nestedRepos?.length ? ['', nestedSummary(run.detection.nestedRepos)] : []), ''];
|
|
41
|
+
|
|
42
|
+
out.push('## Must fix', '');
|
|
43
|
+
const must = findings.filter((f) => f.blocking);
|
|
44
|
+
out.push(...(must.length ? must.map((f, i) => findingBlock(f, i + 1, true) + '\n') : ['Nothing blocks this release.', '']));
|
|
45
|
+
|
|
46
|
+
out.push('## Not tested', '');
|
|
47
|
+
const notRun = coverage.filter((c) => c.status === 'not_run');
|
|
48
|
+
const notApplicable = coverage.filter((c) => c.status === 'not_applicable');
|
|
49
|
+
if (!notRun.length && !notApplicable.length) out.push('Every selected check ran.', '');
|
|
50
|
+
notRun.forEach((c, i) => out.push(`${i + 1}. ${c.id}: did not run${c.required ? ' (required, so the verdict cannot be PASS)' : ''}. ${c.reason}`));
|
|
51
|
+
if (notRun.length) out.push('');
|
|
52
|
+
if (notApplicable.length) out.push(`Not applicable to this repository: ${notApplicable.map((c) => `${c.id}${c.reason ? ` (${c.reason})` : ''}`).join(', ')}.`, '');
|
|
53
|
+
|
|
54
|
+
// A check that ran but could not cover everything (files its tool could not parse, stale data, skipped directories) names what it missed.
|
|
55
|
+
const partial = coverage.filter((c) => c.status === 'ran' && /^degraded:/.test(c.reason ?? ''));
|
|
56
|
+
if (partial.length) {
|
|
57
|
+
out.push('## Not fully tested', '');
|
|
58
|
+
partial.forEach((c, i) => out.push(`${i + 1}. ${c.id}: ran, but ${String(c.reason).replace(/^degraded:\s*/, 'with reduced coverage: ')}`));
|
|
59
|
+
out.push('');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
out.push('## Other findings', '');
|
|
63
|
+
const others = findings.filter((f) => !f.blocking);
|
|
64
|
+
if (!others.length) out.push('None.', '');
|
|
65
|
+
const cats = [...new Set(others.map((f) => f.category))].sort();
|
|
66
|
+
for (const cat of cats) {
|
|
67
|
+
out.push(`### ${cat}`, '');
|
|
68
|
+
others.filter((f) => f.category === cat).forEach((f, i) => out.push(findingBlock(f, i + 1, false) + '\n'));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
out.push('## Acceptances and review items', '');
|
|
72
|
+
const accLines = [
|
|
73
|
+
...findings.filter((f) => f.status === 'accepted').map((f) => `Accepted: ${f.title} (${f.id}).`),
|
|
74
|
+
...findings.filter((f) => f.status === 'expired-acceptance').map((f) => `Acceptance expired, finding is open again: ${f.title} (${f.id}).`),
|
|
75
|
+
...acceptances.invalid.map((a) => `Ignored invalid acceptance (${a.problem}); it suppresses nothing.`),
|
|
76
|
+
...(run.removedChecks ?? []).map((r) => `Required check "${r.id}" was removed by vsg.config.json: ${r.reason}`),
|
|
77
|
+
...review.map((r) => `For human review: ${r}`),
|
|
78
|
+
];
|
|
79
|
+
out.push(...(accLines.length ? accLines.map((l, i) => `${i + 1}. ${l}`) : ['None.']), '');
|
|
80
|
+
|
|
81
|
+
out.push('## Tools and data versions', '');
|
|
82
|
+
out.push(`vsg ${run.vsgVersion}.`);
|
|
83
|
+
for (const c of coverage) {
|
|
84
|
+
const bits = [c.status, `${c.durationMs} ms`];
|
|
85
|
+
if (c.toolVersion) bits.push(`tool ${c.toolVersion}`);
|
|
86
|
+
if (c.dataVersions && Object.keys(c.dataVersions).length) bits.push(`data ${JSON.stringify(c.dataVersions)}`);
|
|
87
|
+
out.push(`- ${c.id}: ${bits.join(', ')}`);
|
|
88
|
+
}
|
|
89
|
+
if (run.detection.stacks.length) out.push(`- Detected: ${run.detection.stacks.join(', ')}`);
|
|
90
|
+
return `${out.join('\n')}\n`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Writes all outputs. `raw` maps check id to { filename: text }. Returns the output paths. */
|
|
94
|
+
export function writeOutputs(outDir, run) {
|
|
95
|
+
const sourceSnapshot = run.sourceSnapshot ?? retainedSnapshot(outDir, 'sourceSnapshot');
|
|
96
|
+
const runtimeSnapshot = run.runtimeSnapshot ?? retainedSnapshot(outDir, 'runtimeSnapshot');
|
|
97
|
+
const staging = path.join(outDir, `.staging-${process.pid}`);
|
|
98
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
99
|
+
fs.mkdirSync(path.join(staging, 'raw'), { recursive: true });
|
|
100
|
+
const json = (x) => `${JSON.stringify(x, null, 2)}\n`;
|
|
101
|
+
const findingsDoc = {
|
|
102
|
+
schemaVersion: 1, vsgVersion: run.vsgVersion, mode: run.mode, target: /^https?:\/\//i.test(String(run.target)) ? scrubUrl(run.target) : run.target, authorization: run.authorization ? scrubText(run.authorization) : null, startedAt: run.startedAt, finishedAt: run.finishedAt,
|
|
103
|
+
verdict: run.verdict, counts: run.counts, checks: run.coverage, findings: run.findings,
|
|
104
|
+
acceptances: { valid: run.acceptances.valid.length, invalid: run.acceptances.invalid.length }, review: run.review,
|
|
105
|
+
...(sourceSnapshot ? { sourceSnapshot } : {}), ...(runtimeSnapshot ? { runtimeSnapshot } : {}),
|
|
106
|
+
};
|
|
107
|
+
fs.writeFileSync(path.join(staging, 'findings.json'), json(findingsDoc));
|
|
108
|
+
fs.writeFileSync(path.join(staging, 'results.sarif'), json(toSarif(run.findings, { vsgVersion: run.vsgVersion })));
|
|
109
|
+
fs.writeFileSync(path.join(staging, 'coverage.json'), json({ schemaVersion: 1, mode: run.mode, detection: run.detection, skippedNestedRepos: run.detection.nestedRepos?.length ? { count: run.detection.nestedRepos.length, summary: nestedSummary(run.detection.nestedRepos), repos: run.detection.nestedRepos } : null, checks: run.coverage, ...(sourceSnapshot ? { sourceSnapshot } : {}), ...(runtimeSnapshot ? { runtimeSnapshot } : {}) }));
|
|
110
|
+
fs.writeFileSync(path.join(staging, 'report.md'), renderReport(run));
|
|
111
|
+
fs.writeFileSync(path.join(staging, 'run.log'), `${run.log.map((l) => scrubText(l)).join('\n')}\n`);
|
|
112
|
+
for (const [checkId, files] of Object.entries(run.raw)) {
|
|
113
|
+
for (const [name, content] of Object.entries(files)) {
|
|
114
|
+
const dir = path.join(staging, 'raw', path.basename(checkId));
|
|
115
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
116
|
+
fs.writeFileSync(path.join(dir, path.basename(name)), scrubText(Buffer.isBuffer(content) ? content.toString('utf8') : typeof content === 'string' ? content : JSON.stringify(content, null, 2)));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const name of OUTPUTS) {
|
|
120
|
+
fs.rmSync(path.join(outDir, name), { recursive: true, force: true });
|
|
121
|
+
fs.renameSync(path.join(staging, name), path.join(outDir, name));
|
|
122
|
+
}
|
|
123
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
124
|
+
return {
|
|
125
|
+
dir: outDir, report: path.join(outDir, 'report.md'), findings: path.join(outDir, 'findings.json'), sarif: path.join(outDir, 'results.sarif'),
|
|
126
|
+
coverage: path.join(outDir, 'coverage.json'), log: path.join(outDir, 'run.log'), raw: path.join(outDir, 'raw'),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Safe process execution: argument arrays only, environment allowlist, timeout with process-tree kill.
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
import { scrubText } from './findings.mjs';
|
|
4
|
+
|
|
5
|
+
// Names every scanner may inherit. Anything else (tokens, cloud keys, proxies with credentials) must be named per tool.
|
|
6
|
+
const BASE_ENV = new Set([
|
|
7
|
+
'PATH', 'PATHEXT', 'SYSTEMROOT', 'SYSTEMDRIVE', 'WINDIR', 'COMSPEC', 'TEMP', 'TMP', 'TMPDIR', 'HOME', 'HOMEDRIVE', 'HOMEPATH',
|
|
8
|
+
'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'PROGRAMDATA', 'PROGRAMFILES', 'PROGRAMFILES(X86)', 'PROGRAMW6432',
|
|
9
|
+
'NUMBER_OF_PROCESSORS', 'PROCESSOR_ARCHITECTURE', 'OS', 'LANG', 'LC_ALL', 'TERM', 'TZ', 'NO_COLOR', 'CI',
|
|
10
|
+
'XDG_CACHE_HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME',
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export function buildEnv(envNames = [], extra = {}) {
|
|
14
|
+
const allowed = new Set([...BASE_ENV, ...envNames.map((n) => n.toUpperCase())]);
|
|
15
|
+
const env = {};
|
|
16
|
+
for (const [k, v] of Object.entries(process.env)) if (allowed.has(k.toUpperCase())) env[k] = v;
|
|
17
|
+
return { ...env, ...extra };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function killTree(child) {
|
|
21
|
+
if (!child.pid) return;
|
|
22
|
+
if (process.platform === 'win32') {
|
|
23
|
+
spawnSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
|
|
24
|
+
} else {
|
|
25
|
+
try { process.kill(-child.pid, 'SIGKILL'); } catch { try { child.kill('SIGKILL'); } catch { /* already gone */ } }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Runs a command. Resolves { status: 'ran'|'not_run', exitCode, stdout, stderr, durationMs, reason }.
|
|
31
|
+
* 'ran' means the process started and exited (the caller interprets exitCode). 'not_run' means it could not start, timed out, was
|
|
32
|
+
* cancelled through `signal` (an AbortSignal; the whole process tree is killed), or wrote more than maxOutputBytes (the tree is
|
|
33
|
+
* killed then too: an incomplete output is never read as a result).
|
|
34
|
+
*/
|
|
35
|
+
export function runProcess({ command, args = [], cwd, envNames = [], extraEnv = {}, timeoutMs = 600_000, maxOutputBytes = 32 * 1024 * 1024, input, log, signal }) {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
const started = Date.now();
|
|
38
|
+
const chunks = { out: [], err: [] };
|
|
39
|
+
let size = 0;
|
|
40
|
+
let truncated = false;
|
|
41
|
+
let timedOut = false;
|
|
42
|
+
let aborted = false;
|
|
43
|
+
let settled = false;
|
|
44
|
+
let child;
|
|
45
|
+
let timer; // declared before finish() can run: spawn() may throw synchronously (seen in sandboxed agent shells)
|
|
46
|
+
const onAbort = () => { aborted = true; if (child) killTree(child); };
|
|
47
|
+
const finish = (result) => {
|
|
48
|
+
if (settled) return;
|
|
49
|
+
settled = true;
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
signal?.removeEventListener('abort', onAbort);
|
|
52
|
+
const durationMs = Date.now() - started;
|
|
53
|
+
const full = { stdout: Buffer.concat(chunks.out).toString('utf8'), stderr: Buffer.concat(chunks.err).toString('utf8'), durationMs, truncated, ...result };
|
|
54
|
+
log?.(`$ ${[command, ...args].map((a) => scrubText(a)).join(' ')} -> ${full.status}${full.exitCode !== undefined ? ` exit ${full.exitCode}` : ''}${full.reason ? ` (${full.reason})` : ''} in ${durationMs}ms`);
|
|
55
|
+
resolve(full);
|
|
56
|
+
};
|
|
57
|
+
if (signal?.aborted) { finish({ status: 'not_run', reason: 'cancelled before it started' }); return; }
|
|
58
|
+
try {
|
|
59
|
+
child = spawn(command, args, { cwd, env: buildEnv(envNames, extraEnv), shell: false, windowsHide: true, detached: process.platform !== 'win32', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
60
|
+
} catch (e) {
|
|
61
|
+
finish({ status: 'not_run', reason: `could not start ${command}: ${e.message}` });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
65
|
+
timer = setTimeout(() => { timedOut = true; killTree(child); }, timeoutMs);
|
|
66
|
+
const collect = (key) => (d) => {
|
|
67
|
+
size += d.length;
|
|
68
|
+
if (size > maxOutputBytes) {
|
|
69
|
+
if (!truncated) { truncated = true; killTree(child); }
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
chunks[key].push(d);
|
|
73
|
+
};
|
|
74
|
+
child.stdout.on('data', collect('out'));
|
|
75
|
+
child.stderr.on('data', collect('err'));
|
|
76
|
+
child.on('error', (e) => finish({ status: 'not_run', reason: `could not start ${command}: ${e.message}` }));
|
|
77
|
+
child.on('close', (code, signalName) => {
|
|
78
|
+
if (truncated) finish({ status: 'not_run', reason: `output exceeded ${Math.round((maxOutputBytes / 1048576) * 100) / 100} MB and the process was stopped` });
|
|
79
|
+
else if (aborted) finish({ status: 'not_run', reason: 'cancelled and stopped' });
|
|
80
|
+
else if (timedOut) finish({ status: 'not_run', reason: `timed out after ${timeoutMs / 1000}s and was stopped` });
|
|
81
|
+
else finish({ status: 'ran', exitCode: code ?? -1, signal: signalName });
|
|
82
|
+
});
|
|
83
|
+
child.stdin.on('error', () => {});
|
|
84
|
+
child.stdin.end(input);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// SARIF 2.1.0 in (normalizer) and out (results.sarif), plus the CWE to OWASP Top 10:2025 map.
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import { scrubText, redact } from './findings.mjs';
|
|
4
|
+
|
|
5
|
+
const A = {
|
|
6
|
+
A01: 'A01:2025 Broken Access Control',
|
|
7
|
+
A02: 'A02:2025 Security Misconfiguration',
|
|
8
|
+
A03: 'A03:2025 Software Supply Chain Failures',
|
|
9
|
+
A04: 'A04:2025 Cryptographic Failures',
|
|
10
|
+
A05: 'A05:2025 Injection',
|
|
11
|
+
A06: 'A06:2025 Insecure Design',
|
|
12
|
+
A07: 'A07:2025 Authentication Failures',
|
|
13
|
+
A08: 'A08:2025 Software or Data Integrity Failures',
|
|
14
|
+
A09: 'A09:2025 Security Logging and Alerting Failures',
|
|
15
|
+
A10: 'A10:2025 Mishandling of Exceptional Conditions',
|
|
16
|
+
};
|
|
17
|
+
const GROUPS = {
|
|
18
|
+
A01: [22, 23, 36, 73, 200, 201, 219, 284, 285, 352, 359, 402, 425, 441, 497, 538, 540, 548, 552, 566, 601, 639, 668, 706, 862, 863, 918, 922, 1275],
|
|
19
|
+
A02: [2, 11, 13, 15, 16, 260, 315, 489, 547, 611, 614, 756, 776, 942, 1004, 1032, 1174],
|
|
20
|
+
A03: [829, 937, 1035, 1104, 1357, 1395],
|
|
21
|
+
A04: [261, 296, 310, 311, 312, 319, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 335, 336, 337, 338, 340, 347, 523, 757, 759, 760, 780, 818, 916],
|
|
22
|
+
A05: [20, 74, 75, 77, 78, 79, 80, 83, 87, 88, 89, 90, 91, 93, 94, 95, 96, 97, 98, 99, 113, 116, 138, 184, 470, 471, 564, 610, 643, 644, 652, 917, 943, 1236, 1336],
|
|
23
|
+
A06: [73, 183, 256, 266, 269, 311, 419, 430, 434, 444, 451, 463, 501, 522, 525, 539, 579, 598, 602, 642, 646, 653, 656, 657, 799, 807, 840, 841, 927, 1021, 1173],
|
|
24
|
+
A07: [255, 259, 287, 288, 290, 294, 295, 297, 300, 302, 304, 306, 307, 346, 384, 521, 613, 620, 640, 798, 940, 941, 1216],
|
|
25
|
+
A08: [345, 353, 426, 494, 502, 565, 784, 830, 915],
|
|
26
|
+
A09: [117, 223, 532, 778],
|
|
27
|
+
A10: [209, 215, 234, 248, 252, 274, 280, 369, 390, 391, 394, 396, 397, 431, 456, 476, 636, 703, 754, 755, 833],
|
|
28
|
+
};
|
|
29
|
+
// first group listed wins when a CWE appears in several groups
|
|
30
|
+
export const CWE_TO_OWASP = {};
|
|
31
|
+
for (const [group, cwes] of Object.entries(GROUPS)) for (const n of cwes) CWE_TO_OWASP[n] ??= A[group];
|
|
32
|
+
|
|
33
|
+
export function owaspForCwes(cwes) {
|
|
34
|
+
const out = new Set();
|
|
35
|
+
for (const c of cwes) {
|
|
36
|
+
const n = Number(String(c).replace(/\D/g, ''));
|
|
37
|
+
if (CWE_TO_OWASP[n]) out.add(CWE_TO_OWASP[n]);
|
|
38
|
+
}
|
|
39
|
+
return [...out];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** security-severity 9+ critical, 7+ high, 4+ medium, above 0 low; otherwise level error/warning/note -> high/medium/low. */
|
|
43
|
+
export function severityFromSarif(result, rule) {
|
|
44
|
+
const raw = result?.properties?.['security-severity'] ?? rule?.properties?.['security-severity'];
|
|
45
|
+
const score = raw === undefined ? NaN : Number(raw);
|
|
46
|
+
if (Number.isFinite(score) && score > 0) return score >= 9 ? 'critical' : score >= 7 ? 'high' : score >= 4 ? 'medium' : 'low';
|
|
47
|
+
const level = result?.level ?? rule?.defaultConfiguration?.level ?? 'warning';
|
|
48
|
+
return { error: 'high', warning: 'medium', note: 'low' }[level] ?? 'info';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const PRECISION = { 'very-high': 'high', high: 'high', medium: 'medium', low: 'low' };
|
|
52
|
+
|
|
53
|
+
function cwesOf(rule, result) {
|
|
54
|
+
const found = new Set();
|
|
55
|
+
const sources = [...(rule?.properties?.tags ?? []), ...(result?.properties?.tags ?? []), rule?.properties?.cwe, ...(rule?.relationships ?? []).map((r) => r?.target?.id)].flat();
|
|
56
|
+
for (const s of sources) {
|
|
57
|
+
const m = typeof s === 'string' ? /cwe[-/ _]?(\d+)/i.exec(s) : null;
|
|
58
|
+
if (m) found.add(`CWE-${m[1]}`);
|
|
59
|
+
}
|
|
60
|
+
return [...found];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function uriToPath(uri, repo) {
|
|
64
|
+
let p = String(uri ?? '');
|
|
65
|
+
if (p.startsWith('file:///')) p = p.slice(8);
|
|
66
|
+
else if (p.startsWith('file://')) p = p.slice(7);
|
|
67
|
+
try { p = decodeURIComponent(p); } catch { /* keep raw */ }
|
|
68
|
+
p = p.replace(/\\/g, '/');
|
|
69
|
+
if (repo) {
|
|
70
|
+
const root = repo.replace(/\\/g, '/').replace(/\/$/, '');
|
|
71
|
+
if (p.toLowerCase().startsWith(`${root.toLowerCase()}/`)) p = p.slice(root.length + 1);
|
|
72
|
+
}
|
|
73
|
+
return p.replace(/^\.\//, '');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Normalizes SARIF 2.1.0 to raw findings (input for makeFinding).
|
|
78
|
+
* opts: { tool, category, repo, secrets (redact snippets), fix, confidence }
|
|
79
|
+
*/
|
|
80
|
+
export function normalizeSarif(sarif, opts = {}) {
|
|
81
|
+
const out = [];
|
|
82
|
+
for (const run of sarif?.runs ?? []) {
|
|
83
|
+
const driver = run.tool?.driver ?? {};
|
|
84
|
+
const rules = driver.rules ?? [];
|
|
85
|
+
const byId = new Map(rules.map((r) => [r.id, r]));
|
|
86
|
+
for (const result of run.results ?? []) {
|
|
87
|
+
const rule = byId.get(result.ruleId) ?? rules[result.ruleIndex];
|
|
88
|
+
const ruleId = result.ruleId ?? rule?.id ?? 'unknown';
|
|
89
|
+
const phys = result.locations?.[0]?.physicalLocation;
|
|
90
|
+
const file = phys?.artifactLocation?.uri ? uriToPath(phys.artifactLocation.uri, opts.repo) : undefined;
|
|
91
|
+
const line = phys?.region?.startLine;
|
|
92
|
+
const snippet = phys?.region?.snippet?.text ?? phys?.contextRegion?.snippet?.text ?? '';
|
|
93
|
+
const evidence = snippet ? (opts.secrets ? redact(snippet.trim()) : scrubText(snippet.trim().slice(0, 500))) : '';
|
|
94
|
+
const cwe = cwesOf(rule, result);
|
|
95
|
+
out.push({
|
|
96
|
+
tool: opts.tool ?? driver.name,
|
|
97
|
+
ruleId,
|
|
98
|
+
key: `${ruleId}:${crypto.createHash('sha256').update(snippet.trim() || String(line ?? '')).digest('hex').slice(0, 12)}`,
|
|
99
|
+
category: opts.category ?? 'other',
|
|
100
|
+
severity: severityFromSarif(result, rule),
|
|
101
|
+
confidence: opts.confidence ?? PRECISION[rule?.properties?.precision] ?? 'medium',
|
|
102
|
+
title: rule?.shortDescription?.text ?? rule?.name ?? ruleId,
|
|
103
|
+
description: result.message?.text ?? rule?.fullDescription?.text ?? '',
|
|
104
|
+
location: { file, line },
|
|
105
|
+
evidence,
|
|
106
|
+
refs: [rule?.helpUri].filter(Boolean),
|
|
107
|
+
fix: opts.fix ?? '',
|
|
108
|
+
cwe,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const LEVEL = { critical: 'error', high: 'error', medium: 'warning', low: 'note', info: 'note' };
|
|
116
|
+
const SCORE = { critical: 9.5, high: 8, medium: 5, low: 2 };
|
|
117
|
+
|
|
118
|
+
/** Writes our findings as SARIF 2.1.0 (results.sarif). */
|
|
119
|
+
export function toSarif(findings, { vsgVersion = '0.0.0' } = {}) {
|
|
120
|
+
const rules = new Map();
|
|
121
|
+
const results = findings.map((f) => {
|
|
122
|
+
const ruleId = `${f.check}/${f.sources[0]?.ruleId ?? f.id}`;
|
|
123
|
+
if (!rules.has(ruleId)) rules.set(ruleId, { id: ruleId, name: ruleId, shortDescription: { text: f.title } });
|
|
124
|
+
const result = {
|
|
125
|
+
ruleId,
|
|
126
|
+
level: LEVEL[f.severity],
|
|
127
|
+
message: { text: f.description ? `${f.title}: ${f.description}` : f.title },
|
|
128
|
+
partialFingerprints: { vsgFingerprint: f.fingerprint },
|
|
129
|
+
properties: { ...(SCORE[f.severity] ? { 'security-severity': String(SCORE[f.severity]) } : {}), severity: f.severity, blocking: f.blocking, status: f.status },
|
|
130
|
+
};
|
|
131
|
+
if (f.location.file) {
|
|
132
|
+
result.locations = [{ physicalLocation: { artifactLocation: { uri: f.location.file }, ...(f.location.line ? { region: { startLine: f.location.line } } : {}) } }];
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
138
|
+
version: '2.1.0',
|
|
139
|
+
runs: [{ tool: { driver: { name: 'vsg', version: vsgVersion, rules: [...rules.values()] } }, results }],
|
|
140
|
+
};
|
|
141
|
+
}
|