@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,114 @@
|
|
|
1
|
+
// Archive extraction with Node built-ins only (zip and tar.gz). Every entry path is validated before anything is written.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import zlib from 'node:zlib';
|
|
5
|
+
import { InstallError } from './errors.mjs';
|
|
6
|
+
|
|
7
|
+
const MAX_TOTAL = 4 * 1024 ** 3;
|
|
8
|
+
|
|
9
|
+
/** Resolves an archive entry name inside root, or throws if it would escape (absolute, drive letter, "..", NUL). */
|
|
10
|
+
export function safeJoin(root, name) {
|
|
11
|
+
const n = String(name).replace(/\\/g, '/');
|
|
12
|
+
if (!n || n.includes('\0') || n.startsWith('/') || /^[A-Za-z]:/.test(n) || n.split('/').includes('..')) {
|
|
13
|
+
throw new InstallError(`Archive entry "${name}" would escape the install directory. Nothing was extracted.`);
|
|
14
|
+
}
|
|
15
|
+
const resolved = path.resolve(root, n);
|
|
16
|
+
if (resolved !== path.resolve(root) && !resolved.startsWith(path.resolve(root) + path.sep)) {
|
|
17
|
+
throw new InstallError(`Archive entry "${name}" would escape the install directory. Nothing was extracted.`);
|
|
18
|
+
}
|
|
19
|
+
return resolved;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Returns [{ name, dir, mode, data() }]. */
|
|
23
|
+
export function listZip(buf) {
|
|
24
|
+
let eocd = -1;
|
|
25
|
+
for (let i = buf.length - 22; i >= Math.max(0, buf.length - 65557); i--) {
|
|
26
|
+
if (buf.readUInt32LE(i) === 0x06054b50) { eocd = i; break; }
|
|
27
|
+
}
|
|
28
|
+
if (eocd < 0) throw new InstallError('Not a valid zip archive.');
|
|
29
|
+
const count = buf.readUInt16LE(eocd + 10);
|
|
30
|
+
let p = buf.readUInt32LE(eocd + 16);
|
|
31
|
+
if (count === 0xffff || p === 0xffffffff) throw new InstallError('Zip64 archives are not supported.');
|
|
32
|
+
const entries = [];
|
|
33
|
+
for (let i = 0; i < count; i++) {
|
|
34
|
+
if (buf.readUInt32LE(p) !== 0x02014b50) throw new InstallError('Corrupt zip directory.');
|
|
35
|
+
const madeBy = buf.readUInt16LE(p + 4);
|
|
36
|
+
const flags = buf.readUInt16LE(p + 8);
|
|
37
|
+
const method = buf.readUInt16LE(p + 10);
|
|
38
|
+
const compSize = buf.readUInt32LE(p + 20);
|
|
39
|
+
const size = buf.readUInt32LE(p + 24);
|
|
40
|
+
const nameLen = buf.readUInt16LE(p + 28);
|
|
41
|
+
const extraLen = buf.readUInt16LE(p + 30);
|
|
42
|
+
const commentLen = buf.readUInt16LE(p + 32);
|
|
43
|
+
const ext = buf.readUInt32LE(p + 38);
|
|
44
|
+
const local = buf.readUInt32LE(p + 42);
|
|
45
|
+
const name = buf.toString('utf8', p + 46, p + 46 + nameLen);
|
|
46
|
+
p += 46 + nameLen + extraLen + commentLen;
|
|
47
|
+
if (flags & 1) throw new InstallError('Encrypted zip entries are not supported.');
|
|
48
|
+
const unixType = madeBy >> 8 === 3 ? (ext >>> 16) & 0xf000 : 0;
|
|
49
|
+
if (unixType === 0xa000) throw new InstallError(`Archive entry "${name}" is a symbolic link, which is not allowed.`);
|
|
50
|
+
entries.push({
|
|
51
|
+
name,
|
|
52
|
+
dir: name.endsWith('/') || name.endsWith('\\'),
|
|
53
|
+
mode: unixType ? (ext >>> 16) & 0o777 : 0,
|
|
54
|
+
data() {
|
|
55
|
+
if (buf.readUInt32LE(local) !== 0x04034b50) throw new InstallError('Corrupt zip entry.');
|
|
56
|
+
const start = local + 30 + buf.readUInt16LE(local + 26) + buf.readUInt16LE(local + 28);
|
|
57
|
+
const raw = buf.subarray(start, start + compSize);
|
|
58
|
+
const out = method === 0 ? raw : method === 8 ? zlib.inflateRawSync(raw, { maxOutputLength: Math.max(size, 1) }) : null;
|
|
59
|
+
if (!out) throw new InstallError(`Unsupported zip compression method ${method}.`);
|
|
60
|
+
if (out.length !== size) throw new InstallError(`Zip entry "${name}" has the wrong size.`);
|
|
61
|
+
return out;
|
|
62
|
+
},
|
|
63
|
+
size,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return entries;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const octal = (b) => parseInt(b.toString('latin1').replace(/\0.*$/s, '').trim() || '0', 8);
|
|
70
|
+
const cstr = (b) => b.toString('utf8').replace(/\0.*$/s, '');
|
|
71
|
+
|
|
72
|
+
export function listTarGz(gz) {
|
|
73
|
+
const buf = zlib.gunzipSync(gz, { maxOutputLength: MAX_TOTAL });
|
|
74
|
+
const entries = [];
|
|
75
|
+
let pendingName = null;
|
|
76
|
+
for (let i = 0; i + 512 <= buf.length;) {
|
|
77
|
+
const h = buf.subarray(i, i + 512);
|
|
78
|
+
if (h.every((b) => b === 0)) break;
|
|
79
|
+
let name = cstr(h.subarray(0, 100));
|
|
80
|
+
if (cstr(h.subarray(257, 262)) === 'ustar' && cstr(h.subarray(345, 500))) name = `${cstr(h.subarray(345, 500))}/${name}`;
|
|
81
|
+
const size = octal(h.subarray(124, 136));
|
|
82
|
+
const type = String.fromCharCode(h[156] || 48);
|
|
83
|
+
const body = buf.subarray(i + 512, i + 512 + size);
|
|
84
|
+
i += 512 + Math.ceil(size / 512) * 512;
|
|
85
|
+
if (type === 'x') {
|
|
86
|
+
for (const m of body.toString('utf8').matchAll(/\d+ path=([^\n]*)\n/g)) pendingName = m[1];
|
|
87
|
+
} else if (type === 'g') {
|
|
88
|
+
continue;
|
|
89
|
+
} else if (type === 'L') {
|
|
90
|
+
pendingName = cstr(body);
|
|
91
|
+
} else if (type === '0' || type === '5') {
|
|
92
|
+
entries.push({ name: pendingName ?? name, dir: type === '5', mode: octal(h.subarray(100, 108)) & 0o777, data: () => body, size });
|
|
93
|
+
pendingName = null;
|
|
94
|
+
} else {
|
|
95
|
+
throw new InstallError(`Archive entry "${name}" has unsupported type "${type}" (links and devices are not allowed).`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return entries;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Extracts into targetDir (created). All entry names are validated before the first write. */
|
|
102
|
+
export function extractArchive(buf, kind, targetDir) {
|
|
103
|
+
const entries = kind === 'zip' ? listZip(buf) : kind === 'tar.gz' ? listTarGz(buf) : null;
|
|
104
|
+
if (!entries) throw new InstallError(`Unknown archive type "${kind}".`);
|
|
105
|
+
const total = entries.reduce((n, e) => n + (e.size ?? 0), 0);
|
|
106
|
+
if (total > MAX_TOTAL) throw new InstallError('Archive expands to more than 4 GB.');
|
|
107
|
+
const planned = entries.map((e) => ({ e, dest: safeJoin(targetDir, e.name) }));
|
|
108
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
109
|
+
for (const { e, dest } of planned) {
|
|
110
|
+
if (e.dir) { fs.mkdirSync(dest, { recursive: true }); continue; }
|
|
111
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
112
|
+
fs.writeFileSync(dest, e.data(), { mode: e.mode & 0o111 ? 0o755 : 0o644 });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// The comparison base for acceptance review (accept.mjs) and dependency changes (checks/dep-change): one resolver, so a report never says
|
|
2
|
+
// "no base ref" while another check found the baseline. Reads git only; the repository's own git config cannot run code (core.fsmonitor off).
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Remote branches tried, in order, when no base is given. actions/checkout never creates origin/HEAD, so a CI run only has origin/<branch>
|
|
9
|
+
* (the pull request's base branch, or the default branch).
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_BASES = ['origin/HEAD', 'origin/main', 'origin/master'];
|
|
12
|
+
|
|
13
|
+
const git = (repo, args) => {
|
|
14
|
+
try {
|
|
15
|
+
return execFileSync('git', ['-c', 'core.fsmonitor=false', '--no-optional-locks', '-C', repo, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 20_000 }).trim();
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const norm = (p) => path.resolve(p).replace(/\\/g, '/').toLowerCase();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* { sha, ref, how } of the comparison base, or { sha: null, why }. The base is, in order: the --base flag, vsg.config.json baseRef,
|
|
25
|
+
* then the merge-base of HEAD with origin/HEAD, origin/main or origin/master. Needs a git repository rooted at the scanned directory.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveBase(repo, { flag, config } = {}) {
|
|
28
|
+
const top = git(repo, ['rev-parse', '--show-toplevel']);
|
|
29
|
+
if (top === null || !top) return { sha: null, why: 'the directory is not a git repository' };
|
|
30
|
+
let same = false;
|
|
31
|
+
try { same = norm(fs.realpathSync(top)) === norm(fs.realpathSync(repo)); } catch { /* compared below */ }
|
|
32
|
+
if (!same) return { sha: null, why: 'the scanned directory is not the root of its git repository' };
|
|
33
|
+
const explicit = flag ?? config?.baseRef;
|
|
34
|
+
if (explicit) {
|
|
35
|
+
const sha = git(repo, ['rev-parse', '--verify', '--quiet', `${explicit}^{commit}`]);
|
|
36
|
+
return sha ? { sha, ref: explicit, how: flag ? '--base' : 'vsg.config.json baseRef' } : { sha: null, why: `base ref "${explicit}" does not exist` };
|
|
37
|
+
}
|
|
38
|
+
for (const remote of DEFAULT_BASES) {
|
|
39
|
+
const sha = git(repo, ['merge-base', 'HEAD', remote]);
|
|
40
|
+
if (sha) return { sha, ref: remote, how: `merge-base with ${remote}` };
|
|
41
|
+
}
|
|
42
|
+
return { sha: null, why: 'no remote default branch (origin/HEAD, origin/main or origin/master) was found' };
|
|
43
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Check discovery: every src/checks/<id>/index.mjs exports { id, title, modes, requiredIn, applies(ctx), run(ctx) }.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { UsageError } from './errors.mjs';
|
|
6
|
+
import { defaultChecksDir } from './paths.mjs';
|
|
7
|
+
|
|
8
|
+
const MODES = ['scan', 'deep', 'url', 'release'];
|
|
9
|
+
|
|
10
|
+
export function validateCheck(c, where) {
|
|
11
|
+
const bad = (m) => { throw new UsageError(`Check ${where} is invalid: ${m}.`); };
|
|
12
|
+
if (!c || typeof c.id !== 'string' || !c.id) bad('id is required');
|
|
13
|
+
if (typeof c.title !== 'string') bad('title is required');
|
|
14
|
+
if (!Array.isArray(c.modes) || c.modes.some((m) => !MODES.includes(m))) bad('modes must list scan, deep, url or release');
|
|
15
|
+
if (!Array.isArray(c.requiredIn) || c.requiredIn.some((m) => !MODES.includes(m))) bad('requiredIn must list modes');
|
|
16
|
+
if (typeof c.applies !== 'function' || typeof c.run !== 'function') bad('applies and run must be functions');
|
|
17
|
+
return c;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Scans each directory for <id>/index.mjs. A later directory overrides an earlier check with the same id (used by tests). */
|
|
21
|
+
export async function discoverChecks(dirs = [defaultChecksDir]) {
|
|
22
|
+
const byId = new Map();
|
|
23
|
+
for (const dir of dirs) {
|
|
24
|
+
let names = [];
|
|
25
|
+
try { names = fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort(); } catch { continue; }
|
|
26
|
+
for (const name of names) {
|
|
27
|
+
const file = path.join(dir, name, 'index.mjs');
|
|
28
|
+
if (!fs.existsSync(file)) continue;
|
|
29
|
+
let mod;
|
|
30
|
+
try { mod = await import(pathToFileURL(file).href); } catch (e) { throw new UsageError(`Check ${name} failed to load: ${e.message}`); }
|
|
31
|
+
const check = validateCheck(mod.default ?? mod, file);
|
|
32
|
+
byId.set(check.id, { id: check.id, title: check.title, modes: check.modes, requiredIn: check.requiredIn, applies: check.applies, run: check.run, timeoutSec: check.timeoutSec,
|
|
33
|
+
// A small phase number is an ordering barrier, not a dependency graph. Checks in one phase still run concurrently.
|
|
34
|
+
phase: Number.isInteger(check.phase) && check.phase >= 0 ? check.phase : 0,
|
|
35
|
+
last: check.last === true, requiredIfApplied: check.requiredIfApplied === true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
39
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Stack detection (REQ-007). Walks the repository once (no symlink following), then applies rule tables.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { findNestedRepos, primeNested, primedNested } from './nested.mjs';
|
|
5
|
+
|
|
6
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', '.vsg', '.next', '.expo', '.venv', 'venv', '__pycache__', '.turbo', '.cache', 'coverage', 'dist', 'build', '.pnpm-store']);
|
|
7
|
+
const MAX_FILES = 100_000;
|
|
8
|
+
const MAX_DEPTH = 10;
|
|
9
|
+
|
|
10
|
+
const LOCKFILES = {
|
|
11
|
+
'package-lock.json': 'npm', 'npm-shrinkwrap.json': 'npm', 'pnpm-lock.yaml': 'pnpm', 'yarn.lock': 'yarn', 'bun.lock': 'bun', 'bun.lockb': 'bun',
|
|
12
|
+
'poetry.lock': 'poetry', 'Pipfile.lock': 'pipenv', 'uv.lock': 'uv', 'Cargo.lock': 'cargo', 'go.sum': 'go', 'composer.lock': 'composer', 'Gemfile.lock': 'bundler',
|
|
13
|
+
};
|
|
14
|
+
const AI_NODE = [/^openai$/, /^@anthropic-ai\//, /^ai$/, /^@ai-sdk\//, /^@google\/generative-ai$/, /^@google\/genai$/, /^langchain$/, /^@langchain\//, /^@mistralai\//, /^groq-sdk$/];
|
|
15
|
+
const AI_PY = ['openai', 'anthropic', 'langchain', 'google-generativeai', 'mistralai', 'groq'];
|
|
16
|
+
|
|
17
|
+
/** Every file below root (posix relative paths), leaving out nested repositories and worktrees (see nested.mjs). */
|
|
18
|
+
export function listFiles(root, nested = primedNested(root) ?? findNestedRepos(root)) {
|
|
19
|
+
const skip = new Set(nested.map((n) => n.path));
|
|
20
|
+
const files = [];
|
|
21
|
+
const walk = (dir, depth) => {
|
|
22
|
+
if (depth > MAX_DEPTH || files.length >= MAX_FILES) return;
|
|
23
|
+
let entries;
|
|
24
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
25
|
+
for (const e of entries) {
|
|
26
|
+
if (e.isSymbolicLink()) continue;
|
|
27
|
+
const full = path.join(dir, e.name);
|
|
28
|
+
if (e.isDirectory()) { if (!SKIP_DIRS.has(e.name) && !skip.has(path.relative(root, full).split(path.sep).join('/'))) walk(full, depth + 1); }
|
|
29
|
+
else if (e.isFile()) files.push(path.relative(root, full).split(path.sep).join('/'));
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
walk(root, 0);
|
|
33
|
+
return files.sort();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const readText = (root, rel) => { try { return fs.readFileSync(path.join(root, rel), 'utf8'); } catch { return ''; } };
|
|
37
|
+
const base = (f) => f.slice(f.lastIndexOf('/') + 1);
|
|
38
|
+
|
|
39
|
+
export function detect(root) {
|
|
40
|
+
const nestedRepos = findNestedRepos(root);
|
|
41
|
+
primeNested(root, nestedRepos);
|
|
42
|
+
const files = listFiles(root, nestedRepos);
|
|
43
|
+
const lockfiles = files.filter((f) => LOCKFILES[base(f)]).map((file) => ({ file, manager: LOCKFILES[base(file)] }));
|
|
44
|
+
const packageJsons = files.filter((f) => base(f) === 'package.json');
|
|
45
|
+
const manifests = files.filter((f) => /^requirements.*\.txt$/.test(base(f)) || ['pyproject.toml', 'Pipfile', 'setup.py', 'go.mod', 'Cargo.toml', 'composer.json', 'Gemfile'].includes(base(f)));
|
|
46
|
+
|
|
47
|
+
const nodeDeps = new Set();
|
|
48
|
+
for (const pj of packageJsons.slice(0, 300)) {
|
|
49
|
+
try {
|
|
50
|
+
const j = JSON.parse(readText(root, pj));
|
|
51
|
+
for (const k of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) for (const n of Object.keys(j[k] ?? {})) nodeDeps.add(n);
|
|
52
|
+
} catch { /* unreadable package.json: no dependency signal */ }
|
|
53
|
+
}
|
|
54
|
+
const pyText = files.filter((f) => /^requirements.*\.txt$/.test(base(f)) || ['pyproject.toml', 'Pipfile', 'setup.py'].includes(base(f)))
|
|
55
|
+
.map((f) => readText(root, f).toLowerCase()).join('\n');
|
|
56
|
+
const pyHas = (name) => new RegExp(`(^|[^a-z0-9_.-])${name.replace(/[-.]/g, '[-_.]')}($|[^a-z0-9_.-])`, 'm').test(pyText);
|
|
57
|
+
const anyNode = (re) => [...nodeDeps].some((d) => re.test(d));
|
|
58
|
+
|
|
59
|
+
const agentConfigs = files.filter((f) => {
|
|
60
|
+
const b = base(f);
|
|
61
|
+
return ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md', 'SKILL.md', '.cursorrules', '.windsurfrules', '.mcp.json', 'copilot-instructions.md'].includes(b)
|
|
62
|
+
|| /^\.(cursor|claude|codex|gemini|windsurf|continue|aider)\//.test(f) || /(^|\/)\.cursor\/rules\//.test(f)
|
|
63
|
+
|| /(^|\/)\.vscode\/(tasks|mcp)\.json$/.test(f) || /(^|\/)\.devcontainer(\/|\.json$)/.test(f)
|
|
64
|
+
|| /(^|\/)\.(husky|githooks)\//.test(f);
|
|
65
|
+
});
|
|
66
|
+
const workflows = files.filter((f) => /(^|\/)\.github\/(workflows\/[^/]+\.ya?ml|actions\/.+\/action\.ya?ml)$/.test(f));
|
|
67
|
+
const docker = files.filter((f) => /^(Dockerfile(\..+)?|.+\.dockerfile|(docker-)?compose([.-].+)?\.ya?ml)$/i.test(base(f)));
|
|
68
|
+
const iac = files.filter((f) => f.endsWith('.tf') || base(f) === 'Chart.yaml' || /(^|\/)(k8s|kubernetes)\/.+\.ya?ml$/.test(f));
|
|
69
|
+
|
|
70
|
+
const detection = {
|
|
71
|
+
nestedRepos,
|
|
72
|
+
lockfiles,
|
|
73
|
+
packageManagers: [...new Set(lockfiles.map((l) => l.manager))].sort(),
|
|
74
|
+
manifests,
|
|
75
|
+
packageJsons,
|
|
76
|
+
nextjs: nodeDeps.has('next') || files.some((f) => /^next\.config\.(js|mjs|ts|cjs)$/.test(base(f))),
|
|
77
|
+
expo: nodeDeps.has('expo') || files.some((f) => /^app\.config\.(js|ts)$/.test(base(f))),
|
|
78
|
+
supabase: anyNode(/^@supabase\//) || nodeDeps.has('supabase') || files.some((f) => /(^|\/)supabase\/(config\.toml|migrations\/)/.test(f)),
|
|
79
|
+
stripe: nodeDeps.has('stripe') || anyNode(/^@stripe\//) || pyHas('stripe'),
|
|
80
|
+
aiSdk: anyNode({ test: (d) => AI_NODE.some((re) => re.test(d)) }) || AI_PY.some(pyHas),
|
|
81
|
+
python: files.some((f) => f.endsWith('.py')) || manifests.some((m) => /requirements|pyproject|Pipfile|setup\.py/.test(m)),
|
|
82
|
+
workflows,
|
|
83
|
+
docker,
|
|
84
|
+
iac,
|
|
85
|
+
agentConfigs,
|
|
86
|
+
};
|
|
87
|
+
detection.stacks = ['nextjs', 'expo', 'supabase', 'stripe', 'aiSdk', 'python'].filter((k) => detection[k])
|
|
88
|
+
.concat(workflows.length ? ['workflows'] : [], docker.length || iac.length ? ['containers-iac'] : [], agentConfigs.length ? ['agent-configs'] : []);
|
|
89
|
+
return detection;
|
|
90
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Error types. UsageError maps to exit code 3 (usage, configuration, authorization or lock error).
|
|
2
|
+
export class UsageError extends Error {}
|
|
3
|
+
export class LockError extends UsageError {}
|
|
4
|
+
export class FetchError extends Error {}
|
|
5
|
+
export class InstallError extends Error {}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Fetch wrapper: host allowlist, timeout, retries for GET, redirect checks, and an on-disk cache with age reporting.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { FetchError } from './errors.mjs';
|
|
5
|
+
import { sha256 } from './findings.mjs';
|
|
6
|
+
import { vsgHome } from './paths.mjs';
|
|
7
|
+
|
|
8
|
+
/** Documented endpoints (build spec, Interface table). Product origins and other hosts are passed in explicitly. */
|
|
9
|
+
export const DOCUMENTED_HOSTS = [
|
|
10
|
+
'api.osv.dev', 'osv-vulnerabilities.storage.googleapis.com',
|
|
11
|
+
'www.cisa.gov', 'api.first.org',
|
|
12
|
+
'registry.npmjs.org', 'api.npmjs.org', 'pypi.org', 'files.pythonhosted.org',
|
|
13
|
+
'api.github.com', 'github.com', 'objects.githubusercontent.com', 'release-assets.githubusercontent.com',
|
|
14
|
+
'github-releases.githubusercontent.com', 'raw.githubusercontent.com',
|
|
15
|
+
'api.supabase.com', 'ghcr.io', 'registry-1.docker.io',
|
|
16
|
+
];
|
|
17
|
+
const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
18
|
+
const isLocalHost = (h) => LOCAL_HOSTS.has(h) || h.endsWith('.localhost');
|
|
19
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
20
|
+
|
|
21
|
+
function checkAllowed(url, allowHosts, strictHosts = false) {
|
|
22
|
+
let u;
|
|
23
|
+
try { u = new URL(url); } catch { throw new FetchError(`Invalid URL "${url}".`); }
|
|
24
|
+
const host = u.hostname.toLowerCase();
|
|
25
|
+
// strictHosts: only the caller's hosts (a live session's one registered origin), never the documented endpoints
|
|
26
|
+
const allowed = new Set([...(strictHosts ? [] : DOCUMENTED_HOSTS), ...allowHosts.map((h) => h.toLowerCase())]);
|
|
27
|
+
// an allowHosts entry may be a bare host or host:port; a registered origin passes host:port so other ports stay refused
|
|
28
|
+
if (!allowed.has(host) && !allowed.has(u.host.toLowerCase())) throw new FetchError(`Host "${host}" is not an allowed endpoint. Register it as a target or use a documented endpoint.`);
|
|
29
|
+
if (u.protocol !== 'https:' && !(u.protocol === 'http:' && isLocalHost(host))) {
|
|
30
|
+
throw new FetchError(`Refusing non-HTTPS request to "${host}". Only local targets may use HTTP.`);
|
|
31
|
+
}
|
|
32
|
+
return u;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
36
|
+
|
|
37
|
+
/** Reads at most `cap` bytes of a response body, then cancels the stream. Returns { buf, truncated }. */
|
|
38
|
+
export async function readCapped(res, cap) {
|
|
39
|
+
if (!res.body) return { buf: Buffer.alloc(0), truncated: false };
|
|
40
|
+
const reader = res.body.getReader();
|
|
41
|
+
const chunks = [];
|
|
42
|
+
let n = 0;
|
|
43
|
+
let truncated = false;
|
|
44
|
+
for (;;) {
|
|
45
|
+
const { done, value } = await reader.read();
|
|
46
|
+
if (done) break;
|
|
47
|
+
chunks.push(value);
|
|
48
|
+
n += value.length;
|
|
49
|
+
if (n > cap) { truncated = true; await reader.cancel().catch(() => {}); break; }
|
|
50
|
+
}
|
|
51
|
+
const all = Buffer.concat(chunks);
|
|
52
|
+
return { buf: truncated ? all.subarray(0, cap) : all, truncated };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Credentials belong to the host they were meant for. Like fetch's own redirect handling, drop them when a redirect changes origin.
|
|
56
|
+
const CREDENTIAL_HEADERS = new Set(['authorization', 'cookie', 'proxy-authorization', 'apikey', 'x-api-key']);
|
|
57
|
+
const withoutCredentials = (headers) => Object.fromEntries(Object.entries(headers ?? {}).filter(([k]) => !CREDENTIAL_HEADERS.has(k.toLowerCase())));
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns { ok, status, headers, body: Buffer }. Never throws for HTTP error statuses; throws FetchError for
|
|
61
|
+
* disallowed hosts and for network failure after retries. Only GET is retried (write probes are never retried).
|
|
62
|
+
* strictHosts: only allowHosts are allowed (no documented endpoints), for the first request and every redirect.
|
|
63
|
+
* A redirect off them is not followed: the 3xx response is returned as it is.
|
|
64
|
+
*/
|
|
65
|
+
export async function vfetch(url, {
|
|
66
|
+
method = 'GET', headers = {}, body, timeoutMs = 30_000, retries = 2, retryDelayMs = 500, allowHosts = [], strictHosts = false, maxBytes = 1_000_000_000,
|
|
67
|
+
fetchImpl = globalThis.fetch,
|
|
68
|
+
} = {}) {
|
|
69
|
+
checkAllowed(url, allowHosts, strictHosts);
|
|
70
|
+
const attempts = method === 'GET' ? retries + 1 : 1;
|
|
71
|
+
let lastError;
|
|
72
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
73
|
+
if (attempt > 0) await sleep(retryDelayMs * 2 ** (attempt - 1));
|
|
74
|
+
let failure = null; // per attempt: an earlier attempt's failure must not mask this one
|
|
75
|
+
try {
|
|
76
|
+
let current = url;
|
|
77
|
+
let sent = headers;
|
|
78
|
+
let done = false;
|
|
79
|
+
for (let hop = 0; hop <= 5 && !done; hop++) {
|
|
80
|
+
checkAllowed(current, allowHosts, strictHosts); // every redirect target is checked too
|
|
81
|
+
const res = await fetchImpl(current, { method, headers: sent, body, redirect: 'manual', signal: AbortSignal.timeout(timeoutMs) });
|
|
82
|
+
const location = res.status >= 300 && res.status < 400 ? res.headers.get('location') : null;
|
|
83
|
+
if (location) {
|
|
84
|
+
const next = new URL(location, current);
|
|
85
|
+
let follow = true;
|
|
86
|
+
if (strictHosts) { try { checkAllowed(next.href, allowHosts, true); } catch { follow = false; } }
|
|
87
|
+
if (follow) {
|
|
88
|
+
if (next.origin !== new URL(current).origin) sent = withoutCredentials(sent);
|
|
89
|
+
current = next.href;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const { buf, truncated } = await readCapped(res, maxBytes);
|
|
94
|
+
if (truncated) throw new FetchError(`Response from ${new URL(current).hostname} exceeds ${maxBytes} bytes.`);
|
|
95
|
+
if ((res.status >= 500 || res.status === 429) && attempt < attempts - 1) { failure = new FetchError(`HTTP ${res.status}`); done = true; continue; }
|
|
96
|
+
return { ok: res.ok, status: res.status, headers: res.headers, body: buf };
|
|
97
|
+
}
|
|
98
|
+
if (!failure) throw new FetchError('Too many redirects.');
|
|
99
|
+
} catch (e) {
|
|
100
|
+
if (e instanceof FetchError && /not an allowed|non-HTTPS|exceeds|Invalid URL|Too many/.test(e.message)) throw e;
|
|
101
|
+
failure = e;
|
|
102
|
+
}
|
|
103
|
+
lastError = failure;
|
|
104
|
+
}
|
|
105
|
+
throw new FetchError(`Request to ${new URL(url).hostname} failed: ${lastError?.message ?? 'unknown error'}.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const cacheFile = (url) => path.join(vsgHome(), 'cache', `${sha256(url).slice(0, 32)}.json`);
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* GET with a disk cache. Fresh cache (age <= ttlMs) is used without a request. If the network fails, a cache no older
|
|
112
|
+
* than maxStaleMs is returned with degraded: true; otherwise this throws (the calling check becomes not_run).
|
|
113
|
+
* Returns { body, source: 'cache'|'network'|'stale-cache', ageMs, degraded }.
|
|
114
|
+
*/
|
|
115
|
+
export async function cachedGet(url, { ttlMs = DAY, maxStaleMs = 7 * DAY, now = Date.now, ...opts } = {}) {
|
|
116
|
+
const file = cacheFile(url);
|
|
117
|
+
let entry = null;
|
|
118
|
+
try { entry = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { /* no usable cache */ }
|
|
119
|
+
const age = entry ? now() - entry.fetchedAt : Infinity;
|
|
120
|
+
if (entry && age <= ttlMs) return { body: Buffer.from(entry.body, 'base64'), source: 'cache', ageMs: age, degraded: false };
|
|
121
|
+
let failure;
|
|
122
|
+
try {
|
|
123
|
+
const res = await vfetch(url, opts);
|
|
124
|
+
if (res.ok) {
|
|
125
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
126
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
127
|
+
fs.writeFileSync(tmp, JSON.stringify({ url, fetchedAt: now(), body: res.body.toString('base64') }), { mode: 0o600 });
|
|
128
|
+
fs.renameSync(tmp, file);
|
|
129
|
+
return { body: res.body, source: 'network', ageMs: 0, degraded: false };
|
|
130
|
+
}
|
|
131
|
+
failure = `HTTP ${res.status}`;
|
|
132
|
+
} catch (e) {
|
|
133
|
+
if (/not an allowed|non-HTTPS/.test(e.message)) throw e;
|
|
134
|
+
failure = e.message;
|
|
135
|
+
}
|
|
136
|
+
if (entry && age <= maxStaleMs) return { body: Buffer.from(entry.body, 'base64'), source: 'stale-cache', ageMs: age, degraded: true };
|
|
137
|
+
const why = entry ? `cached copy is ${Math.floor(age / DAY)} days old (limit ${Math.floor(maxStaleMs / DAY)})` : 'no cached copy';
|
|
138
|
+
throw new FetchError(`Feed ${new URL(url).hostname} is unreachable (${failure}) and ${why}.`);
|
|
139
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Findings model: schema, fingerprint, dedupe and redaction (REQ-004).
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import { owaspForCwes } from './sarif.mjs';
|
|
4
|
+
|
|
5
|
+
export const SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'];
|
|
6
|
+
export const CONFIDENCES = ['confirmed', 'high', 'medium', 'low'];
|
|
7
|
+
export const CATEGORIES = [
|
|
8
|
+
'secret', 'secret-history', 'malicious-package', 'dependency-vuln', 'dependency-change', 'code',
|
|
9
|
+
'supabase', 'workflow', 'agent-surface', 'malware', 'replay', 'live', 'release', 'agent-finding',
|
|
10
|
+
'iac', 'posture', 'license', 'config', 'other',
|
|
11
|
+
];
|
|
12
|
+
export const sha256 = (data) => crypto.createHash('sha256').update(data).digest('hex');
|
|
13
|
+
/** Higher number is worse. */
|
|
14
|
+
export const severityRank = (s) => SEVERITIES.length - SEVERITIES.indexOf(s);
|
|
15
|
+
const confidenceRank = (c) => CONFIDENCES.length - CONFIDENCES.indexOf(c);
|
|
16
|
+
|
|
17
|
+
/** Redaction (spec): first 4 characters, the length, and an 8-character SHA-256 prefix. Values of 8 or fewer characters show no prefix. */
|
|
18
|
+
export function redact(value) {
|
|
19
|
+
const s = String(value);
|
|
20
|
+
const prefix = s.length > 8 ? s.slice(0, 4) : '';
|
|
21
|
+
return `${prefix}[redacted len=${s.length} sha256=${sha256(s).slice(0, 8)}]`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const SECRET_PATTERNS = [
|
|
25
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
26
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
27
|
+
/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}\b/g,
|
|
28
|
+
/\bgithub_pat_[A-Za-z0-9_]{30,}\b/g,
|
|
29
|
+
/\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g,
|
|
30
|
+
/\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{20,}\b/g,
|
|
31
|
+
/\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g,
|
|
32
|
+
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,
|
|
33
|
+
];
|
|
34
|
+
// header and assignment forms: the value is the secret, the name stays readable
|
|
35
|
+
const VALUE_PATTERNS = [
|
|
36
|
+
/\b(Bearer|Basic)\s+([A-Za-z0-9._~+\/=-]{12,})/gi,
|
|
37
|
+
/\b(Set-Cookie|Cookie|Authorization|X-Api-Key)(\s*[:=]\s*)([^\r\n]{8,})/gi,
|
|
38
|
+
/\b([A-Za-z0-9_.-]*(?:password|passwd|secret|token|api[_-]?key|private[_-]?key)[A-Za-z0-9_.-]*)(\s*[:=]\s*["']?)([^\s"',;}]{8,})/gi,
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/** Removes secrets from free text: known values first, then well-known token shapes and secret-named assignments. */
|
|
42
|
+
export function scrubText(text, secrets = []) {
|
|
43
|
+
let out = String(text ?? '');
|
|
44
|
+
for (const s of secrets) if (s && String(s).length >= 8) out = out.split(String(s)).join(redact(s));
|
|
45
|
+
for (const re of SECRET_PATTERNS) out = out.replace(re, (m) => redact(m));
|
|
46
|
+
out = out.replace(VALUE_PATTERNS[0], (_m, scheme, v) => `${scheme} ${redact(v)}`);
|
|
47
|
+
out = out.replace(VALUE_PATTERNS[1], (_m, name, sep, v) => (v.includes('[redacted') ? _m : `${name}${sep}${redact(v)}`));
|
|
48
|
+
out = out.replace(VALUE_PATTERNS[2], (_m, name, sep, v) => (v.includes('[redacted') ? _m : `${name}${sep}${redact(v)}`));
|
|
49
|
+
return out.replace(URL_PATTERN, (u) => scrubUrl(u));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Query and fragment values whose names look like credentials, and userinfo, never reach a finding or its fingerprint.
|
|
53
|
+
const SECRET_PARAM = /token|key|secret|sig|auth|password|passwd|code|session|credential|jwt/i;
|
|
54
|
+
const URL_PATTERN = /\b[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/gi;
|
|
55
|
+
const redactParams = (part) => part.split('&').map((kv) => {
|
|
56
|
+
const i = kv.indexOf('=');
|
|
57
|
+
if (i < 0) return kv;
|
|
58
|
+
let name = kv.slice(0, i);
|
|
59
|
+
try { name = decodeURIComponent(name); } catch { /* keep the raw name */ }
|
|
60
|
+
return SECRET_PARAM.test(name) ? `${kv.slice(0, i)}=REDACTED` : kv;
|
|
61
|
+
}).join('&');
|
|
62
|
+
|
|
63
|
+
/** Makes a URL safe to store: drops userinfo, redacts secret-looking query and fragment values, then scrubs token shapes. The rest is left byte for byte. */
|
|
64
|
+
export function scrubUrl(url) {
|
|
65
|
+
let s = String(url).replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/?#]*@/i, '$1');
|
|
66
|
+
const hashAt = s.indexOf('#');
|
|
67
|
+
let hash = hashAt < 0 ? '' : s.slice(hashAt + 1);
|
|
68
|
+
if (hashAt >= 0) s = s.slice(0, hashAt);
|
|
69
|
+
const queryAt = s.indexOf('?');
|
|
70
|
+
const query = queryAt < 0 ? null : s.slice(queryAt + 1);
|
|
71
|
+
if (queryAt >= 0) s = s.slice(0, queryAt);
|
|
72
|
+
if (query !== null) s += `?${redactParams(query)}`;
|
|
73
|
+
if (hashAt >= 0) hash = hash.includes('=') ? redactParams(hash) : hash;
|
|
74
|
+
// token shapes only: scrubText's assignment patterns would swallow the rest of a query string
|
|
75
|
+
let out = hashAt >= 0 ? `${s}#${hash}` : s;
|
|
76
|
+
for (const re of SECRET_PATTERNS) out = out.replace(re, (m) => redact(m));
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const posix = (p) => String(p).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
81
|
+
|
|
82
|
+
/** Builds a schema-shaped finding from an adapter's raw finding. Throws on an invalid one. */
|
|
83
|
+
export function makeFinding(raw, checkId) {
|
|
84
|
+
const loc = raw.location ?? {};
|
|
85
|
+
const location = {};
|
|
86
|
+
if (loc.file !== undefined) location.file = posix(loc.file);
|
|
87
|
+
for (const k of ['line', 'package', 'version', 'ecosystem']) if (loc[k] !== undefined) location[k] = loc[k];
|
|
88
|
+
if (loc.url !== undefined) location.url = scrubUrl(loc.url);
|
|
89
|
+
const title = typeof raw.title === 'string' ? scrubText(raw.title) : raw.title;
|
|
90
|
+
const key = raw.key ?? raw.ruleId ?? title; // fingerprints come from the scrubbed values, so a rotated secret does not change them
|
|
91
|
+
const fingerprint = sha256([raw.category, key, location.file ?? '', location.package ?? '', location.version ?? '',
|
|
92
|
+
location.ecosystem ?? '', location.url ?? ''].join('\u0000'));
|
|
93
|
+
const cwes = [].concat(raw.cwe ?? []);
|
|
94
|
+
const owasp = [...new Set([...(raw.owasp ?? []), ...owaspForCwes(cwes)])];
|
|
95
|
+
const finding = {
|
|
96
|
+
id: `VSG-${fingerprint.slice(0, 8)}`,
|
|
97
|
+
fingerprint,
|
|
98
|
+
check: checkId,
|
|
99
|
+
sources: raw.sources ?? [{ tool: raw.tool ?? checkId, ruleId: raw.ruleId ?? null }],
|
|
100
|
+
category: raw.category,
|
|
101
|
+
severity: raw.severity,
|
|
102
|
+
confidence: raw.confidence ?? 'medium',
|
|
103
|
+
title,
|
|
104
|
+
description: scrubText(raw.description ?? ''),
|
|
105
|
+
location,
|
|
106
|
+
evidence: scrubText(raw.evidence ?? ''),
|
|
107
|
+
refs: raw.refs ?? [],
|
|
108
|
+
fix: scrubText(raw.fix ?? ''),
|
|
109
|
+
exploited: { kev: raw.exploited?.kev ?? false, epss: raw.exploited?.epss ?? null },
|
|
110
|
+
owasp,
|
|
111
|
+
blocking: false,
|
|
112
|
+
blockReason: '',
|
|
113
|
+
status: 'open',
|
|
114
|
+
isNew: null,
|
|
115
|
+
};
|
|
116
|
+
const errors = validateFinding(finding);
|
|
117
|
+
if (errors.length) throw new Error(`invalid finding "${raw.title ?? '?'}": ${errors.join('; ')}`);
|
|
118
|
+
return finding;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function validateFinding(f) {
|
|
122
|
+
const errors = [];
|
|
123
|
+
const need = (c, m) => { if (!c) errors.push(m); };
|
|
124
|
+
need(/^VSG-[0-9a-f]{8}$/.test(f?.id ?? ''), 'id');
|
|
125
|
+
need(/^[0-9a-f]{64}$/.test(f?.fingerprint ?? ''), 'fingerprint');
|
|
126
|
+
need(typeof f?.check === 'string' && f.check, 'check');
|
|
127
|
+
need(Array.isArray(f?.sources) && f.sources.length > 0, 'sources');
|
|
128
|
+
need(CATEGORIES.includes(f?.category), `category "${f?.category}"`);
|
|
129
|
+
need(SEVERITIES.includes(f?.severity), `severity "${f?.severity}"`);
|
|
130
|
+
need(CONFIDENCES.includes(f?.confidence), `confidence "${f?.confidence}"`);
|
|
131
|
+
need(typeof f?.title === 'string' && f.title, 'title');
|
|
132
|
+
need(typeof f?.description === 'string', 'description');
|
|
133
|
+
need(f?.location && typeof f.location === 'object', 'location');
|
|
134
|
+
need(typeof f?.evidence === 'string', 'evidence');
|
|
135
|
+
need(Array.isArray(f?.refs), 'refs');
|
|
136
|
+
need(typeof f?.fix === 'string', 'fix');
|
|
137
|
+
need(typeof f?.exploited?.kev === 'boolean', 'exploited.kev');
|
|
138
|
+
need(Array.isArray(f?.owasp), 'owasp');
|
|
139
|
+
need(typeof f?.blocking === 'boolean', 'blocking');
|
|
140
|
+
need(['open', 'accepted', 'expired-acceptance'].includes(f?.status), 'status');
|
|
141
|
+
return errors;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** One issue reported by several tools becomes one finding with every source. Deterministic regardless of input order. */
|
|
145
|
+
export function dedupe(findings) {
|
|
146
|
+
const groups = new Map();
|
|
147
|
+
for (const f of findings) {
|
|
148
|
+
if (!groups.has(f.fingerprint)) groups.set(f.fingerprint, []);
|
|
149
|
+
groups.get(f.fingerprint).push(f);
|
|
150
|
+
}
|
|
151
|
+
const out = [];
|
|
152
|
+
for (const group of groups.values()) {
|
|
153
|
+
group.sort((a, b) => severityRank(b.severity) - severityRank(a.severity) || a.check.localeCompare(b.check));
|
|
154
|
+
const merged = structuredClone(group[0]);
|
|
155
|
+
const seen = new Set();
|
|
156
|
+
merged.sources = [];
|
|
157
|
+
for (const g of group) {
|
|
158
|
+
for (const s of g.sources) {
|
|
159
|
+
const k = `${s.tool}\u0000${s.ruleId}`;
|
|
160
|
+
if (!seen.has(k)) { seen.add(k); merged.sources.push(s); }
|
|
161
|
+
}
|
|
162
|
+
if (confidenceRank(g.confidence) > confidenceRank(merged.confidence)) merged.confidence = g.confidence;
|
|
163
|
+
merged.refs = [...new Set([...merged.refs, ...g.refs])];
|
|
164
|
+
merged.owasp = [...new Set([...merged.owasp, ...g.owasp])];
|
|
165
|
+
merged.exploited.kev ||= g.exploited.kev;
|
|
166
|
+
if (g.exploited.epss != null && (merged.exploited.epss == null || g.exploited.epss > merged.exploited.epss)) merged.exploited.epss = g.exploited.epss;
|
|
167
|
+
if (!merged.fix && g.fix) merged.fix = g.fix;
|
|
168
|
+
}
|
|
169
|
+
merged.sources.sort((a, b) => `${a.tool}${a.ruleId}`.localeCompare(`${b.tool}${b.ruleId}`));
|
|
170
|
+
out.push(merged);
|
|
171
|
+
}
|
|
172
|
+
return sortFindings(out);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function sortFindings(findings) {
|
|
176
|
+
return [...findings].sort((a, b) => severityRank(b.severity) - severityRank(a.severity) || a.fingerprint.localeCompare(b.fingerprint));
|
|
177
|
+
}
|