@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,123 @@
|
|
|
1
|
+
// install-posture (REQ-011): is the product's install path protected? Reports a dependency firewall in CI workflows, pnpm build-script
|
|
2
|
+
// restrictions and npm ignore-scripts. Warning only: category "posture" never blocks, and the check is not required in any mode.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { listFiles } from '../../core/detect.mjs';
|
|
6
|
+
import { parsePnpmLock } from '../_lib/lockfiles.mjs';
|
|
7
|
+
|
|
8
|
+
export const id = 'install-posture';
|
|
9
|
+
export const title = 'Install-time protection posture (dependency firewall, pnpm builds, npm ignore-scripts)';
|
|
10
|
+
export const modes = ['scan', 'deep', 'release'];
|
|
11
|
+
export const requiredIn = [];
|
|
12
|
+
export const timeoutSec = 120;
|
|
13
|
+
|
|
14
|
+
const base = (f) => f.slice(f.lastIndexOf('/') + 1);
|
|
15
|
+
const INSTALL = /\b(?:npm\s+(?:ci|install|i)\b|pnpm\s+(?:install|i|add)\b|yarn(?:\s+(?:install|add))?\s*(?:$|--)|yarn\s+(?:install|add)\b|bun\s+(?:install|i)\b|pip3?\s+install\b|python3?\s+-m\s+pip\s+install\b|uv\s+(?:sync|pip\s+install)\b|poetry\s+install\b)/;
|
|
16
|
+
// Aikido Safe Chain (safe-chain, @aikidosec/safe-chain, aikido setup actions) and Socket Firewall (sfw).
|
|
17
|
+
const FIREWALL = [/safe-chain/i, /aikido/i, /(^|[\s"'`/@])sfw(?=$|[\s"'`])/m, /socket[-\s]?firewall/i];
|
|
18
|
+
const stripComments = (text) => text.split(/\r?\n/).map((l) => (/^\s*#/.test(l) ? '' : l.replace(/\s+#.*$/, ''))).join('\n');
|
|
19
|
+
const read = (repo, rel) => { try { return fs.readFileSync(path.join(repo, rel), 'utf8'); } catch { return null; } };
|
|
20
|
+
|
|
21
|
+
export function applies(ctx) {
|
|
22
|
+
const d = ctx.detection;
|
|
23
|
+
return d.workflows.length || d.packageJsons.length || d.lockfiles.length
|
|
24
|
+
? true
|
|
25
|
+
: { applies: false, reason: 'no CI workflows or package manager files were found' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const posture = (o) => ({ category: 'posture', tool: 'vsg', confidence: 'high', refs: [], ...o });
|
|
29
|
+
|
|
30
|
+
export async function run(ctx) {
|
|
31
|
+
const repo = ctx.repo;
|
|
32
|
+
const d = ctx.detection;
|
|
33
|
+
const files = listFiles(repo);
|
|
34
|
+
const findings = [];
|
|
35
|
+
const evidence = { workflows: [], pnpm: [], npm: [] };
|
|
36
|
+
|
|
37
|
+
// ---- CI workflows ----
|
|
38
|
+
const texts = new Map(d.workflows.map((f) => [f, stripComments(read(repo, f) ?? '')]));
|
|
39
|
+
const isAction = (f) => /\/actions\//.test(f);
|
|
40
|
+
const actionProtected = [...texts].some(([f, t]) => isAction(f) && FIREWALL.some((re) => re.test(t)));
|
|
41
|
+
for (const [file, text] of texts) {
|
|
42
|
+
if (isAction(file) || !text.split(/\r?\n/).some((l) => INSTALL.test(l))) continue;
|
|
43
|
+
const protectedHere = FIREWALL.some((re) => re.test(text)) || actionProtected;
|
|
44
|
+
evidence.workflows.push({ file, installSteps: true, firewall: protectedHere });
|
|
45
|
+
findings.push(posture(protectedHere
|
|
46
|
+
? { severity: 'info', key: 'firewall-present', ruleId: 'install-posture/firewall-present', location: { file }, title: `CI installs run through a dependency firewall: ${file}`, description: 'This workflow installs dependencies and references Aikido Safe Chain or Socket Firewall.', evidence: file, fix: '' }
|
|
47
|
+
: {
|
|
48
|
+
severity: 'medium', key: 'firewall-absent', ruleId: 'install-posture/firewall-absent', location: { file },
|
|
49
|
+
title: `CI installs run without a dependency firewall: ${file}`,
|
|
50
|
+
description: 'This workflow installs dependencies with no Aikido Safe Chain (safe-chain, aikido setup action) or Socket Firewall (sfw) in front of the package manager, so a freshly published malicious package is installed without a check.',
|
|
51
|
+
evidence: `${file} runs a package install and mentions no dependency firewall.`,
|
|
52
|
+
fix: 'Install Aikido Safe Chain in the workflow before the install step (Richard approves any CI change), or prefix installs with sfw. See the Safe Chain documentation for the setup step.',
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
if (!evidence.workflows.length) evidence.workflows.push({ note: d.workflows.length ? 'workflows contain no package install steps' : 'no CI workflows found' });
|
|
56
|
+
|
|
57
|
+
// ---- pnpm ----
|
|
58
|
+
const pnpmLocks = d.lockfiles.filter((l) => l.manager === 'pnpm');
|
|
59
|
+
const workspaceFiles = files.filter((f) => base(f) === 'pnpm-workspace.yaml');
|
|
60
|
+
const npmrcFiles = files.filter((f) => base(f) === '.npmrc');
|
|
61
|
+
const pkgJsons = d.packageJsons.map((f) => ({ file: f, json: (() => { try { return JSON.parse(read(repo, f) ?? ''); } catch { return null; } })() }));
|
|
62
|
+
const dangerous = [];
|
|
63
|
+
let hasAllowList = false;
|
|
64
|
+
for (const f of workspaceFiles) {
|
|
65
|
+
const t = stripComments(read(repo, f) ?? '');
|
|
66
|
+
if (/^\s*dangerouslyAllowAllBuilds\s*:\s*true\b/m.test(t)) dangerous.push(f);
|
|
67
|
+
if (/^\s*(onlyBuiltDependencies|neverBuiltDependencies)\s*:/m.test(t)) hasAllowList = true;
|
|
68
|
+
}
|
|
69
|
+
for (const { file, json } of pkgJsons) {
|
|
70
|
+
if (json?.pnpm?.dangerouslyAllowAllBuilds === true) dangerous.push(file);
|
|
71
|
+
if (json?.pnpm?.onlyBuiltDependencies || json?.pnpm?.neverBuiltDependencies) hasAllowList = true;
|
|
72
|
+
}
|
|
73
|
+
for (const f of npmrcFiles) {
|
|
74
|
+
const t = stripComments(read(repo, f) ?? '');
|
|
75
|
+
if (/^\s*dangerously-allow-all-builds\s*=\s*true\b/m.test(t)) dangerous.push(f);
|
|
76
|
+
if (/^\s*(only-built-dependencies|never-built-dependencies)/m.test(t)) hasAllowList = true;
|
|
77
|
+
}
|
|
78
|
+
for (const file of [...new Set(dangerous)]) {
|
|
79
|
+
findings.push(posture({
|
|
80
|
+
severity: 'high', key: 'pnpm-allow-all-builds', ruleId: 'install-posture/pnpm-allow-all-builds', location: { file },
|
|
81
|
+
title: `pnpm runs every dependency's build script: ${file}`,
|
|
82
|
+
description: 'dangerouslyAllowAllBuilds is true, so any dependency (including a newly compromised one) can run install-time scripts on developer machines and CI. This is a warning and does not block the release.',
|
|
83
|
+
evidence: `${file} sets dangerouslyAllowAllBuilds: true`,
|
|
84
|
+
fix: 'Remove dangerouslyAllowAllBuilds and list only the packages that need build scripts under onlyBuiltDependencies.',
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
for (const l of pnpmLocks) {
|
|
88
|
+
const text = read(repo, l.file);
|
|
89
|
+
let withScripts = 0;
|
|
90
|
+
try { withScripts = parsePnpmLock(text ?? '').filter((p) => p.installScript).length; } catch { /* unreadable lockfile: no install-script count */ }
|
|
91
|
+
evidence.pnpm.push({ lockfile: l.file, packagesWithBuildScripts: withScripts, allowListConfigured: hasAllowList, dangerouslyAllowAllBuilds: dangerous.length > 0 });
|
|
92
|
+
if (withScripts > 0 && !hasAllowList && !dangerous.length) {
|
|
93
|
+
findings.push(posture({
|
|
94
|
+
severity: 'info', key: 'pnpm-no-only-built', ruleId: 'install-posture/pnpm-no-only-built', location: { file: l.file },
|
|
95
|
+
title: `pnpm has no onlyBuiltDependencies list although ${withScripts} dependencies have build scripts`,
|
|
96
|
+
description: 'Without an explicit allowlist, which dependency build scripts may run depends on the pnpm version in use.',
|
|
97
|
+
evidence: `${l.file}: ${withScripts} packages require a build step; no onlyBuiltDependencies setting was found.`,
|
|
98
|
+
fix: 'Add onlyBuiltDependencies to pnpm-workspace.yaml (or the pnpm field of package.json) listing only the packages that need to build.',
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---- npm ----
|
|
104
|
+
const npmLocks = d.lockfiles.filter((l) => l.manager === 'npm');
|
|
105
|
+
if (npmLocks.length) {
|
|
106
|
+
const rcWith = npmrcFiles.filter((f) => /^\s*ignore-scripts\s*=\s*true\b/m.test(stripComments(read(repo, f) ?? '')));
|
|
107
|
+
evidence.npm.push({ lockfiles: npmLocks.map((l) => l.file), ignoreScripts: rcWith });
|
|
108
|
+
findings.push(posture(rcWith.length
|
|
109
|
+
? { severity: 'info', key: 'npm-ignore-scripts-set', ruleId: 'install-posture/npm-ignore-scripts-set', location: { file: rcWith[0] }, title: 'npm ignores dependency install scripts (.npmrc ignore-scripts=true)', description: 'Dependency lifecycle scripts do not run during npm install.', evidence: `${rcWith[0]} sets ignore-scripts=true`, fix: '' }
|
|
110
|
+
: {
|
|
111
|
+
severity: 'low', key: 'npm-ignore-scripts-unset', ruleId: 'install-posture/npm-ignore-scripts-unset', location: { file: npmLocks[0].file },
|
|
112
|
+
title: '.npmrc does not set ignore-scripts=true',
|
|
113
|
+
description: 'npm runs install-time scripts of every dependency. Malicious packages commonly use them to steal credentials.',
|
|
114
|
+
evidence: `No .npmrc with ignore-scripts=true was found next to ${npmLocks.map((l) => l.file).join(', ')}.`,
|
|
115
|
+
fix: 'Set ignore-scripts=true in .npmrc and run the few build scripts you need explicitly (for example npm rebuild <package>).',
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
if (!evidence.pnpm.length) delete evidence.pnpm;
|
|
119
|
+
if (!evidence.npm.length) delete evidence.npm;
|
|
120
|
+
return { status: 'ran', reason: '', findings, evidence: { 'posture.json': JSON.stringify(evidence, null, 2) } };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default { id, title, modes, requiredIn, timeoutSec, applies, run };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// licenses (REQ-020): per-package licenses from OSV-Scanner (--licenses, JSON) checked against a deny list.
|
|
2
|
+
// Default deny: AGPL-*, SSPL-*, and GPL-* when the product is distributed as a desktop or mobile app. vsg.config.json "licenseAllow"
|
|
3
|
+
// lists SPDX ids the owner has accepted. Warning only: category "license" never blocks.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { listFiles } from '../../core/detect.mjs';
|
|
7
|
+
import { osvExcludeArgs } from '../_lib/osv.mjs';
|
|
8
|
+
|
|
9
|
+
export const id = 'licenses';
|
|
10
|
+
export const title = 'Dependency licenses (AGPL, SSPL and distributed-app GPL)';
|
|
11
|
+
export const modes = ['deep', 'release'];
|
|
12
|
+
export const requiredIn = ['deep', 'release'];
|
|
13
|
+
export const timeoutSec = 600;
|
|
14
|
+
|
|
15
|
+
const base = (f) => f.slice(f.lastIndexOf('/') + 1);
|
|
16
|
+
const PY_PACKAGERS = /(^|[^a-z0-9_.-])(pyinstaller|cx[-_.]?freeze|nuitka|py2exe|briefcase)($|[^a-z0-9_.-])/i;
|
|
17
|
+
|
|
18
|
+
export function applies(ctx) {
|
|
19
|
+
const d = ctx.detection;
|
|
20
|
+
return d.lockfiles.length > 0 || (d.manifests ?? []).some((m) => /^requirements.*\.txt$/.test(base(m)))
|
|
21
|
+
? true
|
|
22
|
+
: { applies: false, reason: 'no lockfile or requirements file was found' };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 'mobile' (Expo), 'desktop' (Python with a Windows packaging marker), or null. */
|
|
26
|
+
export function distributionKind(ctx) {
|
|
27
|
+
if (ctx.detection.expo) return 'mobile';
|
|
28
|
+
if (!ctx.detection.python) return null;
|
|
29
|
+
const files = listFiles(ctx.repo);
|
|
30
|
+
if (files.some((f) => f.toLowerCase().endsWith('.spec'))) return 'desktop';
|
|
31
|
+
for (const f of files.filter((x) => /^requirements.*\.txt$/.test(base(x)) || ['pyproject.toml', 'Pipfile', 'setup.py'].includes(base(x)))) {
|
|
32
|
+
try { if (PY_PACKAGERS.test(fs.readFileSync(path.join(ctx.repo, f), 'utf8'))) return 'desktop'; } catch { /* unreadable file: no marker */ }
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---- SPDX expression handling: OR means the user may pick, AND means every part applies ----
|
|
38
|
+
function tokens(expr) { return expr.match(/\(|\)|[^\s()]+/g) ?? []; }
|
|
39
|
+
function parseExpr(tk) {
|
|
40
|
+
let pos = 0;
|
|
41
|
+
const peek = () => tk[pos];
|
|
42
|
+
const orExpr = () => {
|
|
43
|
+
const parts = [andExpr()];
|
|
44
|
+
while (peek()?.toUpperCase() === 'OR') { pos++; parts.push(andExpr()); }
|
|
45
|
+
return parts.length === 1 ? parts[0] : { or: parts };
|
|
46
|
+
};
|
|
47
|
+
const andExpr = () => {
|
|
48
|
+
const parts = [atom()];
|
|
49
|
+
while (peek()?.toUpperCase() === 'AND') { pos++; parts.push(atom()); }
|
|
50
|
+
return parts.length === 1 ? parts[0] : { and: parts };
|
|
51
|
+
};
|
|
52
|
+
const atom = () => {
|
|
53
|
+
const t = tk[pos++];
|
|
54
|
+
if (t === '(') { const e = orExpr(); if (peek() === ')') pos++; return e; }
|
|
55
|
+
if (peek()?.toUpperCase() === 'WITH') pos += 2; // exceptions are ignored: the base license decides
|
|
56
|
+
return { id: String(t ?? '') };
|
|
57
|
+
};
|
|
58
|
+
return orExpr();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Returns the denied license ids in an expression, or [] when the expression is acceptable. */
|
|
62
|
+
export function deniedIn(expression, { denyGpl, allow = new Set() }) {
|
|
63
|
+
const denied = (id) => {
|
|
64
|
+
const u = id.toUpperCase().replace(/\+$/, '');
|
|
65
|
+
if (allow.has(u)) return false;
|
|
66
|
+
return /^AGPL-/.test(u) || /^SSPL-/.test(u) || (denyGpl && /^GPL-/.test(u));
|
|
67
|
+
};
|
|
68
|
+
const walk = (n) => {
|
|
69
|
+
if (n.id !== undefined) return denied(n.id) ? [n.id] : [];
|
|
70
|
+
if (n.and) return n.and.flatMap(walk);
|
|
71
|
+
const per = n.or.map(walk);
|
|
72
|
+
return per.every((p) => p.length) ? per.flat() : [];
|
|
73
|
+
};
|
|
74
|
+
let tree;
|
|
75
|
+
try { tree = parseExpr(tokens(String(expression))); } catch { return []; }
|
|
76
|
+
return walk(tree);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const relFile = (repo, p) => path.relative(path.resolve(repo), path.resolve(p)).split(path.sep).join('/');
|
|
80
|
+
const UNKNOWN = (l) => !l || /^(unknown|unlicensed|none|noassertion)$/i.test(l);
|
|
81
|
+
|
|
82
|
+
export async function run(ctx) {
|
|
83
|
+
const res = await ctx.runTool('osv-scanner', [
|
|
84
|
+
'scan', 'source', '-r', '--format', 'json', '--all-packages', '--licenses', '--no-resolve', '--allow-no-lockfiles', '--no-ignore',
|
|
85
|
+
...osvExcludeArgs(ctx),
|
|
86
|
+
'.',
|
|
87
|
+
]);
|
|
88
|
+
if (res.status !== 'ran') return { status: 'not_run', reason: res.reason };
|
|
89
|
+
if (res.truncated) return { status: 'not_run', reason: 'OSV-Scanner output was too large to read completely.' };
|
|
90
|
+
if (res.exitCode !== 0 && res.exitCode !== 1) {
|
|
91
|
+
const tail = res.stderr.trim().split(/\r?\n/).slice(-3).join(' | ');
|
|
92
|
+
return { status: 'not_run', reason: `OSV-Scanner failed with exit code ${res.exitCode}${tail ? `: ${tail}` : ''}. Check network access (license data comes from deps.dev).` };
|
|
93
|
+
}
|
|
94
|
+
let osv;
|
|
95
|
+
try { osv = JSON.parse(res.stdout.replace(/^/, '')); } catch { return { status: 'not_run', reason: 'OSV-Scanner did not return valid JSON.' }; }
|
|
96
|
+
|
|
97
|
+
const kind = distributionKind(ctx);
|
|
98
|
+
const denyGpl = kind !== null;
|
|
99
|
+
const allow = new Set((ctx.config?.licenseAllow ?? []).map((l) => String(l).toUpperCase()));
|
|
100
|
+
const findings = [];
|
|
101
|
+
const counts = new Map();
|
|
102
|
+
const unknown = [];
|
|
103
|
+
let packages = 0;
|
|
104
|
+
for (const r of osv.results ?? []) {
|
|
105
|
+
const file = relFile(ctx.repo, r.source?.path ?? '');
|
|
106
|
+
for (const p of r.packages ?? []) {
|
|
107
|
+
packages++;
|
|
108
|
+
const lic = (p.licenses ?? []).map(String);
|
|
109
|
+
for (const l of lic) counts.set(l, (counts.get(l) ?? 0) + 1);
|
|
110
|
+
if (!lic.length || lic.every(UNKNOWN)) { unknown.push(`${p.package.name}@${p.package.version}`); continue; }
|
|
111
|
+
const expression = lic.join(' AND ');
|
|
112
|
+
const hits = [...new Set(deniedIn(expression, { denyGpl, allow }))];
|
|
113
|
+
if (!hits.length) continue;
|
|
114
|
+
const why = hits.some((h) => /^GPL-/i.test(h)) && !hits.some((h) => /^(AGPL|SSPL)-/i.test(h))
|
|
115
|
+
? `GPL licenses are denied by default for ${kind === 'mobile' ? 'mobile (Expo)' : 'distributed desktop (Python plus Windows packaging)'} apps because shipping the app distributes the dependency`
|
|
116
|
+
: `${hits.join(', ')} is a network-copyleft license (AGPL and SSPL are denied by default)`;
|
|
117
|
+
findings.push({
|
|
118
|
+
category: 'license', severity: 'medium', confidence: 'high', key: `license:${hits.sort().join('+')}`, tool: 'osv-scanner', ruleId: `license/${hits[0]}`,
|
|
119
|
+
location: { file, package: p.package.name, version: p.package.version, ecosystem: p.package.ecosystem },
|
|
120
|
+
title: `${hits.join(', ')} license: ${p.package.name}@${p.package.version}`,
|
|
121
|
+
description: `${p.package.name}@${p.package.version} is licensed ${lic.join(', ')}. ${why}. This is a warning and does not block the release.`,
|
|
122
|
+
evidence: `${p.package.name}@${p.package.version} (${p.package.ecosystem}) in ${file}: ${lic.join(', ')}`,
|
|
123
|
+
refs: [], fix: `Replace the dependency, get a commercial license, or have Richard confirm the use is acceptable and add ${hits.join(', ')} to "licenseAllow" in vsg.config.json.`,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (unknown.length) {
|
|
128
|
+
findings.push({
|
|
129
|
+
category: 'license', severity: 'info', confidence: 'medium', key: 'license-unknown', tool: 'osv-scanner', ruleId: 'license/unknown', location: {},
|
|
130
|
+
title: `${unknown.length} dependencies have no license data`,
|
|
131
|
+
description: 'OSV-Scanner could not determine a license for these packages, so they could not be checked against the deny list.',
|
|
132
|
+
evidence: unknown.slice(0, 20).join(', ') + (unknown.length > 20 ? `, and ${unknown.length - 20} more` : ''), refs: [], fix: 'Check these packages by hand if the product is distributed.',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const summary = [...counts].sort((a, b) => b[1] - a[1]).map(([name, count]) => ({ name, count }));
|
|
136
|
+
return {
|
|
137
|
+
status: 'ran', reason: '', findings, toolVersion: ctx.registry?.get('osv-scanner')?.version ?? null,
|
|
138
|
+
dataVersions: { licenses: 'deps.dev via OSV-Scanner' },
|
|
139
|
+
evidence: { 'licenses.json': JSON.stringify({ distribution: kind, denyGpl, allow: [...allow], packages, summary, unknown }, null, 2) },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default { id, title, modes, requiredIn, timeoutSec, applies, run };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// live-basic (REQ-023): passive observations of a registered target. Security headers, cookie flags, CORS with an untrusted
|
|
2
|
+
// Origin, a fixed list of sensitive files (with a random-path baseline) and source maps. Ordinary GETs only.
|
|
3
|
+
import { analyzeCookies, analyzeCors, analyzeHeaders, UNTRUSTED_ORIGIN } from '../../live/analyze.mjs';
|
|
4
|
+
import { probeSensitiveFiles } from '../../live/exposure.mjs';
|
|
5
|
+
|
|
6
|
+
export const id = 'live-basic';
|
|
7
|
+
export const title = 'Live site: headers, cookies, CORS, exposed files, source maps';
|
|
8
|
+
export const modes = ['url', 'release'];
|
|
9
|
+
export const requiredIn = ['url', 'release'];
|
|
10
|
+
export const timeoutSec = 900;
|
|
11
|
+
|
|
12
|
+
const MAX_MAPS = 20;
|
|
13
|
+
const MAP_REF = /\/\/[#@]\s*sourceMappingURL=([^\s'"]+)/;
|
|
14
|
+
const live = (f) => ({ category: 'live', confidence: 'high', tool: 'vsg-live', refs: [], ...f });
|
|
15
|
+
|
|
16
|
+
// always applies: without a target the run reports not_run, so a required check can never be silently skipped
|
|
17
|
+
export const applies = () => true;
|
|
18
|
+
|
|
19
|
+
async function sourceMaps(session) {
|
|
20
|
+
const { items } = await session.scripts();
|
|
21
|
+
const findings = [];
|
|
22
|
+
let probed = 0;
|
|
23
|
+
for (const s of items) {
|
|
24
|
+
const tail = s.body.subarray(Math.max(0, s.body.length - 4096)).toString('utf8');
|
|
25
|
+
const ref = MAP_REF.exec(tail)?.[1] ?? s.headers.get('sourcemap') ?? s.headers.get('x-sourcemap');
|
|
26
|
+
if (!ref) continue;
|
|
27
|
+
const base = {
|
|
28
|
+
ruleId: 'source-map', key: `source-map:${new URL(s.url).pathname}`, severity: 'medium', confidence: 'confirmed',
|
|
29
|
+
fix: 'Do not deploy source maps to production (or serve them only to authenticated developers, or upload them to your error tracker instead). Disable production browser source maps in the build.', cwe: [540],
|
|
30
|
+
};
|
|
31
|
+
if (ref.startsWith('data:')) {
|
|
32
|
+
findings.push(live({ ...base, title: `Source map embedded in ${new URL(s.url).pathname}`, location: { url: s.url }, description: 'The script carries its original source code inline, so anyone can read the unminified source.', evidence: 'sourceMappingURL is an inline data: URI.' }));
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (probed >= MAX_MAPS) continue;
|
|
36
|
+
let mapUrl;
|
|
37
|
+
try { mapUrl = new URL(ref, s.url); } catch { continue; }
|
|
38
|
+
if (mapUrl.origin !== session.origin) continue;
|
|
39
|
+
probed++;
|
|
40
|
+
let res;
|
|
41
|
+
try { res = await session.get(mapUrl.href, { maxBytes: 65_536 }); } catch { continue; }
|
|
42
|
+
if (res.status !== 200 || !/"(sources|mappings)"\s*:/.test(res.body.toString('utf8'))) continue;
|
|
43
|
+
findings.push(live({ ...base, key: `source-map:${mapUrl.pathname}`, title: `Source map is publicly readable: ${mapUrl.pathname}`, location: { url: mapUrl.href }, description: 'The site publishes a source map, so anyone can reconstruct the original, readable source code of the script. That shows internal routes, comments and logic that make attacks easier.', evidence: `Referenced by ${new URL(s.url).pathname}; HTTP 200 and the content has source-map fields.` }));
|
|
44
|
+
}
|
|
45
|
+
return { findings, probed };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function run(ctx) {
|
|
49
|
+
const session = ctx.live;
|
|
50
|
+
if (!session) return { status: 'not_run', reason: 'No target URL was given. Pass a registered URL (vsg release --url <preview>).' };
|
|
51
|
+
const displayUrl = session.displayUrl ?? session.url;
|
|
52
|
+
let page;
|
|
53
|
+
try { page = await session.page(); } catch (e) { return { status: 'not_run', reason: `Could not read ${displayUrl}: ${e.message}` }; }
|
|
54
|
+
if (page.status < 200 || page.status >= 400) return { status: 'not_run', reason: `${displayUrl} returned HTTP ${page.status}, so its headers could not be assessed. Check that the site is up and does not block automated requests.` };
|
|
55
|
+
|
|
56
|
+
const findings = [...analyzeHeaders(page.headers, displayUrl), ...analyzeCookies(page.headers.getSetCookie?.() ?? [], displayUrl)];
|
|
57
|
+
let cors = null;
|
|
58
|
+
try {
|
|
59
|
+
cors = await session.get(session.url, { headers: { origin: UNTRUSTED_ORIGIN }, maxBytes: 65_536 });
|
|
60
|
+
findings.push(...analyzeCors(cors.headers, displayUrl));
|
|
61
|
+
} catch (e) { return { status: 'not_run', reason: `The CORS probe for ${displayUrl} failed: ${e.message}` }; }
|
|
62
|
+
|
|
63
|
+
const files = await probeSensitiveFiles(session);
|
|
64
|
+
findings.push(...files.findings);
|
|
65
|
+
if (files.probed.length && files.probed.every((p) => p.error)) return { status: 'not_run', reason: `None of the sensitive-file probes for ${displayUrl} could be read: ${files.probed[0].error}` };
|
|
66
|
+
|
|
67
|
+
let maps;
|
|
68
|
+
try { maps = await sourceMaps(session); } catch (e) { return { status: 'not_run', reason: `Could not read the scripts served by ${displayUrl}: ${e.message}` }; }
|
|
69
|
+
findings.push(...maps.findings);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
status: 'ran', reason: '', findings,
|
|
73
|
+
evidence: { 'summary.json': { url: displayUrl, entryStatus: page.status, sensitiveFilesProbed: files.probed, soft404BaselineStatus: files.baselineStatus, mapsProbed: maps.probed, requestsSoFar: session.requests.length } },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default { id, title, modes, requiredIn, timeoutSec, applies, run };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// live-js-secrets (REQ-012, REQ-023): secrets in the JavaScript a registered site serves. Fetches the entry HTML, collects same-origin
|
|
2
|
+
// scripts (including Next.js _next/static chunks), caps at 200 files and 50 MB, and scans each with Betterleaks. Any secret is
|
|
3
|
+
// Critical. JWTs are decoded: a Supabase service_role token is Critical; anon keys and project URLs are recorded in evidence only.
|
|
4
|
+
import { hitToFinding, ROTATE, scanSecretsInBuffer } from '../_lib/secrets.mjs';
|
|
5
|
+
import { scrubUrl } from '../../core/findings.mjs';
|
|
6
|
+
import { inspectJwts, supabaseRefs } from '../../live/secrets.mjs';
|
|
7
|
+
|
|
8
|
+
export const id = 'live-js-secrets';
|
|
9
|
+
export const title = 'Live site: secrets in served JavaScript';
|
|
10
|
+
export const modes = ['url', 'release'];
|
|
11
|
+
export const requiredIn = ['url', 'release'];
|
|
12
|
+
export const timeoutSec = 1800;
|
|
13
|
+
|
|
14
|
+
// always applies: without a target the run reports not_run, so a required check can never be silently skipped
|
|
15
|
+
export const applies = () => true;
|
|
16
|
+
|
|
17
|
+
async function pool(items, limit, fn) {
|
|
18
|
+
const queue = [...items];
|
|
19
|
+
await Promise.all(Array.from({ length: Math.min(limit, queue.length) }, async () => { while (queue.length) await fn(queue.shift()); }));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function run(ctx) {
|
|
23
|
+
const session = ctx.live;
|
|
24
|
+
if (!session) return { status: 'not_run', reason: 'No target URL was given. Pass a registered URL (vsg release --url <preview>).' };
|
|
25
|
+
const displayUrl = session.displayUrl ?? scrubUrl(session.url);
|
|
26
|
+
let page;
|
|
27
|
+
let scripts;
|
|
28
|
+
try {
|
|
29
|
+
page = await session.page();
|
|
30
|
+
if (page.status < 200 || page.status >= 400) return { status: 'not_run', reason: `${displayUrl} returned HTTP ${page.status}, so its JavaScript could not be read. Check that the site is up and does not block automated requests.` };
|
|
31
|
+
if (page.truncated) return { status: 'not_run', reason: `Live JavaScript coverage is incomplete for ${displayUrl}: the entry HTML was truncated before all referenced scripts could be collected. Rerun after the entry page is readable in full.` };
|
|
32
|
+
scripts = await session.scripts();
|
|
33
|
+
} catch (e) { return { status: 'not_run', reason: `Could not read ${displayUrl}: ${e.message}` }; }
|
|
34
|
+
|
|
35
|
+
const truncated = scripts.items.filter((s) => s.truncated).map((s) => scrubUrl(s.url));
|
|
36
|
+
const failed = scripts.failed.map((s) => ({ ...s, url: scrubUrl(s.url) }));
|
|
37
|
+
if (failed.length || truncated.length || scripts.capped) {
|
|
38
|
+
const gaps = [];
|
|
39
|
+
if (failed.length) gaps.push(`${failed.length} referenced script(s) could not be read`);
|
|
40
|
+
if (truncated.length) gaps.push(`${truncated.length} referenced script(s) were truncated`);
|
|
41
|
+
if (scripts.capped) gaps.push(`the scan stopped at the file or size cap (${scripts.notScanned} script(s) were not scanned)`);
|
|
42
|
+
return {
|
|
43
|
+
status: 'not_run', reason: `Live JavaScript coverage is incomplete for ${displayUrl}: ${gaps.join('; ')}. Rerun after all referenced scripts are readable and within the scan cap.`,
|
|
44
|
+
evidence: { 'summary.json': { url: displayUrl, documentsScanned: 1 + scripts.items.length, bytes: scripts.bytes, capped: scripts.capped, failed, truncated, validated: ctx.mode === 'release' && !ctx.flags?.noValidate } },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const validate = ctx.mode === 'release' && !ctx.flags?.noValidate;
|
|
49
|
+
const docs = [{ url: page.url, body: page.body }, ...scripts.items.map((s) => ({ url: s.url, body: s.body }))];
|
|
50
|
+
const findings = [];
|
|
51
|
+
const anon = new Map();
|
|
52
|
+
const projects = new Set();
|
|
53
|
+
let failure = null;
|
|
54
|
+
|
|
55
|
+
await pool(docs, 4, async (doc) => {
|
|
56
|
+
if (failure) return;
|
|
57
|
+
const pathName = new URL(doc.url).pathname || '/';
|
|
58
|
+
const text = doc.body.toString('utf8');
|
|
59
|
+
for (const ref of supabaseRefs(text)) projects.add(ref);
|
|
60
|
+
const jwts = inspectJwts(text);
|
|
61
|
+
const handled = new Set();
|
|
62
|
+
for (const j of jwts) {
|
|
63
|
+
if (j.role === 'service_role') {
|
|
64
|
+
handled.add(j.hash);
|
|
65
|
+
findings.push({
|
|
66
|
+
category: 'secret', severity: 'critical', confidence: 'confirmed', tool: 'vsg-live', ruleId: 'supabase-service-role-jwt', key: `service-role:${j.hash}`,
|
|
67
|
+
location: { url: doc.url }, title: 'Supabase service_role key in served JavaScript',
|
|
68
|
+
description: 'A JWT whose payload has role "service_role" is served to every visitor. That key bypasses Row Level Security, so anyone can read and change all data in the project.',
|
|
69
|
+
evidence: `${j.redacted} (role service_role${j.ref ? `, project ${j.ref}` : ''})`, refs: [], cwe: [798],
|
|
70
|
+
fix: `${ROTATE} For Supabase, rotate the JWT secret or the service key in the project settings, and use the service key only in server code.`,
|
|
71
|
+
});
|
|
72
|
+
} else if (j.role === 'anon') {
|
|
73
|
+
handled.add(j.hash);
|
|
74
|
+
anon.set(j.hash, { redacted: j.redacted, ref: j.ref, file: pathName });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const res = await scanSecretsInBuffer(ctx, doc.body, { name: pathName, validate });
|
|
78
|
+
if (res.status !== 'ran') { failure ||= res.reason; return; }
|
|
79
|
+
for (const hit of res.hits) {
|
|
80
|
+
if (handled.has(hit.hash)) continue; // a JWT already classified above (anon keys are public by design)
|
|
81
|
+
const f = hitToFinding(hit, {
|
|
82
|
+
category: 'secret', severity: 'critical', where: 'in served JavaScript',
|
|
83
|
+
fix: `${ROTATE} Then find which source value or public-prefixed environment variable (NEXT_PUBLIC_, EXPO_PUBLIC_, VITE_) reached the bundle and rebuild.`,
|
|
84
|
+
});
|
|
85
|
+
f.location = { url: doc.url };
|
|
86
|
+
findings.push(f);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
if (failure) return { status: 'not_run', reason: failure };
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
status: 'ran', reason: '', findings, toolVersion: ctx.registry?.get('betterleaks')?.version ?? null,
|
|
93
|
+
evidence: {
|
|
94
|
+
'summary.json': { url: displayUrl, documentsScanned: docs.length, bytes: scripts.bytes, capped: scripts.capped, failed, validated: validate },
|
|
95
|
+
'supabase.json': { projects: [...projects].sort(), anonKeys: [...anon.values()].map((a) => ({ ...a, note: 'anon keys are public by design; recorded for review, not reported' })) },
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default { id, title, modes, requiredIn, timeoutSec, applies, run };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// ClamAV adapter (Linux): clamscan -r -i --no-summary. Exit 0 clean, 1 found, 2 error. vsg never installs ClamAV or updates its signatures.
|
|
2
|
+
export const CLAMAV_TIMEOUT_MS = 30 * 60 * 1000;
|
|
3
|
+
|
|
4
|
+
/** Parses "<path>: <Signature> FOUND" lines. Returns { status: 'clean' | 'detected' | 'error', detections: [{ file, signature }], reason }. */
|
|
5
|
+
export function parseClamav(stdout, stderr, exitCode) {
|
|
6
|
+
const detections = [];
|
|
7
|
+
for (const line of String(stdout ?? '').split(/\r?\n/)) {
|
|
8
|
+
const m = /^(.+): (\S+) FOUND\s*$/.exec(line);
|
|
9
|
+
if (m) detections.push({ file: m[1], signature: m[2] });
|
|
10
|
+
}
|
|
11
|
+
if (detections.length) return { status: 'detected', detections, reason: '' };
|
|
12
|
+
if (exitCode === 0) return { status: 'clean', detections, reason: '' };
|
|
13
|
+
const why = String(stderr ?? '').split(/\r?\n/).find((l) => /error|no supported database|can't|cannot/i.test(l)) ?? String(stderr ?? '').trim().split(/\r?\n/)[0];
|
|
14
|
+
return { status: 'error', detections, reason: `clamscan exited ${exitCode}${why ? `: ${why.slice(0, 200)}` : ''}` };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const clamscanArgs = (targets) => ['-r', '-i', '--no-summary', ...targets];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Microsoft Defender adapter (Windows): MpCmdRun.exe custom scans, no elevation needed, nothing remediated.
|
|
2
|
+
// Sources: Microsoft Learn, "Use the command line to manage Microsoft Defender Antivirus" (-Scan, -ScanType 3, -File, -DisableRemediation;
|
|
3
|
+
// exit 0 = no malware or remediated, 2 = malware not remediated or scan error; detections are printed in the command output) and the
|
|
4
|
+
// output of a real clean scan ("Scanning <path> found no threats."). The detection block layout (a "Threat" line, then "file" resource
|
|
5
|
+
// lines) follows public MpCmdRun parsers; the parser below accepts both "Threat" and "Threat Name" and any resource kind.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
export const DEFENDER_TIMEOUT_MS = 30 * 60 * 1000;
|
|
10
|
+
|
|
11
|
+
/** MpCmdRun.exe: newest platform folder first, then the inbox copy. Null when not Windows or not found. */
|
|
12
|
+
export function findDefender(env = process.env, platform = process.platform) {
|
|
13
|
+
if (platform !== 'win32') return null;
|
|
14
|
+
const candidates = [path.join(env.ProgramFiles ?? 'C:\\Program Files', 'Windows Defender', 'MpCmdRun.exe')];
|
|
15
|
+
const platformDir = path.join(env.ProgramData ?? 'C:\\ProgramData', 'Microsoft', 'Windows Defender', 'Platform');
|
|
16
|
+
try { for (const v of fs.readdirSync(platformDir).sort().reverse()) candidates.unshift(path.join(platformDir, v, 'MpCmdRun.exe')); } catch { /* no platform folder */ }
|
|
17
|
+
return candidates.find((c) => fs.existsSync(c)) ?? null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const defenderArgs = (target) => ['-Scan', '-ScanType', '3', '-File', target, '-DisableRemediation'];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parses MpCmdRun custom-scan output. Returns { status: 'clean' | 'detected' | 'error', threats: [{ name, files: [path] }], reason }.
|
|
24
|
+
* A reported threat count with no parsable block still counts as a detection (the target path is the location).
|
|
25
|
+
*/
|
|
26
|
+
export function parseDefender(stdout, exitCode, target) {
|
|
27
|
+
const text = String(stdout ?? '');
|
|
28
|
+
const threats = [];
|
|
29
|
+
let current = null;
|
|
30
|
+
for (const line of text.split(/\r?\n/)) {
|
|
31
|
+
const t = /^\s*Threat(?:\s+Name|\s+\d+)?\s*:\s*(.+?)\s*$/i.exec(line);
|
|
32
|
+
if (t) { current = { name: t[1], files: [] }; threats.push(current); continue; }
|
|
33
|
+
const r = /^\s+(?:file|containerfile|folder|process|regkey|regvalue|webfile|behavior|amsi)\s*:\s*(.+?)\s*$/i.exec(line);
|
|
34
|
+
if (r && current) current.files.push(r[1].replace(/^_/, ''));
|
|
35
|
+
}
|
|
36
|
+
const count = Number(/found\s+(\d+)\s+threats?/i.exec(text)?.[1] ?? 0);
|
|
37
|
+
if (threats.length || count > 0) {
|
|
38
|
+
if (!threats.length) threats.push({ name: 'unnamed threat (see the raw Defender output)', files: [target] });
|
|
39
|
+
return { status: 'detected', threats, reason: '' };
|
|
40
|
+
}
|
|
41
|
+
if (exitCode === 0) return { status: 'clean', threats: [], reason: '' };
|
|
42
|
+
return { status: 'error', threats: [], reason: `MpCmdRun exited ${exitCode} without a detection: the scan did not complete` };
|
|
43
|
+
}
|