@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
package/dist/commands.js
ADDED
|
@@ -0,0 +1,2012 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Additional CLI commands for Lattice
|
|
4
|
+
* These extend the core functionality with quality-of-life improvements
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.validateProjectSetup = validateProjectSetup;
|
|
8
|
+
exports.configShow = configShow;
|
|
9
|
+
exports.rulesExplain = rulesExplain;
|
|
10
|
+
exports.diff = diff;
|
|
11
|
+
exports.ci = ci;
|
|
12
|
+
exports.completions = completions;
|
|
13
|
+
exports.detectProjectType = detectProjectType;
|
|
14
|
+
exports.doctorFix = doctorFix;
|
|
15
|
+
exports.securityScan = securityScan;
|
|
16
|
+
exports.loadAllowlist = loadAllowlist;
|
|
17
|
+
exports.addToAllowlist = addToAllowlist;
|
|
18
|
+
exports.generateSarifReport = generateSarifReport;
|
|
19
|
+
const fs_1 = require("fs");
|
|
20
|
+
const path_1 = require("path");
|
|
21
|
+
const child_process_1 = require("child_process");
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// PACKAGE.JSON VALIDATION - Check if project is properly initialized
|
|
24
|
+
// ============================================================================
|
|
25
|
+
async function validateProjectSetup(cwd) {
|
|
26
|
+
const packageJsonPath = (0, path_1.join)(cwd, 'package.json');
|
|
27
|
+
try {
|
|
28
|
+
await fs_1.promises.access(packageJsonPath);
|
|
29
|
+
// Check if it's a valid JSON
|
|
30
|
+
try {
|
|
31
|
+
const content = await fs_1.promises.readFile(packageJsonPath, 'utf-8');
|
|
32
|
+
JSON.parse(content);
|
|
33
|
+
return { valid: true };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return {
|
|
37
|
+
valid: false,
|
|
38
|
+
error: 'package.json exists but is not valid JSON',
|
|
39
|
+
suggestion: 'Fix the JSON syntax in package.json',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return {
|
|
45
|
+
valid: false,
|
|
46
|
+
error: 'package.json not found',
|
|
47
|
+
suggestion: getProjectSetupSuggestion(cwd),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function getProjectSetupSuggestion(cwd) {
|
|
52
|
+
return `
|
|
53
|
+
Lattice requires an existing project. To fix this:
|
|
54
|
+
|
|
55
|
+
Option 1 - Initialize a new Next.js project:
|
|
56
|
+
npx create-next-app@latest . --typescript --tailwind --app
|
|
57
|
+
|
|
58
|
+
Option 2 - Initialize a new Expo project:
|
|
59
|
+
npx create-expo-app@latest . --template blank-typescript
|
|
60
|
+
|
|
61
|
+
Option 3 - Initialize a basic Node project:
|
|
62
|
+
npm init -y
|
|
63
|
+
|
|
64
|
+
Then run 'npx latticeai' to add Lattice to your project.
|
|
65
|
+
|
|
66
|
+
Note: Lattice adds guardrails and verification to existing projects.
|
|
67
|
+
It does not create the base project structure.
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
async function configShow(options = {}) {
|
|
71
|
+
const cwd = options.cwd || process.cwd();
|
|
72
|
+
const configPath = (0, path_1.join)(cwd, '.lattice', 'config.json');
|
|
73
|
+
try {
|
|
74
|
+
const configContent = await fs_1.promises.readFile(configPath, 'utf-8');
|
|
75
|
+
const config = JSON.parse(configContent);
|
|
76
|
+
if (options.json) {
|
|
77
|
+
console.log(JSON.stringify(config, null, 2));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
console.log('\n📋 Lattice Configuration\n');
|
|
81
|
+
console.log('─'.repeat(50));
|
|
82
|
+
console.log(` 📁 Config file: .lattice/config.json`);
|
|
83
|
+
console.log('─'.repeat(50));
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log(' Project Type: ' + formatValue(config.projectType));
|
|
86
|
+
console.log(' Strictness: ' + formatValue(config.strictnessPreset));
|
|
87
|
+
if (config.integrations) {
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log(' Integrations:');
|
|
90
|
+
for (const [key, value] of Object.entries(config.integrations)) {
|
|
91
|
+
console.log(` • ${key}: ${formatValue(value)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Show manifest info if available
|
|
95
|
+
const manifestPath = (0, path_1.join)(cwd, '.lattice', 'manifest.json');
|
|
96
|
+
try {
|
|
97
|
+
const manifestContent = await fs_1.promises.readFile(manifestPath, 'utf-8');
|
|
98
|
+
const manifest = JSON.parse(manifestContent);
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log(' Manifest:');
|
|
101
|
+
console.log(` • Generator: v${manifest.generatorVersion || 'unknown'}`);
|
|
102
|
+
console.log(` • Generated: ${manifest.timestamp ? new Date(manifest.timestamp).toLocaleString() : 'unknown'}`);
|
|
103
|
+
console.log(` • Files: ${manifest.files?.length || 0}`);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// No manifest
|
|
107
|
+
}
|
|
108
|
+
// Show compliance info if available
|
|
109
|
+
const compliancePath = (0, path_1.join)(cwd, '.lattice', 'compliance.json');
|
|
110
|
+
try {
|
|
111
|
+
const complianceContent = await fs_1.promises.readFile(compliancePath, 'utf-8');
|
|
112
|
+
const compliance = JSON.parse(complianceContent);
|
|
113
|
+
const lastRun = compliance.history?.[0];
|
|
114
|
+
if (lastRun) {
|
|
115
|
+
console.log('');
|
|
116
|
+
console.log(' Last Verify:');
|
|
117
|
+
console.log(` • Status: ${lastRun.passed ? '✓ Passed' : '✗ Failed'}`);
|
|
118
|
+
console.log(` • Time: ${new Date(lastRun.timestamp).toLocaleString()}`);
|
|
119
|
+
console.log(` • Checks: ${lastRun.checksRun?.join(', ') || 'unknown'}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// No compliance
|
|
124
|
+
}
|
|
125
|
+
console.log('');
|
|
126
|
+
console.log('─'.repeat(50));
|
|
127
|
+
console.log(' Run: lattice config show --json for raw output');
|
|
128
|
+
console.log('─'.repeat(50));
|
|
129
|
+
console.log('');
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (error.code === 'ENOENT') {
|
|
133
|
+
console.log('\n✗ No Lattice configuration found.');
|
|
134
|
+
console.log(' Run: npx latticeai to set up Lattice\n');
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function formatValue(value) {
|
|
141
|
+
if (value === null || value === undefined)
|
|
142
|
+
return '(not set)';
|
|
143
|
+
if (typeof value === 'boolean')
|
|
144
|
+
return value ? 'yes' : 'no';
|
|
145
|
+
if (typeof value === 'object')
|
|
146
|
+
return JSON.stringify(value);
|
|
147
|
+
return String(value);
|
|
148
|
+
}
|
|
149
|
+
async function rulesExplain(options = {}) {
|
|
150
|
+
const cwd = options.cwd || process.cwd();
|
|
151
|
+
const rulesPath = (0, path_1.join)(cwd, '.cursor', 'rules.md');
|
|
152
|
+
try {
|
|
153
|
+
const rulesContent = await fs_1.promises.readFile(rulesPath, 'utf-8');
|
|
154
|
+
console.log('\n📖 Lattice Rules Explanation\n');
|
|
155
|
+
console.log('─'.repeat(60));
|
|
156
|
+
console.log('');
|
|
157
|
+
console.log('Lattice generates shared agent instructions for Cursor, Codex, and Claude.');
|
|
158
|
+
console.log('The legacy compatibility file at .cursor/rules.md contains:');
|
|
159
|
+
console.log('');
|
|
160
|
+
// Analyze the rules content
|
|
161
|
+
const sections = [];
|
|
162
|
+
if (rulesContent.includes('Version Protocol')) {
|
|
163
|
+
sections.push('📦 Version Protocol - Ensures AI uses correct package versions');
|
|
164
|
+
}
|
|
165
|
+
if (rulesContent.includes('Project Type')) {
|
|
166
|
+
sections.push('🏗️ Project Type - Specifies framework (Next.js, Expo, etc.)');
|
|
167
|
+
}
|
|
168
|
+
if (rulesContent.includes('Verification') || rulesContent.includes('verify')) {
|
|
169
|
+
sections.push('✓ Verification Rules - How to check code quality');
|
|
170
|
+
}
|
|
171
|
+
if (rulesContent.includes('CI') || rulesContent.includes('Continuous Integration')) {
|
|
172
|
+
sections.push('🔄 CI Enforcement - GitHub Actions workflow requirements');
|
|
173
|
+
}
|
|
174
|
+
if (rulesContent.includes('Architecture') || rulesContent.includes('directory structure')) {
|
|
175
|
+
sections.push('📁 Architecture - Directory structure and patterns');
|
|
176
|
+
}
|
|
177
|
+
if (sections.length > 0) {
|
|
178
|
+
console.log('Detected sections:');
|
|
179
|
+
for (const section of sections) {
|
|
180
|
+
console.log(` ${section}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
console.log('');
|
|
184
|
+
console.log('─'.repeat(60));
|
|
185
|
+
console.log('');
|
|
186
|
+
// Show stats
|
|
187
|
+
const lines = rulesContent.split('\n').length;
|
|
188
|
+
const headers = (rulesContent.match(/^#+\s/gm) || []).length;
|
|
189
|
+
const codeBlocks = (rulesContent.match(/```/g) || []).length / 2;
|
|
190
|
+
console.log('📊 Stats:');
|
|
191
|
+
console.log(` Lines: ${lines}`);
|
|
192
|
+
console.log(` Sections: ${headers}`);
|
|
193
|
+
console.log(` Code blocks: ${Math.floor(codeBlocks)}`);
|
|
194
|
+
console.log('');
|
|
195
|
+
// Extract the LATTICE markers
|
|
196
|
+
const latticeMatch = rulesContent.match(/\x3c!-- LATTICE:START v([\d.]+)/);
|
|
197
|
+
if (latticeMatch) {
|
|
198
|
+
console.log(`🔖 Lattice version: ${latticeMatch[1]}`);
|
|
199
|
+
}
|
|
200
|
+
console.log('');
|
|
201
|
+
console.log('─'.repeat(60));
|
|
202
|
+
console.log(' View files: .cursor/rules.md, .cursor/rules/lattice-core.mdc, AGENTS.md');
|
|
203
|
+
console.log(' Regenerate: npx latticeai update');
|
|
204
|
+
console.log('─'.repeat(60));
|
|
205
|
+
console.log('');
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (error.code === 'ENOENT') {
|
|
209
|
+
console.log('\n✗ No Lattice rules file found at .cursor/rules.md');
|
|
210
|
+
console.log(' Run: npx latticeai to generate rules, AGENTS.md, and Claude settings\n');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function diff(options = {}) {
|
|
217
|
+
const cwd = options.cwd || process.cwd();
|
|
218
|
+
const manifestPath = (0, path_1.join)(cwd, '.lattice', 'manifest.json');
|
|
219
|
+
try {
|
|
220
|
+
const manifestContent = await fs_1.promises.readFile(manifestPath, 'utf-8');
|
|
221
|
+
const manifest = JSON.parse(manifestContent);
|
|
222
|
+
const trackedFiles = manifest.files || [];
|
|
223
|
+
if (options.json) {
|
|
224
|
+
const changes = await computeChanges(cwd, trackedFiles);
|
|
225
|
+
console.log(JSON.stringify(changes, null, 2));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
console.log('\n📊 Lattice Diff\n');
|
|
229
|
+
console.log('Changes since last generation:\n');
|
|
230
|
+
const changes = await computeChanges(cwd, trackedFiles);
|
|
231
|
+
if (changes.modified.length === 0 && changes.deleted.length === 0 && changes.added.length === 0) {
|
|
232
|
+
console.log(' ✓ No changes detected\n');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (changes.modified.length > 0) {
|
|
236
|
+
console.log(' Modified:');
|
|
237
|
+
for (const file of changes.modified) {
|
|
238
|
+
console.log(` • ${file}`);
|
|
239
|
+
}
|
|
240
|
+
console.log('');
|
|
241
|
+
}
|
|
242
|
+
if (changes.deleted.length > 0) {
|
|
243
|
+
console.log(' Deleted:');
|
|
244
|
+
for (const file of changes.deleted) {
|
|
245
|
+
console.log(` ✗ ${file}`);
|
|
246
|
+
}
|
|
247
|
+
console.log('');
|
|
248
|
+
}
|
|
249
|
+
if (changes.added.length > 0) {
|
|
250
|
+
console.log(' New (untracked by Lattice):');
|
|
251
|
+
for (const file of changes.added.slice(0, 10)) {
|
|
252
|
+
console.log(` + ${file}`);
|
|
253
|
+
}
|
|
254
|
+
if (changes.added.length > 10) {
|
|
255
|
+
console.log(` ... and ${changes.added.length - 10} more`);
|
|
256
|
+
}
|
|
257
|
+
console.log('');
|
|
258
|
+
}
|
|
259
|
+
console.log('─'.repeat(50));
|
|
260
|
+
console.log(' Run: npx latticeai update to regenerate rules');
|
|
261
|
+
console.log('─'.repeat(50));
|
|
262
|
+
console.log('');
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
if (error.code === 'ENOENT') {
|
|
266
|
+
console.log('\n✗ No Lattice manifest found.');
|
|
267
|
+
console.log(' Run: npx latticeai to set up Lattice\n');
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async function computeChanges(cwd, trackedFiles) {
|
|
274
|
+
const changes = {
|
|
275
|
+
modified: [],
|
|
276
|
+
deleted: [],
|
|
277
|
+
added: [],
|
|
278
|
+
};
|
|
279
|
+
for (const file of trackedFiles) {
|
|
280
|
+
const fullPath = (0, path_1.join)(cwd, file.path);
|
|
281
|
+
try {
|
|
282
|
+
await fs_1.promises.access(fullPath);
|
|
283
|
+
// File exists - could check hash for modification
|
|
284
|
+
// For now, we just check existence
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
changes.deleted.push(file.path);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Check for new config files in .lattice or .cursor
|
|
291
|
+
const latticeDir = (0, path_1.join)(cwd, '.lattice');
|
|
292
|
+
const cursorDir = (0, path_1.join)(cwd, '.cursor');
|
|
293
|
+
try {
|
|
294
|
+
const latticeFiles = await fs_1.promises.readdir(latticeDir);
|
|
295
|
+
for (const file of latticeFiles) {
|
|
296
|
+
const relativePath = `.lattice/${file}`;
|
|
297
|
+
if (!trackedFiles.some((f) => f.path === relativePath)) {
|
|
298
|
+
changes.added.push(relativePath);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
// Directory doesn't exist
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
const cursorFiles = await fs_1.promises.readdir(cursorDir);
|
|
307
|
+
for (const file of cursorFiles) {
|
|
308
|
+
const relativePath = `.cursor/${file}`;
|
|
309
|
+
if (!trackedFiles.some((f) => f.path === relativePath)) {
|
|
310
|
+
changes.added.push(relativePath);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Directory doesn't exist
|
|
316
|
+
}
|
|
317
|
+
return changes;
|
|
318
|
+
}
|
|
319
|
+
async function ci(options = {}) {
|
|
320
|
+
const cwd = options.cwd || process.cwd();
|
|
321
|
+
const provider = options.provider || 'github';
|
|
322
|
+
// Read config to determine project type
|
|
323
|
+
let projectType = 'nextjs';
|
|
324
|
+
try {
|
|
325
|
+
const configPath = (0, path_1.join)(cwd, '.lattice', 'config.json');
|
|
326
|
+
const configContent = await fs_1.promises.readFile(configPath, 'utf-8');
|
|
327
|
+
const config = JSON.parse(configContent);
|
|
328
|
+
projectType = config.projectType || 'nextjs';
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
// Use default
|
|
332
|
+
}
|
|
333
|
+
const isExpo = projectType === 'expo-eas';
|
|
334
|
+
let workflow;
|
|
335
|
+
if (provider === 'github') {
|
|
336
|
+
workflow = generateGitHubWorkflow(isExpo);
|
|
337
|
+
const outputPath = (0, path_1.join)(cwd, '.github', 'workflows', 'ci.yml');
|
|
338
|
+
if (options.dryRun) {
|
|
339
|
+
console.log('\n📄 Generated GitHub Actions Workflow:\n');
|
|
340
|
+
console.log(workflow);
|
|
341
|
+
console.log(`\nWould write to: ${outputPath}`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
await fs_1.promises.mkdir((0, path_1.dirname)(outputPath), { recursive: true });
|
|
345
|
+
await fs_1.promises.writeFile(outputPath, workflow);
|
|
346
|
+
console.log(`\n✓ Generated: ${outputPath}\n`);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
workflow = generateGitLabCI(isExpo);
|
|
350
|
+
const outputPath = (0, path_1.join)(cwd, '.gitlab-ci.yml');
|
|
351
|
+
if (options.dryRun) {
|
|
352
|
+
console.log('\n📄 Generated GitLab CI:\n');
|
|
353
|
+
console.log(workflow);
|
|
354
|
+
console.log(`\nWould write to: ${outputPath}`);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
await fs_1.promises.writeFile(outputPath, workflow);
|
|
358
|
+
console.log(`\n✓ Generated: ${outputPath}\n`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function generateGitHubWorkflow(isExpo) {
|
|
362
|
+
const buildStep = isExpo
|
|
363
|
+
? ''
|
|
364
|
+
: `
|
|
365
|
+
- name: Build
|
|
366
|
+
run: npm run build`;
|
|
367
|
+
return `name: CI
|
|
368
|
+
|
|
369
|
+
on:
|
|
370
|
+
push:
|
|
371
|
+
branches: [main, master]
|
|
372
|
+
pull_request:
|
|
373
|
+
branches: [main, master]
|
|
374
|
+
|
|
375
|
+
jobs:
|
|
376
|
+
verify:
|
|
377
|
+
runs-on: ubuntu-latest
|
|
378
|
+
|
|
379
|
+
steps:
|
|
380
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
381
|
+
|
|
382
|
+
- name: Setup Node.js
|
|
383
|
+
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
|
384
|
+
with:
|
|
385
|
+
node-version: '24.22.0'
|
|
386
|
+
cache: 'npm'
|
|
387
|
+
|
|
388
|
+
- name: Install dependencies
|
|
389
|
+
run: npm ci
|
|
390
|
+
|
|
391
|
+
- name: Lint
|
|
392
|
+
run: npm run lint
|
|
393
|
+
|
|
394
|
+
- name: Typecheck
|
|
395
|
+
run: npm run typecheck
|
|
396
|
+
|
|
397
|
+
- name: Test
|
|
398
|
+
run: npm run test
|
|
399
|
+
${buildStep}
|
|
400
|
+
`;
|
|
401
|
+
}
|
|
402
|
+
function generateGitLabCI(isExpo) {
|
|
403
|
+
const buildJob = isExpo
|
|
404
|
+
? ''
|
|
405
|
+
: `
|
|
406
|
+
build:
|
|
407
|
+
stage: build
|
|
408
|
+
script:
|
|
409
|
+
- npm run build
|
|
410
|
+
`;
|
|
411
|
+
return `image: node:24.22.0
|
|
412
|
+
|
|
413
|
+
stages:
|
|
414
|
+
- verify
|
|
415
|
+
- build
|
|
416
|
+
|
|
417
|
+
cache:
|
|
418
|
+
paths:
|
|
419
|
+
- node_modules/
|
|
420
|
+
|
|
421
|
+
before_script:
|
|
422
|
+
- npm ci
|
|
423
|
+
|
|
424
|
+
lint:
|
|
425
|
+
stage: verify
|
|
426
|
+
script:
|
|
427
|
+
- npm run lint
|
|
428
|
+
|
|
429
|
+
typecheck:
|
|
430
|
+
stage: verify
|
|
431
|
+
script:
|
|
432
|
+
- npm run typecheck
|
|
433
|
+
|
|
434
|
+
test:
|
|
435
|
+
stage: verify
|
|
436
|
+
script:
|
|
437
|
+
- npm run test
|
|
438
|
+
${buildJob}`;
|
|
439
|
+
}
|
|
440
|
+
function completions(shell) {
|
|
441
|
+
switch (shell) {
|
|
442
|
+
case 'bash':
|
|
443
|
+
console.log(generateBashCompletions());
|
|
444
|
+
break;
|
|
445
|
+
case 'zsh':
|
|
446
|
+
console.log(generateZshCompletions());
|
|
447
|
+
break;
|
|
448
|
+
case 'fish':
|
|
449
|
+
console.log(generateFishCompletions());
|
|
450
|
+
break;
|
|
451
|
+
case 'powershell':
|
|
452
|
+
console.log(generatePowerShellCompletions());
|
|
453
|
+
break;
|
|
454
|
+
default:
|
|
455
|
+
console.error(`Unknown shell: ${shell}`);
|
|
456
|
+
console.error('Supported: bash, zsh, fish, powershell');
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function generateBashCompletions() {
|
|
460
|
+
return `# Lattice CLI bash completions
|
|
461
|
+
# Add to ~/.bashrc: eval "$(lattice completions bash)"
|
|
462
|
+
|
|
463
|
+
_lattice_completions() {
|
|
464
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
465
|
+
local commands="init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci security-scan scan completions help"
|
|
466
|
+
|
|
467
|
+
if [ "\${COMP_CWORD}" -eq 1 ]; then
|
|
468
|
+
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
469
|
+
elif [ "\${COMP_WORDS[1]}" = "bundle" ]; then
|
|
470
|
+
COMPREPLY=( $(compgen -W "update diff rollback" -- "\${cur}") )
|
|
471
|
+
elif [ "\${COMP_WORDS[1]}" = "config" ]; then
|
|
472
|
+
COMPREPLY=( $(compgen -W "show" -- "\${cur}") )
|
|
473
|
+
elif [ "\${COMP_WORDS[1]}" = "rules" ]; then
|
|
474
|
+
COMPREPLY=( $(compgen -W "explain" -- "\${cur}") )
|
|
475
|
+
elif [ "\${COMP_WORDS[1]}" = "completions" ]; then
|
|
476
|
+
COMPREPLY=( $(compgen -W "bash zsh fish powershell" -- "\${cur}") )
|
|
477
|
+
fi
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
complete -F _lattice_completions lattice
|
|
481
|
+
complete -F _lattice_completions latticeai
|
|
482
|
+
`;
|
|
483
|
+
}
|
|
484
|
+
function generateZshCompletions() {
|
|
485
|
+
return `#compdef lattice latticeai
|
|
486
|
+
# Lattice CLI zsh completions
|
|
487
|
+
# Add to ~/.zshrc: eval "$(lattice completions zsh)"
|
|
488
|
+
|
|
489
|
+
_lattice() {
|
|
490
|
+
local commands=(
|
|
491
|
+
'init:Initialize Lattice configuration'
|
|
492
|
+
'generate:Generate the Lattice pack'
|
|
493
|
+
'apply:Apply pack to project'
|
|
494
|
+
'verify:Run verification checks'
|
|
495
|
+
'verify-rules:Verify rules file'
|
|
496
|
+
'doctor:Check environment'
|
|
497
|
+
'status:Show current status'
|
|
498
|
+
'drift:Detect configuration drift'
|
|
499
|
+
'progress:Show PROJECT_GUIDE.md progress'
|
|
500
|
+
'bundle:Manage bundles'
|
|
501
|
+
'check:Check for updates'
|
|
502
|
+
'update:Update Lattice rules'
|
|
503
|
+
'setup:Interactive setup'
|
|
504
|
+
'login:Authenticate with Lattice'
|
|
505
|
+
'logout:Remove credentials'
|
|
506
|
+
'whoami:Show login status'
|
|
507
|
+
'create:Create project from Architect'
|
|
508
|
+
'config:Configuration commands'
|
|
509
|
+
'rules:Rules commands'
|
|
510
|
+
'ci:Generate CI workflow'
|
|
511
|
+
'diff:Show changes since generation'
|
|
512
|
+
'security-scan:Scan for exposed secrets'
|
|
513
|
+
'scan:Scan for exposed secrets (alias)'
|
|
514
|
+
'completions:Generate shell completions'
|
|
515
|
+
'help:Show help'
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
_describe 'command' commands
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
compdef _lattice lattice
|
|
522
|
+
compdef _lattice latticeai
|
|
523
|
+
`;
|
|
524
|
+
}
|
|
525
|
+
function generateFishCompletions() {
|
|
526
|
+
return `# Lattice CLI fish completions
|
|
527
|
+
# Add to ~/.config/fish/completions/lattice.fish
|
|
528
|
+
|
|
529
|
+
complete -c lattice -f
|
|
530
|
+
complete -c latticeai -f
|
|
531
|
+
|
|
532
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff completions help" -a init -d "Initialize Lattice"
|
|
533
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff completions help" -a generate -d "Generate pack"
|
|
534
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff completions help" -a apply -d "Apply pack"
|
|
535
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff completions help" -a verify -d "Run checks"
|
|
536
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff completions help" -a doctor -d "Check environment"
|
|
537
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff completions help" -a status -d "Show status"
|
|
538
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff completions help" -a login -d "Authenticate"
|
|
539
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff completions help" -a create -d "Create from Architect"
|
|
540
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff completions help" -a config -d "Config commands"
|
|
541
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff security-scan scan completions help" -a ci -d "Generate CI"
|
|
542
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff security-scan scan completions help" -a security-scan -d "Scan for exposed secrets"
|
|
543
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff security-scan scan completions help" -a scan -d "Scan for exposed secrets (alias)"
|
|
544
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff security-scan scan completions help" -a completions -d "Shell completions"
|
|
545
|
+
complete -c lattice -n "not __fish_seen_subcommand_from init generate apply verify verify-rules doctor status drift progress bundle check update setup login logout whoami create config rules ci diff security-scan scan completions help" -a help -d "Show help"
|
|
546
|
+
|
|
547
|
+
complete -c lattice -n "__fish_seen_subcommand_from bundle" -a "update diff rollback"
|
|
548
|
+
complete -c lattice -n "__fish_seen_subcommand_from config" -a "show"
|
|
549
|
+
complete -c lattice -n "__fish_seen_subcommand_from rules" -a "explain"
|
|
550
|
+
complete -c lattice -n "__fish_seen_subcommand_from completions" -a "bash zsh fish powershell"
|
|
551
|
+
|
|
552
|
+
# Copy for latticeai alias
|
|
553
|
+
complete -c latticeai -w lattice
|
|
554
|
+
`;
|
|
555
|
+
}
|
|
556
|
+
function generatePowerShellCompletions() {
|
|
557
|
+
return `# Lattice CLI PowerShell completions
|
|
558
|
+
# Add to $PROFILE: Invoke-Expression (lattice completions powershell | Out-String)
|
|
559
|
+
|
|
560
|
+
$scriptblock = {
|
|
561
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
562
|
+
|
|
563
|
+
$commands = @(
|
|
564
|
+
@{ Name = 'init'; Description = 'Initialize Lattice configuration' }
|
|
565
|
+
@{ Name = 'generate'; Description = 'Generate the Lattice pack' }
|
|
566
|
+
@{ Name = 'apply'; Description = 'Apply pack to project' }
|
|
567
|
+
@{ Name = 'verify'; Description = 'Run verification checks' }
|
|
568
|
+
@{ Name = 'verify-rules'; Description = 'Verify rules file' }
|
|
569
|
+
@{ Name = 'doctor'; Description = 'Check environment' }
|
|
570
|
+
@{ Name = 'status'; Description = 'Show current status' }
|
|
571
|
+
@{ Name = 'drift'; Description = 'Detect configuration drift' }
|
|
572
|
+
@{ Name = 'progress'; Description = 'Show PROJECT_GUIDE.md progress' }
|
|
573
|
+
@{ Name = 'bundle'; Description = 'Manage bundles' }
|
|
574
|
+
@{ Name = 'check'; Description = 'Check for updates' }
|
|
575
|
+
@{ Name = 'update'; Description = 'Update Lattice rules' }
|
|
576
|
+
@{ Name = 'setup'; Description = 'Interactive setup' }
|
|
577
|
+
@{ Name = 'login'; Description = 'Authenticate with Lattice' }
|
|
578
|
+
@{ Name = 'logout'; Description = 'Remove credentials' }
|
|
579
|
+
@{ Name = 'whoami'; Description = 'Show login status' }
|
|
580
|
+
@{ Name = 'create'; Description = 'Create project from Architect' }
|
|
581
|
+
@{ Name = 'config'; Description = 'Configuration commands' }
|
|
582
|
+
@{ Name = 'rules'; Description = 'Rules commands' }
|
|
583
|
+
@{ Name = 'ci'; Description = 'Generate CI workflow' }
|
|
584
|
+
@{ Name = 'diff'; Description = 'Show changes since generation' }
|
|
585
|
+
@{ Name = 'security-scan'; Description = 'Scan for exposed secrets' }
|
|
586
|
+
@{ Name = 'scan'; Description = 'Scan for exposed secrets (alias)' }
|
|
587
|
+
@{ Name = 'completions'; Description = 'Generate shell completions' }
|
|
588
|
+
@{ Name = 'help'; Description = 'Show help' }
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
$commands | Where-Object { $_.Name -like "$wordToComplete*" } | ForEach-Object {
|
|
592
|
+
[System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Description)
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
Register-ArgumentCompleter -Native -CommandName lattice -ScriptBlock $scriptblock
|
|
597
|
+
Register-ArgumentCompleter -Native -CommandName latticeai -ScriptBlock $scriptblock
|
|
598
|
+
`;
|
|
599
|
+
}
|
|
600
|
+
// ============================================================================
|
|
601
|
+
// DETECT PROJECT TYPE
|
|
602
|
+
// ============================================================================
|
|
603
|
+
async function detectProjectType(cwd) {
|
|
604
|
+
const packageJsonPath = (0, path_1.join)(cwd, 'package.json');
|
|
605
|
+
try {
|
|
606
|
+
const packageContent = await fs_1.promises.readFile(packageJsonPath, 'utf-8');
|
|
607
|
+
const pkg = JSON.parse(packageContent);
|
|
608
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
609
|
+
// Check for Next.js
|
|
610
|
+
if (deps['next']) {
|
|
611
|
+
return {
|
|
612
|
+
projectType: 'nextjs',
|
|
613
|
+
confidence: 'high',
|
|
614
|
+
reason: 'Found "next" in dependencies',
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
// Check for Expo
|
|
618
|
+
if (deps['expo']) {
|
|
619
|
+
return {
|
|
620
|
+
projectType: 'expo-eas',
|
|
621
|
+
confidence: 'high',
|
|
622
|
+
reason: 'Found "expo" in dependencies',
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
// Check for React Native without Expo
|
|
626
|
+
if (deps['react-native'] && !deps['expo']) {
|
|
627
|
+
return {
|
|
628
|
+
projectType: 'expo-eas',
|
|
629
|
+
confidence: 'medium',
|
|
630
|
+
reason: 'Found "react-native" (assuming Expo)',
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
// Check for other React frameworks
|
|
634
|
+
if (deps['react']) {
|
|
635
|
+
return {
|
|
636
|
+
projectType: 'nextjs',
|
|
637
|
+
confidence: 'low',
|
|
638
|
+
reason: 'Found "react" but no framework - assuming Next.js',
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
projectType: null,
|
|
643
|
+
confidence: 'low',
|
|
644
|
+
reason: 'Could not detect project type from dependencies',
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
return {
|
|
649
|
+
projectType: null,
|
|
650
|
+
confidence: 'low',
|
|
651
|
+
reason: 'No package.json found',
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
async function doctorFix(options = {}) {
|
|
656
|
+
const cwd = options.cwd || process.cwd();
|
|
657
|
+
const results = [];
|
|
658
|
+
// 1. Check for missing required scripts in package.json
|
|
659
|
+
const packageJsonPath = (0, path_1.join)(cwd, 'package.json');
|
|
660
|
+
try {
|
|
661
|
+
const packageContent = await fs_1.promises.readFile(packageJsonPath, 'utf-8');
|
|
662
|
+
const pkg = JSON.parse(packageContent);
|
|
663
|
+
const scripts = pkg.scripts || {};
|
|
664
|
+
const requiredScripts = ['lint', 'typecheck', 'test', 'build'];
|
|
665
|
+
let modified = false;
|
|
666
|
+
for (const script of requiredScripts) {
|
|
667
|
+
if (!scripts[script]) {
|
|
668
|
+
const defaultScript = getDefaultScript(script, pkg);
|
|
669
|
+
if (!options.dryRun) {
|
|
670
|
+
pkg.scripts = pkg.scripts || {};
|
|
671
|
+
pkg.scripts[script] = defaultScript;
|
|
672
|
+
modified = true;
|
|
673
|
+
}
|
|
674
|
+
results.push({
|
|
675
|
+
issue: `Missing script: ${script}`,
|
|
676
|
+
fixed: !options.dryRun,
|
|
677
|
+
action: `Added "${script}": "${defaultScript}"`,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (modified && !options.dryRun) {
|
|
682
|
+
await fs_1.promises.writeFile(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
catch {
|
|
686
|
+
results.push({
|
|
687
|
+
issue: 'No package.json found',
|
|
688
|
+
fixed: false,
|
|
689
|
+
action: 'Create package.json manually',
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
// 2. Check for .gitignore
|
|
693
|
+
const gitignorePath = (0, path_1.join)(cwd, '.gitignore');
|
|
694
|
+
try {
|
|
695
|
+
await fs_1.promises.access(gitignorePath);
|
|
696
|
+
}
|
|
697
|
+
catch {
|
|
698
|
+
if (!options.dryRun) {
|
|
699
|
+
const defaultGitignore = `node_modules/
|
|
700
|
+
.next/
|
|
701
|
+
dist/
|
|
702
|
+
.env.local
|
|
703
|
+
.env*.local
|
|
704
|
+
*.log
|
|
705
|
+
.DS_Store
|
|
706
|
+
`;
|
|
707
|
+
await fs_1.promises.writeFile(gitignorePath, defaultGitignore);
|
|
708
|
+
}
|
|
709
|
+
results.push({
|
|
710
|
+
issue: 'Missing .gitignore',
|
|
711
|
+
fixed: !options.dryRun,
|
|
712
|
+
action: 'Created default .gitignore',
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
// 3. Check for tsconfig.json if TypeScript is installed
|
|
716
|
+
try {
|
|
717
|
+
const packageContent = await fs_1.promises.readFile(packageJsonPath, 'utf-8');
|
|
718
|
+
const pkg = JSON.parse(packageContent);
|
|
719
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
720
|
+
if (deps['typescript']) {
|
|
721
|
+
const tsconfigPath = (0, path_1.join)(cwd, 'tsconfig.json');
|
|
722
|
+
try {
|
|
723
|
+
await fs_1.promises.access(tsconfigPath);
|
|
724
|
+
}
|
|
725
|
+
catch {
|
|
726
|
+
results.push({
|
|
727
|
+
issue: 'TypeScript installed but no tsconfig.json',
|
|
728
|
+
fixed: false,
|
|
729
|
+
action: 'Run: npx tsc --init',
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
catch {
|
|
735
|
+
// No package.json
|
|
736
|
+
}
|
|
737
|
+
return results;
|
|
738
|
+
}
|
|
739
|
+
function getDefaultScript(name, pkg) {
|
|
740
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
741
|
+
const isNext = !!deps['next'];
|
|
742
|
+
const isExpo = !!deps['expo'];
|
|
743
|
+
switch (name) {
|
|
744
|
+
case 'lint':
|
|
745
|
+
return 'eslint . --ext .ts,.tsx,.js,.jsx';
|
|
746
|
+
case 'typecheck':
|
|
747
|
+
return 'tsc --noEmit';
|
|
748
|
+
case 'test':
|
|
749
|
+
if (deps['jest'])
|
|
750
|
+
return 'jest --passWithNoTests';
|
|
751
|
+
if (deps['vitest'])
|
|
752
|
+
return 'vitest run';
|
|
753
|
+
return 'echo "No tests configured" && exit 0';
|
|
754
|
+
case 'build':
|
|
755
|
+
if (isNext)
|
|
756
|
+
return 'next build';
|
|
757
|
+
if (isExpo)
|
|
758
|
+
return 'expo prebuild';
|
|
759
|
+
return 'tsc';
|
|
760
|
+
default:
|
|
761
|
+
return `echo "TODO: add ${name} script"`;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
// Common secret patterns
|
|
765
|
+
const SECRET_PATTERNS = [
|
|
766
|
+
// Generic API Keys - catch various naming patterns
|
|
767
|
+
{
|
|
768
|
+
name: 'API Key',
|
|
769
|
+
pattern: /(?:api[_-]?key|apikey|api[_-]?token|api[_-]?secret)\s*[:=]\s*['"]([a-zA-Z0-9_\-+/=]{20,})['"]/gi,
|
|
770
|
+
severity: 'critical',
|
|
771
|
+
envVarName: () => 'API_KEY',
|
|
772
|
+
},
|
|
773
|
+
// API Key in camelCase/PascalCase
|
|
774
|
+
{
|
|
775
|
+
name: 'API Key',
|
|
776
|
+
pattern: /(?:apiKey|apiToken|apiSecret|ApiKey|ApiToken|ApiSecret)\s*[:=]\s*['"]([a-zA-Z0-9_\-+/=]{20,})['"]/gi,
|
|
777
|
+
severity: 'critical',
|
|
778
|
+
envVarName: () => 'API_KEY',
|
|
779
|
+
},
|
|
780
|
+
// Environment variable style API keys (ALL_CAPS format)
|
|
781
|
+
{
|
|
782
|
+
name: 'API Key (Env Var Format)',
|
|
783
|
+
pattern: /([A-Z][A-Z0-9_]*API[_-]?KEY|[A-Z][A-Z0-9_]*API[_-]?TOKEN)\s*[:=]\s*['"]([a-zA-Z0-9_\-+/=]{20,})['"]/g,
|
|
784
|
+
severity: 'critical',
|
|
785
|
+
envVarName: (match) => {
|
|
786
|
+
// match[1] is the variable name, match[2] is the value
|
|
787
|
+
if (match && match[1]) {
|
|
788
|
+
return match[1];
|
|
789
|
+
}
|
|
790
|
+
return 'API_KEY';
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
// Generic secret/token patterns (catch-all for unknown services)
|
|
794
|
+
{
|
|
795
|
+
name: 'Secret/Token',
|
|
796
|
+
pattern: /(?:secret|token|auth[_-]?token|access[_-]?token)\s*[:=]\s*['"]([a-zA-Z0-9_\-+/=]{32,})['"]/gi,
|
|
797
|
+
severity: 'high',
|
|
798
|
+
envVarName: (match) => {
|
|
799
|
+
const varName = match[0].match(/(\w+)\s*[:=]/)?.[1]?.toUpperCase().replace(/[_-]/g, '_') || 'SECRET';
|
|
800
|
+
return varName;
|
|
801
|
+
},
|
|
802
|
+
},
|
|
803
|
+
// AI/ML Service API Keys
|
|
804
|
+
{
|
|
805
|
+
name: 'Anthropic API Key',
|
|
806
|
+
pattern: /(?:anthropic[_-]?api[_-]?key|ANTHROPIC_API_KEY)\s*[:=]\s*['"](sk-ant-[a-zA-Z0-9\-_]{95,})['"]/gi,
|
|
807
|
+
severity: 'critical',
|
|
808
|
+
envVarName: () => 'ANTHROPIC_API_KEY',
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
name: 'OpenAI API Key',
|
|
812
|
+
pattern: /(?:openai[_-]?api[_-]?key|OPENAI_API_KEY)\s*[:=]\s*['"](sk-[a-zA-Z0-9]{32,})['"]/gi,
|
|
813
|
+
severity: 'critical',
|
|
814
|
+
envVarName: () => 'OPENAI_API_KEY',
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
name: 'Replicate API Token',
|
|
818
|
+
pattern: /(?:replicate[_-]?api[_-]?token|REPLICATE_API_TOKEN)\s*[:=]\s*['"](r8_[a-zA-Z0-9\-_]{37,})['"]/gi,
|
|
819
|
+
severity: 'critical',
|
|
820
|
+
envVarName: () => 'REPLICATE_API_TOKEN',
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
name: 'Google Gemini API Key',
|
|
824
|
+
pattern: /(?:google[_-]?gemini[_-]?api[_-]?key|GOOGLE_GEMINI_API_KEY|gemini[_-]?api[_-]?key)\s*[:=]\s*['"](AIza[0-9A-Za-z\-_]{35})['"]/gi,
|
|
825
|
+
severity: 'critical',
|
|
826
|
+
envVarName: () => 'GOOGLE_GEMINI_API_KEY',
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
name: 'Google API Key',
|
|
830
|
+
pattern: /(?:google[_-]?api[_-]?key|GOOGLE_API_KEY)\s*[:=]\s*['"](AIza[0-9A-Za-z\-_]{35})['"]/gi,
|
|
831
|
+
severity: 'critical',
|
|
832
|
+
envVarName: () => 'GOOGLE_API_KEY',
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
name: 'Cohere API Key',
|
|
836
|
+
pattern: /(?:cohere[_-]?api[_-]?key|COHERE_API_KEY)\s*[:=]\s*['"]([a-zA-Z0-9\-_]{40,})['"]/gi,
|
|
837
|
+
severity: 'critical',
|
|
838
|
+
envVarName: () => 'COHERE_API_KEY',
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
name: 'Hugging Face Token',
|
|
842
|
+
pattern: /(?:huggingface[_-]?token|hf[_-]?token|HUGGINGFACE_TOKEN)\s*[:=]\s*['"](hf_[a-zA-Z0-9\-_]{37,})['"]/gi,
|
|
843
|
+
severity: 'critical',
|
|
844
|
+
envVarName: () => 'HUGGINGFACE_TOKEN',
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
name: 'Together AI API Key',
|
|
848
|
+
pattern: /(?:together[_-]?ai[_-]?api[_-]?key|TOGETHER_API_KEY)\s*[:=]\s*['"]([a-zA-Z0-9\-_]{40,})['"]/gi,
|
|
849
|
+
severity: 'critical',
|
|
850
|
+
envVarName: () => 'TOGETHER_API_KEY',
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
name: 'Groq API Key',
|
|
854
|
+
pattern: /(?:groq[_-]?api[_-]?key|GROQ_API_KEY)\s*[:=]\s*['"](gsk_[a-zA-Z0-9\-_]{48,})['"]/gi,
|
|
855
|
+
severity: 'critical',
|
|
856
|
+
envVarName: () => 'GROQ_API_KEY',
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
name: 'Perplexity API Key',
|
|
860
|
+
pattern: /(?:perplexity[_-]?api[_-]?key|PERPLEXITY_API_KEY)\s*[:=]\s*['"](pplx-[a-zA-Z0-9\-_]{40,})['"]/gi,
|
|
861
|
+
severity: 'critical',
|
|
862
|
+
envVarName: () => 'PERPLEXITY_API_KEY',
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
name: 'AWS Access Key',
|
|
866
|
+
pattern: /(?:aws[_-]?access[_-]?key[_-]?id|aws_access_key)\s*[:=]\s*['"](AKIA[0-9A-Z]{16})['"]/gi,
|
|
867
|
+
severity: 'critical',
|
|
868
|
+
envVarName: () => 'AWS_ACCESS_KEY_ID',
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
name: 'AWS Secret Key',
|
|
872
|
+
pattern: /(?:aws[_-]?secret[_-]?access[_-]?key|aws_secret_key)\s*[:=]\s*['"]([a-zA-Z0-9/+=]{40})['"]/gi,
|
|
873
|
+
severity: 'critical',
|
|
874
|
+
envVarName: () => 'AWS_SECRET_ACCESS_KEY',
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
name: 'GitHub Token',
|
|
878
|
+
pattern: /(?:github[_-]?token|gh_token|ghp_)\s*[:=]\s*['"](ghp_[a-zA-Z0-9]{36})['"]/gi,
|
|
879
|
+
severity: 'critical',
|
|
880
|
+
envVarName: () => 'GITHUB_TOKEN',
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
name: 'Stripe Key',
|
|
884
|
+
pattern: /(?:stripe[_-]?(?:secret|private)[_-]?key|sk_live_)\s*[:=]\s*['"](sk_(live|test)_[a-zA-Z0-9]{24,})['"]/gi,
|
|
885
|
+
severity: 'critical',
|
|
886
|
+
envVarName: () => 'STRIPE_SECRET_KEY',
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
name: 'Stripe Publishable Key',
|
|
890
|
+
pattern: /(?:stripe[_-]?publishable[_-]?key|pk_live_)\s*[:=]\s*['"](pk_(live|test)_[a-zA-Z0-9]{24,})['"]/gi,
|
|
891
|
+
severity: 'high',
|
|
892
|
+
envVarName: () => 'STRIPE_PUBLISHABLE_KEY',
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
name: 'JWT Secret',
|
|
896
|
+
pattern: /(?:jwt[_-]?secret|jwt_secret)\s*[:=]\s*['"]([a-zA-Z0-9_\-]{32,})['"]/gi,
|
|
897
|
+
severity: 'critical',
|
|
898
|
+
envVarName: () => 'JWT_SECRET',
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
name: 'Database Password',
|
|
902
|
+
pattern: /(?:db[_-]?password|database[_-]?password|password)\s*[:=]\s*['"]([^'"]{8,})['"]/gi,
|
|
903
|
+
severity: 'critical',
|
|
904
|
+
envVarName: () => 'DB_PASSWORD',
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
name: 'MongoDB Connection String',
|
|
908
|
+
pattern: /(?:mongodb[_-]?uri|mongo[_-]?connection)\s*[:=]\s*['"](mongodb(?:\+srv)?:\/\/[^'"]+)['"]/gi,
|
|
909
|
+
severity: 'critical',
|
|
910
|
+
envVarName: () => 'MONGODB_URI',
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
name: 'PostgreSQL Connection String',
|
|
914
|
+
pattern: /(?:postgres[_-]?uri|postgresql[_-]?connection|DATABASE_URL)\s*[:=]\s*['"](postgres(?:ql)?:\/\/[^'"]+)['"]/gi,
|
|
915
|
+
severity: 'critical',
|
|
916
|
+
envVarName: () => 'DATABASE_URL',
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
name: 'Private Key',
|
|
920
|
+
pattern: /(?:private[_-]?key|-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----)/gi,
|
|
921
|
+
severity: 'critical',
|
|
922
|
+
envVarName: () => 'PRIVATE_KEY',
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
name: 'OAuth Client Secret',
|
|
926
|
+
pattern: /(?:oauth[_-]?client[_-]?secret|client[_-]?secret)\s*[:=]\s*['"]([a-zA-Z0-9_\-]{20,})['"]/gi,
|
|
927
|
+
severity: 'critical',
|
|
928
|
+
envVarName: () => 'OAUTH_CLIENT_SECRET',
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
name: 'SendGrid API Key',
|
|
932
|
+
pattern: /(?:sendgrid[_-]?api[_-]?key)\s*[:=]\s*['"](SG\.[a-zA-Z0-9_\-]{22}\.[a-zA-Z0-9_\-]{43})['"]/gi,
|
|
933
|
+
severity: 'critical',
|
|
934
|
+
envVarName: () => 'SENDGRID_API_KEY',
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
name: 'Twilio Auth Token',
|
|
938
|
+
pattern: /(?:twilio[_-]?auth[_-]?token)\s*[:=]\s*['"]([a-zA-Z0-9]{32})['"]/gi,
|
|
939
|
+
severity: 'critical',
|
|
940
|
+
envVarName: () => 'TWILIO_AUTH_TOKEN',
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
name: 'Slack Webhook URL',
|
|
944
|
+
pattern: /(?:slack[_-]?webhook)\s*[:=]\s*['"](https:\/\/hooks\.slack\.com\/services\/[^'"]+)['"]/gi,
|
|
945
|
+
severity: 'high',
|
|
946
|
+
envVarName: () => 'SLACK_WEBHOOK_URL',
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
name: 'Firebase Service Account',
|
|
950
|
+
pattern: /(?:firebase[_-]?service[_-]?account|GOOGLE_APPLICATION_CREDENTIALS)\s*[:=]\s*['"]([^'"]+\.json)['"]/gi,
|
|
951
|
+
severity: 'critical',
|
|
952
|
+
envVarName: () => 'FIREBASE_SERVICE_ACCOUNT',
|
|
953
|
+
},
|
|
954
|
+
// Cloud Services
|
|
955
|
+
{
|
|
956
|
+
name: 'Azure Connection String',
|
|
957
|
+
pattern: /(?:azure[_-]?connection[_-]?string|AZURE_STORAGE_CONNECTION_STRING)\s*[:=]\s*['"](DefaultEndpointsProtocol=https[^'"]+)['"]/gi,
|
|
958
|
+
severity: 'critical',
|
|
959
|
+
envVarName: () => 'AZURE_STORAGE_CONNECTION_STRING',
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
name: 'GCP Service Account Key',
|
|
963
|
+
pattern: /(?:gcp[_-]?service[_-]?account|GOOGLE_APPLICATION_CREDENTIALS|GCP_SERVICE_ACCOUNT)\s*[:=]\s*['"]([^'"]+\.json)['"]/gi,
|
|
964
|
+
severity: 'critical',
|
|
965
|
+
envVarName: () => 'GCP_SERVICE_ACCOUNT_KEY',
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
name: 'Vercel Token',
|
|
969
|
+
pattern: /(?:vercel[_-]?token|VERCEL_TOKEN)\s*[:=]\s*['"]([a-zA-Z0-9_\-]{40,})['"]/gi,
|
|
970
|
+
severity: 'critical',
|
|
971
|
+
envVarName: () => 'VERCEL_TOKEN',
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
name: 'Netlify Token',
|
|
975
|
+
pattern: /(?:netlify[_-]?token|NETLIFY_TOKEN)\s*[:=]\s*['"]([a-zA-Z0-9_\-]{40,})['"]/gi,
|
|
976
|
+
severity: 'critical',
|
|
977
|
+
envVarName: () => 'NETLIFY_TOKEN',
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
name: 'Heroku API Key',
|
|
981
|
+
pattern: /(?:heroku[_-]?api[_-]?key|HEROKU_API_KEY)\s*[:=]\s*['"]([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})['"]/gi,
|
|
982
|
+
severity: 'critical',
|
|
983
|
+
envVarName: () => 'HEROKU_API_KEY',
|
|
984
|
+
},
|
|
985
|
+
// Additional Database Services
|
|
986
|
+
{
|
|
987
|
+
name: 'Redis Connection String',
|
|
988
|
+
pattern: /(?:redis[_-]?url|REDIS_URL|redis[_-]?connection)\s*[:=]\s*['"](redis[s]?:\/\/[^'"]+)['"]/gi,
|
|
989
|
+
severity: 'critical',
|
|
990
|
+
envVarName: () => 'REDIS_URL',
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
name: 'MySQL Connection String',
|
|
994
|
+
pattern: /(?:mysql[_-]?uri|MYSQL_URL|mysql[_-]?connection)\s*[:=]\s*['"](mysql:\/\/[^'"]+)['"]/gi,
|
|
995
|
+
severity: 'critical',
|
|
996
|
+
envVarName: () => 'MYSQL_URL',
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
name: 'Supabase Anon Key',
|
|
1000
|
+
pattern: /(?:supabase[_-]?anon[_-]?key|SUPABASE_ANON_KEY)\s*[:=]\s*['"](eyJ[a-zA-Z0-9_\-]{100,})['"]/gi,
|
|
1001
|
+
severity: 'critical',
|
|
1002
|
+
envVarName: () => 'SUPABASE_ANON_KEY',
|
|
1003
|
+
},
|
|
1004
|
+
{
|
|
1005
|
+
name: 'Supabase Service Role Key',
|
|
1006
|
+
pattern: /(?:supabase[_-]?service[_-]?role[_-]?key|SUPABASE_SERVICE_ROLE_KEY)\s*[:=]\s*['"](eyJ[a-zA-Z0-9_\-]{100,})['"]/gi,
|
|
1007
|
+
severity: 'critical',
|
|
1008
|
+
envVarName: () => 'SUPABASE_SERVICE_ROLE_KEY',
|
|
1009
|
+
},
|
|
1010
|
+
// Authentication Services
|
|
1011
|
+
{
|
|
1012
|
+
name: 'Auth0 Client Secret',
|
|
1013
|
+
pattern: /(?:auth0[_-]?client[_-]?secret|AUTH0_CLIENT_SECRET)\s*[:=]\s*['"]([a-zA-Z0-9_\-]{32,})['"]/gi,
|
|
1014
|
+
severity: 'critical',
|
|
1015
|
+
envVarName: () => 'AUTH0_CLIENT_SECRET',
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
name: 'Clerk Secret Key',
|
|
1019
|
+
pattern: /(?:clerk[_-]?secret[_-]?key|CLERK_SECRET_KEY)\s*[:=]\s*['"](sk_[a-z]_[a-zA-Z0-9_\-]{50,})['"]/gi,
|
|
1020
|
+
severity: 'critical',
|
|
1021
|
+
envVarName: () => 'CLERK_SECRET_KEY',
|
|
1022
|
+
},
|
|
1023
|
+
// Email Services
|
|
1024
|
+
{
|
|
1025
|
+
name: 'Mailgun API Key',
|
|
1026
|
+
pattern: /(?:mailgun[_-]?api[_-]?key|MAILGUN_API_KEY)\s*[:=]\s*['"](key-[a-f0-9]{32})['"]/gi,
|
|
1027
|
+
severity: 'critical',
|
|
1028
|
+
envVarName: () => 'MAILGUN_API_KEY',
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
name: 'Postmark API Key',
|
|
1032
|
+
pattern: /(?:postmark[_-]?api[_-]?key|POSTMARK_API_KEY)\s*[:=]\s*['"]([a-zA-Z0-9_\-]{40,})['"]/gi,
|
|
1033
|
+
severity: 'critical',
|
|
1034
|
+
envVarName: () => 'POSTMARK_API_KEY',
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
name: 'Resend API Key',
|
|
1038
|
+
pattern: /(?:resend[_-]?api[_-]?key|RESEND_API_KEY)\s*[:=]\s*['"](re_[a-zA-Z0-9_\-]{40,})['"]/gi,
|
|
1039
|
+
severity: 'critical',
|
|
1040
|
+
envVarName: () => 'RESEND_API_KEY',
|
|
1041
|
+
},
|
|
1042
|
+
// Monitoring & Error Tracking
|
|
1043
|
+
{
|
|
1044
|
+
name: 'Sentry DSN',
|
|
1045
|
+
pattern: /(?:sentry[_-]?dsn|SENTRY_DSN)\s*[:=]\s*['"](https:\/\/[a-f0-9]{32}@[^'"]+\/[0-9]+)['"]/gi,
|
|
1046
|
+
severity: 'high',
|
|
1047
|
+
envVarName: () => 'SENTRY_DSN',
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
name: 'Datadog API Key',
|
|
1051
|
+
pattern: /(?:datadog[_-]?api[_-]?key|DD_API_KEY)\s*[:=]\s*['"]([a-f0-9]{32})['"]/gi,
|
|
1052
|
+
severity: 'critical',
|
|
1053
|
+
envVarName: () => 'DD_API_KEY',
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
name: 'New Relic License Key',
|
|
1057
|
+
pattern: /(?:newrelic[_-]?license[_-]?key|NEW_RELIC_LICENSE_KEY)\s*[:=]\s*['"]([a-f0-9]{40})['"]/gi,
|
|
1058
|
+
severity: 'critical',
|
|
1059
|
+
envVarName: () => 'NEW_RELIC_LICENSE_KEY',
|
|
1060
|
+
},
|
|
1061
|
+
// Payment Services
|
|
1062
|
+
{
|
|
1063
|
+
name: 'PayPal Client Secret',
|
|
1064
|
+
pattern: /(?:paypal[_-]?client[_-]?secret|PAYPAL_CLIENT_SECRET)\s*[:=]\s*['"]([a-zA-Z0-9_\-]{40,})['"]/gi,
|
|
1065
|
+
severity: 'critical',
|
|
1066
|
+
envVarName: () => 'PAYPAL_CLIENT_SECRET',
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
name: 'Square Access Token',
|
|
1070
|
+
pattern: /(?:square[_-]?access[_-]?token|SQUARE_ACCESS_TOKEN)\s*[:=]\s*['"](EAAA[a-zA-Z0-9_\-]{40,})['"]/gi,
|
|
1071
|
+
severity: 'critical',
|
|
1072
|
+
envVarName: () => 'SQUARE_ACCESS_TOKEN',
|
|
1073
|
+
},
|
|
1074
|
+
// Communication Services
|
|
1075
|
+
{
|
|
1076
|
+
name: 'Discord Bot Token',
|
|
1077
|
+
pattern: /(?:discord[_-]?bot[_-]?token|DISCORD_BOT_TOKEN)\s*[:=]\s*['"]([MN][A-Za-z\d]{23}\.[\w-]{6}\.[\w-]{27})['"]/gi,
|
|
1078
|
+
severity: 'critical',
|
|
1079
|
+
envVarName: () => 'DISCORD_BOT_TOKEN',
|
|
1080
|
+
},
|
|
1081
|
+
{
|
|
1082
|
+
name: 'Discord Webhook URL',
|
|
1083
|
+
pattern: /(?:discord[_-]?webhook|DISCORD_WEBHOOK)\s*[:=]\s*['"](https:\/\/discord\.com\/api\/webhooks\/[^'"]+)['"]/gi,
|
|
1084
|
+
severity: 'high',
|
|
1085
|
+
envVarName: () => 'DISCORD_WEBHOOK_URL',
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
name: 'Telegram Bot Token',
|
|
1089
|
+
pattern: /(?:telegram[_-]?bot[_-]?token|TELEGRAM_BOT_TOKEN)\s*[:=]\s*['"]([0-9]{8,10}:[a-zA-Z0-9_-]{35})['"]/gi,
|
|
1090
|
+
severity: 'critical',
|
|
1091
|
+
envVarName: () => 'TELEGRAM_BOT_TOKEN',
|
|
1092
|
+
},
|
|
1093
|
+
// Search & CDN Services
|
|
1094
|
+
{
|
|
1095
|
+
name: 'Algolia API Key',
|
|
1096
|
+
pattern: /(?:algolia[_-]?api[_-]?key|ALGOLIA_API_KEY)\s*[:=]\s*['"]([a-f0-9]{32})['"]/gi,
|
|
1097
|
+
severity: 'critical',
|
|
1098
|
+
envVarName: () => 'ALGOLIA_API_KEY',
|
|
1099
|
+
},
|
|
1100
|
+
{
|
|
1101
|
+
name: 'Cloudinary URL',
|
|
1102
|
+
pattern: /(?:cloudinary[_-]?url|CLOUDINARY_URL)\s*[:=]\s*['"](cloudinary:\/\/[^'"]+)['"]/gi,
|
|
1103
|
+
severity: 'critical',
|
|
1104
|
+
envVarName: () => 'CLOUDINARY_URL',
|
|
1105
|
+
},
|
|
1106
|
+
// CI/CD Services
|
|
1107
|
+
{
|
|
1108
|
+
name: 'CircleCI Token',
|
|
1109
|
+
pattern: /(?:circleci[_-]?token|CIRCLE_TOKEN)\s*[:=]\s*['"]([a-f0-9]{40})['"]/gi,
|
|
1110
|
+
severity: 'critical',
|
|
1111
|
+
envVarName: () => 'CIRCLE_TOKEN',
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
name: 'Travis CI Token',
|
|
1115
|
+
pattern: /(?:travis[_-]?ci[_-]?token|TRAVIS_TOKEN)\s*[:=]\s*['"]([a-zA-Z0-9_\-]{22})['"]/gi,
|
|
1116
|
+
severity: 'critical',
|
|
1117
|
+
envVarName: () => 'TRAVIS_TOKEN',
|
|
1118
|
+
},
|
|
1119
|
+
// Analytics & Mobile Services
|
|
1120
|
+
{
|
|
1121
|
+
name: 'RevenueCat API Key',
|
|
1122
|
+
pattern: /(?:revenuecat[_-]?api[_-]?key|revenue[_-]?cat[_-]?key|REVENUECAT_API_KEY)\s*[:=]\s*['"](test_[a-zA-Z0-9_\-]{30,}|rcat_[a-zA-Z0-9_\-]{30,})['"]/gi,
|
|
1123
|
+
severity: 'critical',
|
|
1124
|
+
envVarName: () => 'REVENUECAT_API_KEY',
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
name: 'Amplitude API Key',
|
|
1128
|
+
pattern: /(?:amplitude[_-]?api[_-]?key|AMPLITUDE_API_KEY)\s*[:=]\s*['"]([a-f0-9]{32})['"]/gi,
|
|
1129
|
+
severity: 'critical',
|
|
1130
|
+
envVarName: () => 'AMPLITUDE_API_KEY',
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
name: 'Mixpanel Token',
|
|
1134
|
+
pattern: /(?:mixpanel[_-]?token|MIXPANEL_TOKEN)\s*[:=]\s*['"]([a-f0-9]{32})['"]/gi,
|
|
1135
|
+
severity: 'critical',
|
|
1136
|
+
envVarName: () => 'MIXPANEL_TOKEN',
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
name: 'Segment Write Key',
|
|
1140
|
+
pattern: /(?:segment[_-]?write[_-]?key|SEGMENT_WRITE_KEY)\s*[:=]\s*['"]([a-zA-Z0-9_\-]{20,})['"]/gi,
|
|
1141
|
+
severity: 'critical',
|
|
1142
|
+
envVarName: () => 'SEGMENT_WRITE_KEY',
|
|
1143
|
+
},
|
|
1144
|
+
// Email addresses in config files (especially sensitive ones)
|
|
1145
|
+
{
|
|
1146
|
+
name: 'Email Address in Config',
|
|
1147
|
+
pattern: /(?:email|apple[_-]?id|admin[_-]?email|contact[_-]?email|appleId)\s*[:=]\s*['"]([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})['"]/gi,
|
|
1148
|
+
severity: 'medium',
|
|
1149
|
+
envVarName: (match) => {
|
|
1150
|
+
const varName = match[0].match(/(\w+)\s*[:=]/)?.[1]?.toUpperCase().replace(/[_-]/g, '_') || 'EMAIL';
|
|
1151
|
+
return varName;
|
|
1152
|
+
},
|
|
1153
|
+
},
|
|
1154
|
+
// Catch emails in JSON config files (eas.json, app.config.js, etc.) - only in config files
|
|
1155
|
+
// Only trigger if the field name suggests it's sensitive (appleId, email, adminEmail, etc.)
|
|
1156
|
+
{
|
|
1157
|
+
name: 'Email in JSON Config',
|
|
1158
|
+
pattern: /(?:appleId|apple[_-]?id|email|admin[_-]?email|contact[_-]?email|support[_-]?email)\s*[:=]\s*['"]([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})['"]/gi,
|
|
1159
|
+
severity: 'medium',
|
|
1160
|
+
envVarName: () => 'EMAIL',
|
|
1161
|
+
},
|
|
1162
|
+
// Generic hex strings that look like API keys (32+ chars) - catch any hex string that looks like a key
|
|
1163
|
+
{
|
|
1164
|
+
name: 'Hex API Key',
|
|
1165
|
+
pattern: /(?:key|api[_-]?key|token|amplitude|analytics)\s*[:=]\s*['"]([a-f0-9]{32,})['"]/gi,
|
|
1166
|
+
severity: 'high',
|
|
1167
|
+
envVarName: (match) => {
|
|
1168
|
+
const varName = match[0].match(/(\w+)\s*[:=]/)?.[1]?.toUpperCase().replace(/[_-]/g, '_') || 'API_KEY';
|
|
1169
|
+
return varName;
|
|
1170
|
+
},
|
|
1171
|
+
},
|
|
1172
|
+
// Catch any hex string in assignment (very generic, catches Amplitude and similar)
|
|
1173
|
+
// Only trigger if it's in a context that suggests it's a key (near "key", "api", "token", etc.)
|
|
1174
|
+
{
|
|
1175
|
+
name: 'Hex String (Potential Key)',
|
|
1176
|
+
pattern: /(?:key|api|token|secret|amplitude|analytics|mixpanel|amplitudeKey|apiKey).*[:=]\s*['"]([a-f0-9]{32})['"]/gi,
|
|
1177
|
+
severity: 'high',
|
|
1178
|
+
envVarName: () => 'API_KEY',
|
|
1179
|
+
},
|
|
1180
|
+
// Very generic: catch any 32-char hex string that's assigned (Amplitude, Mixpanel, etc.)
|
|
1181
|
+
// This is more aggressive but necessary to catch keys with generic variable names
|
|
1182
|
+
{
|
|
1183
|
+
name: 'Hex API Key (Generic)',
|
|
1184
|
+
pattern: /(?:const|let|var|export\s+const)\s+\w*(?:key|api|token|id|secret)\w*\s*[:=]\s*['"]([a-f0-9]{32})['"]/gi,
|
|
1185
|
+
severity: 'high',
|
|
1186
|
+
envVarName: () => 'API_KEY',
|
|
1187
|
+
},
|
|
1188
|
+
// Fallback patterns (catches || 'secret' or ?? 'secret')
|
|
1189
|
+
{
|
|
1190
|
+
name: 'Hardcoded Secret Fallback',
|
|
1191
|
+
pattern: /\|\|\s*['"]([a-zA-Z0-9_\-+/=]{20,})['"]|\?\?\s*['"]([a-zA-Z0-9_\-+/=]{20,})['"]/gi,
|
|
1192
|
+
severity: 'high',
|
|
1193
|
+
envVarName: () => 'SECRET',
|
|
1194
|
+
},
|
|
1195
|
+
// Test keys (often start with test_)
|
|
1196
|
+
{
|
|
1197
|
+
name: 'Test API Key',
|
|
1198
|
+
pattern: /(?:test[_-]?key|test[_-]?api[_-]?key|TEST_KEY)\s*[:=]\s*['"](test_[a-zA-Z0-9_\-]{20,})['"]/gi,
|
|
1199
|
+
severity: 'critical',
|
|
1200
|
+
envVarName: () => 'TEST_API_KEY',
|
|
1201
|
+
},
|
|
1202
|
+
// Generic test_ prefixed keys (catches RevenueCat and others) - very generic to catch any test_ key
|
|
1203
|
+
{
|
|
1204
|
+
name: 'Test Key (test_ prefix)',
|
|
1205
|
+
pattern: /[:=]\s*['"](test_[a-zA-Z0-9_\-]{25,})['"]/gi,
|
|
1206
|
+
severity: 'critical',
|
|
1207
|
+
envVarName: () => 'TEST_API_KEY',
|
|
1208
|
+
},
|
|
1209
|
+
// RevenueCat specific (catches rcat_ prefix too)
|
|
1210
|
+
{
|
|
1211
|
+
name: 'RevenueCat Key',
|
|
1212
|
+
pattern: /[:=]\s*['"](rcat_[a-zA-Z0-9_\-]{25,})['"]/gi,
|
|
1213
|
+
severity: 'critical',
|
|
1214
|
+
envVarName: () => 'REVENUECAT_API_KEY',
|
|
1215
|
+
},
|
|
1216
|
+
// Ultra-generic patterns - catch secrets regardless of variable naming
|
|
1217
|
+
// These are aggressive but necessary to catch all secrets
|
|
1218
|
+
// Any string that looks like an API key (20+ alphanumeric chars) in assignment
|
|
1219
|
+
{
|
|
1220
|
+
name: 'Potential API Key (Generic)',
|
|
1221
|
+
pattern: /[:=]\s*['"]([a-zA-Z0-9_\-+/=]{32,})['"]/g,
|
|
1222
|
+
severity: 'medium',
|
|
1223
|
+
envVarName: () => 'API_KEY',
|
|
1224
|
+
},
|
|
1225
|
+
// Any hex string 32+ chars (common for API keys)
|
|
1226
|
+
{
|
|
1227
|
+
name: 'Potential Hex Key',
|
|
1228
|
+
pattern: /[:=]\s*['"]([a-f0-9]{32,})['"]/gi,
|
|
1229
|
+
severity: 'medium',
|
|
1230
|
+
envVarName: () => 'API_KEY',
|
|
1231
|
+
},
|
|
1232
|
+
// Base64-like strings (40+ chars, common for tokens)
|
|
1233
|
+
{
|
|
1234
|
+
name: 'Potential Base64 Token',
|
|
1235
|
+
pattern: /[:=]\s*['"]([A-Za-z0-9+/=]{40,})['"]/g,
|
|
1236
|
+
severity: 'medium',
|
|
1237
|
+
envVarName: () => 'TOKEN',
|
|
1238
|
+
},
|
|
1239
|
+
// Any string starting with common prefixes
|
|
1240
|
+
{
|
|
1241
|
+
name: 'Potential Key (Common Prefix)',
|
|
1242
|
+
pattern: /[:=]\s*['"](sk[_-][a-zA-Z0-9_\-]{20,}|pk[_-][a-zA-Z0-9_\-]{20,}|rk[_-][a-zA-Z0-9_\-]{20,})['"]/gi,
|
|
1243
|
+
severity: 'high',
|
|
1244
|
+
envVarName: () => 'API_KEY',
|
|
1245
|
+
},
|
|
1246
|
+
// JWT tokens (eyJ...)
|
|
1247
|
+
{
|
|
1248
|
+
name: 'JWT Token',
|
|
1249
|
+
pattern: /[:=]\s*['"](eyJ[A-Za-z0-9\-_]{20,}\.[A-Za-z0-9\-_]{20,}\.[A-Za-z0-9\-_]{20,})['"]/g,
|
|
1250
|
+
severity: 'critical',
|
|
1251
|
+
envVarName: () => 'JWT_TOKEN',
|
|
1252
|
+
},
|
|
1253
|
+
// Connection strings (common patterns)
|
|
1254
|
+
{
|
|
1255
|
+
name: 'Connection String',
|
|
1256
|
+
pattern: /[:=]\s*['"]((?:mongodb|postgres|mysql|redis|amqp|https?):\/\/[^'"]{20,})['"]/gi,
|
|
1257
|
+
severity: 'critical',
|
|
1258
|
+
envVarName: () => 'CONNECTION_STRING',
|
|
1259
|
+
},
|
|
1260
|
+
// AWS keys (AKIA prefix)
|
|
1261
|
+
{
|
|
1262
|
+
name: 'AWS Access Key',
|
|
1263
|
+
pattern: /[:=]\s*['"](AKIA[0-9A-Z]{16})['"]/gi,
|
|
1264
|
+
severity: 'critical',
|
|
1265
|
+
envVarName: () => 'AWS_ACCESS_KEY_ID',
|
|
1266
|
+
},
|
|
1267
|
+
// Private keys (PEM format)
|
|
1268
|
+
{
|
|
1269
|
+
name: 'Private Key (PEM)',
|
|
1270
|
+
pattern: /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----[\s\S]{50,}-----END\s+(?:RSA\s+)?PRIVATE\s+KEY-----/gi,
|
|
1271
|
+
severity: 'critical',
|
|
1272
|
+
envVarName: () => 'PRIVATE_KEY',
|
|
1273
|
+
},
|
|
1274
|
+
// OAuth tokens
|
|
1275
|
+
{
|
|
1276
|
+
name: 'OAuth Token',
|
|
1277
|
+
pattern: /[:=]\s*['"](ya29\.[A-Za-z0-9\-_]{50,}|1\/\/[A-Za-z0-9\-_]{50,})['"]/gi,
|
|
1278
|
+
severity: 'critical',
|
|
1279
|
+
envVarName: () => 'OAUTH_TOKEN',
|
|
1280
|
+
},
|
|
1281
|
+
// Generic password pattern (8+ chars, not just "password")
|
|
1282
|
+
{
|
|
1283
|
+
name: 'Password',
|
|
1284
|
+
pattern: /(?:password|pwd|passwd|secret[_-]?key)\s*[:=]\s*['"]([^'"]{8,})['"]/gi,
|
|
1285
|
+
severity: 'critical',
|
|
1286
|
+
envVarName: () => 'PASSWORD',
|
|
1287
|
+
},
|
|
1288
|
+
];
|
|
1289
|
+
// Files to ignore during scan
|
|
1290
|
+
const IGNORE_PATTERNS = [
|
|
1291
|
+
/node_modules/,
|
|
1292
|
+
/\.git/,
|
|
1293
|
+
/dist/,
|
|
1294
|
+
/build/,
|
|
1295
|
+
/\.next/,
|
|
1296
|
+
/\.env/,
|
|
1297
|
+
/\.env\.local/,
|
|
1298
|
+
/\.env\.example/,
|
|
1299
|
+
/package-lock\.json/,
|
|
1300
|
+
/yarn\.lock/,
|
|
1301
|
+
/pnpm-lock\.yaml/,
|
|
1302
|
+
/\.lattice/,
|
|
1303
|
+
];
|
|
1304
|
+
// File extensions to scan - comprehensive list of all text-based files that could be publicly exposed
|
|
1305
|
+
const SCAN_EXTENSIONS = [
|
|
1306
|
+
// Source code
|
|
1307
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
1308
|
+
// Config files
|
|
1309
|
+
'.json', '.yaml', '.yml', '.toml', '.ini', '.conf', '.config',
|
|
1310
|
+
// Web files (publicly exposed)
|
|
1311
|
+
'.html', '.htm', '.css', '.scss', '.sass', '.less', '.styl',
|
|
1312
|
+
// Documentation (could contain examples with secrets)
|
|
1313
|
+
'.md', '.txt', '.rst', '.adoc',
|
|
1314
|
+
// Scripts
|
|
1315
|
+
'.sh', '.bash', '.zsh', '.fish', '.ps1', '.bat', '.cmd',
|
|
1316
|
+
// Docker & deployment
|
|
1317
|
+
'.dockerfile', '.dockerignore', '.gitignore', '.gitattributes',
|
|
1318
|
+
// Environment files
|
|
1319
|
+
'.env', '.env.local', '.env.production', '.env.development',
|
|
1320
|
+
// Config variants
|
|
1321
|
+
'.config.js', '.config.ts', '.config.json', '.config.yaml',
|
|
1322
|
+
// App configs
|
|
1323
|
+
'app.json', 'app.config.js', 'app.config.ts',
|
|
1324
|
+
// Build configs
|
|
1325
|
+
'webpack.config.js', 'vite.config.js', 'rollup.config.js',
|
|
1326
|
+
'next.config.js', 'nuxt.config.js', 'svelte.config.js',
|
|
1327
|
+
// Other configs
|
|
1328
|
+
'tsconfig.json', 'jsconfig.json', 'tailwind.config.js',
|
|
1329
|
+
'postcss.config.js', 'babel.config.js', 'jest.config.js',
|
|
1330
|
+
// EAS config
|
|
1331
|
+
'eas.json',
|
|
1332
|
+
// No extension but important files
|
|
1333
|
+
'Dockerfile', 'docker-compose.yml', 'Makefile', 'Procfile',
|
|
1334
|
+
];
|
|
1335
|
+
async function shouldScanFile(filePath, cwd) {
|
|
1336
|
+
const relativePath = (0, path_1.relative)(cwd, filePath);
|
|
1337
|
+
const fileName = relativePath.split(/[/\\]/).pop() || '';
|
|
1338
|
+
const lowerPath = relativePath.toLowerCase();
|
|
1339
|
+
// Check ignore patterns
|
|
1340
|
+
for (const pattern of IGNORE_PATTERNS) {
|
|
1341
|
+
if (pattern.test(relativePath)) {
|
|
1342
|
+
return false;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
// Check if file has no extension but is a known config file
|
|
1346
|
+
const noExtConfigFiles = ['Dockerfile', 'Makefile', 'Procfile', 'docker-compose.yml', 'eas.json', 'app.json'];
|
|
1347
|
+
if (noExtConfigFiles.includes(fileName) || noExtConfigFiles.some(name => lowerPath.includes(name.toLowerCase()))) {
|
|
1348
|
+
return true;
|
|
1349
|
+
}
|
|
1350
|
+
// Check file extension - handle files with multiple dots like app.config.js
|
|
1351
|
+
const lastDot = relativePath.lastIndexOf('.');
|
|
1352
|
+
if (lastDot === -1) {
|
|
1353
|
+
// No extension - check if it's a config file by name
|
|
1354
|
+
if (fileName.includes('config') || fileName.includes('Config')) {
|
|
1355
|
+
return true;
|
|
1356
|
+
}
|
|
1357
|
+
return false;
|
|
1358
|
+
}
|
|
1359
|
+
const ext = relativePath.substring(lastDot).toLowerCase();
|
|
1360
|
+
const baseName = relativePath.substring(0, lastDot).toLowerCase();
|
|
1361
|
+
// Check if extension matches directly
|
|
1362
|
+
if (SCAN_EXTENSIONS.some(e => e.toLowerCase() === ext)) {
|
|
1363
|
+
return true;
|
|
1364
|
+
}
|
|
1365
|
+
// Check for compound extensions like .config.js, .config.ts
|
|
1366
|
+
if (ext === '.js' || ext === '.ts' || ext === '.json' || ext === '.yaml' || ext === '.yml') {
|
|
1367
|
+
if (baseName.endsWith('.config') || baseName.includes('.config.')) {
|
|
1368
|
+
return true;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
// Check for build config files (webpack.config.js, vite.config.js, etc.)
|
|
1372
|
+
if (baseName.includes('webpack.config') || baseName.includes('vite.config') ||
|
|
1373
|
+
baseName.includes('rollup.config') || baseName.includes('next.config') ||
|
|
1374
|
+
baseName.includes('nuxt.config') || baseName.includes('svelte.config') ||
|
|
1375
|
+
baseName.includes('tailwind.config') || baseName.includes('postcss.config') ||
|
|
1376
|
+
baseName.includes('babel.config') || baseName.includes('jest.config') ||
|
|
1377
|
+
baseName.includes('tsconfig') || baseName.includes('jsconfig') ||
|
|
1378
|
+
baseName.includes('app.config')) {
|
|
1379
|
+
return true;
|
|
1380
|
+
}
|
|
1381
|
+
// Scan any text file that could be publicly exposed
|
|
1382
|
+
// This is aggressive but necessary for security
|
|
1383
|
+
const textFileExtensions = ['.txt', '.log', '.xml', '.svg', '.vue', '.svelte', '.astro'];
|
|
1384
|
+
if (textFileExtensions.includes(ext)) {
|
|
1385
|
+
return true;
|
|
1386
|
+
}
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
// Validate secret formats
|
|
1390
|
+
async function validateSecret(type, value) {
|
|
1391
|
+
// OpenAI API Key validation
|
|
1392
|
+
if (type.includes('OpenAI')) {
|
|
1393
|
+
if (!value.startsWith('sk-')) {
|
|
1394
|
+
return {
|
|
1395
|
+
isValid: false,
|
|
1396
|
+
reason: 'OpenAI keys must start with "sk-"',
|
|
1397
|
+
suggestions: ['Check if this is a valid OpenAI API key'],
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
if (value.length < 32) {
|
|
1401
|
+
return {
|
|
1402
|
+
isValid: false,
|
|
1403
|
+
reason: 'OpenAI keys must be at least 32 characters',
|
|
1404
|
+
suggestions: ['Key appears truncated'],
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
return { isValid: true };
|
|
1408
|
+
}
|
|
1409
|
+
// AWS Access Key validation
|
|
1410
|
+
if (type.includes('AWS')) {
|
|
1411
|
+
if (!value.startsWith('AKIA')) {
|
|
1412
|
+
return {
|
|
1413
|
+
isValid: false,
|
|
1414
|
+
reason: 'AWS access keys must start with "AKIA"',
|
|
1415
|
+
suggestions: ['This may not be a valid AWS access key'],
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
if (value.length !== 20) {
|
|
1419
|
+
return {
|
|
1420
|
+
isValid: false,
|
|
1421
|
+
reason: 'AWS access keys must be exactly 20 characters',
|
|
1422
|
+
suggestions: ['Key length is incorrect'],
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
return { isValid: true };
|
|
1426
|
+
}
|
|
1427
|
+
// JWT Token validation
|
|
1428
|
+
if (type.includes('JWT')) {
|
|
1429
|
+
const parts = value.split('.');
|
|
1430
|
+
if (parts.length !== 3) {
|
|
1431
|
+
return {
|
|
1432
|
+
isValid: false,
|
|
1433
|
+
reason: 'JWT tokens must have 3 parts separated by dots',
|
|
1434
|
+
suggestions: ['Invalid JWT format'],
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
return { isValid: true };
|
|
1438
|
+
}
|
|
1439
|
+
// Generic hex validation
|
|
1440
|
+
if (/^[a-f0-9]{32,}$/i.test(value)) {
|
|
1441
|
+
return { isValid: true };
|
|
1442
|
+
}
|
|
1443
|
+
// Generic base64 validation
|
|
1444
|
+
if (/^[A-Za-z0-9+/=]{40,}$/.test(value)) {
|
|
1445
|
+
return { isValid: true };
|
|
1446
|
+
}
|
|
1447
|
+
return {
|
|
1448
|
+
isValid: false,
|
|
1449
|
+
reason: 'Unknown secret format',
|
|
1450
|
+
suggestions: ['Verify this is a valid secret'],
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
async function scanFileForSecrets(filePath, cwd) {
|
|
1454
|
+
const findings = [];
|
|
1455
|
+
try {
|
|
1456
|
+
const content = await fs_1.promises.readFile(filePath, 'utf-8');
|
|
1457
|
+
const lines = content.split('\n');
|
|
1458
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
1459
|
+
let match;
|
|
1460
|
+
const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
|
|
1461
|
+
while ((match = regex.exec(content)) !== null) {
|
|
1462
|
+
if (match.index === undefined)
|
|
1463
|
+
continue;
|
|
1464
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
1465
|
+
const line = lines[lineNumber - 1] || '';
|
|
1466
|
+
const column = match.index - content.substring(0, match.index).lastIndexOf('\n');
|
|
1467
|
+
// Extract the secret value
|
|
1468
|
+
// For ALL_CAPS pattern: match[1]=varName, match[2]=value
|
|
1469
|
+
// For other patterns: match[1]=value
|
|
1470
|
+
let secretValue;
|
|
1471
|
+
if (match.length > 2 && match[2] !== undefined && match[2] !== '') {
|
|
1472
|
+
secretValue = match[2];
|
|
1473
|
+
}
|
|
1474
|
+
else if (match[1] !== undefined && match[1] !== '') {
|
|
1475
|
+
secretValue = match[1];
|
|
1476
|
+
}
|
|
1477
|
+
else {
|
|
1478
|
+
secretValue = match[0];
|
|
1479
|
+
}
|
|
1480
|
+
// Validate the secret
|
|
1481
|
+
const validation = await validateSecret(pattern.name, secretValue);
|
|
1482
|
+
// Skip if already using environment variable (but not if it's a fallback like process.env.KEY || 'hardcoded')
|
|
1483
|
+
// Check if the line has a hardcoded value as a fallback
|
|
1484
|
+
const hasEnvVar = line.includes('process.env') || line.includes('import.meta.env') || line.includes('process.env');
|
|
1485
|
+
const hasFallback = line.includes('||') || line.includes('??') || line.includes('||') || line.includes('??');
|
|
1486
|
+
// If it's using env var AND has NO fallback, skip (it's safe)
|
|
1487
|
+
// BUT if it has a fallback, that's still a security issue - flag it
|
|
1488
|
+
if (hasEnvVar && !hasFallback) {
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
// Also skip if it's clearly a comment or documentation example
|
|
1492
|
+
// (but be careful - some secrets are in comments!)
|
|
1493
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('*') || line.trim().startsWith('#')) {
|
|
1494
|
+
// Only skip if it's clearly an example (contains "example", "demo", "placeholder")
|
|
1495
|
+
if (line.toLowerCase().includes('example') || line.toLowerCase().includes('demo') ||
|
|
1496
|
+
line.toLowerCase().includes('placeholder') || line.toLowerCase().includes('your_')) {
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
findings.push({
|
|
1501
|
+
file: (0, path_1.relative)(cwd, filePath),
|
|
1502
|
+
line: lineNumber,
|
|
1503
|
+
column: column - 1,
|
|
1504
|
+
type: pattern.name,
|
|
1505
|
+
severity: pattern.severity,
|
|
1506
|
+
value: secretValue.substring(0, 20) + '...',
|
|
1507
|
+
suggestion: pattern.envVarName ? `Use ${pattern.envVarName(match)} environment variable` : 'Move to environment variable',
|
|
1508
|
+
validation: validation,
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
catch (error) {
|
|
1514
|
+
// Skip files that can't be read (binary, etc.)
|
|
1515
|
+
if (error.code !== 'EISDIR') {
|
|
1516
|
+
// Silently skip
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return findings;
|
|
1520
|
+
}
|
|
1521
|
+
async function getAllFiles(dir, cwd) {
|
|
1522
|
+
const files = [];
|
|
1523
|
+
try {
|
|
1524
|
+
const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
|
|
1525
|
+
for (const entry of entries) {
|
|
1526
|
+
const fullPath = (0, path_1.join)(dir, entry.name);
|
|
1527
|
+
if (entry.isDirectory()) {
|
|
1528
|
+
if (await shouldScanFile(fullPath, cwd)) {
|
|
1529
|
+
files.push(...await getAllFiles(fullPath, cwd));
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
else if (entry.isFile()) {
|
|
1533
|
+
if (await shouldScanFile(fullPath, cwd)) {
|
|
1534
|
+
files.push(fullPath);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
catch {
|
|
1540
|
+
// Skip directories we can't read
|
|
1541
|
+
}
|
|
1542
|
+
return files;
|
|
1543
|
+
}
|
|
1544
|
+
async function ensureGitignore(cwd) {
|
|
1545
|
+
const gitignorePath = (0, path_1.join)(cwd, '.gitignore');
|
|
1546
|
+
const envPatterns = [
|
|
1547
|
+
'.env',
|
|
1548
|
+
'.env.local',
|
|
1549
|
+
'.env*.local',
|
|
1550
|
+
'.env.production',
|
|
1551
|
+
'.env.development',
|
|
1552
|
+
];
|
|
1553
|
+
let gitignoreContent = '';
|
|
1554
|
+
try {
|
|
1555
|
+
gitignoreContent = await fs_1.promises.readFile(gitignorePath, 'utf-8');
|
|
1556
|
+
}
|
|
1557
|
+
catch {
|
|
1558
|
+
// .gitignore doesn't exist, create it
|
|
1559
|
+
}
|
|
1560
|
+
let needsUpdate = false;
|
|
1561
|
+
for (const pattern of envPatterns) {
|
|
1562
|
+
if (!gitignoreContent.includes(pattern)) {
|
|
1563
|
+
gitignoreContent += `\n${pattern}`;
|
|
1564
|
+
needsUpdate = true;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
if (needsUpdate) {
|
|
1568
|
+
await fs_1.promises.writeFile(gitignorePath, gitignoreContent.trim() + '\n', 'utf-8');
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
async function fixSecretInFile(filePath, finding, envVarName, cwd) {
|
|
1572
|
+
try {
|
|
1573
|
+
let content = await fs_1.promises.readFile(filePath, 'utf-8');
|
|
1574
|
+
const lines = content.split('\n');
|
|
1575
|
+
const lineIndex = finding.line - 1;
|
|
1576
|
+
if (lineIndex >= lines.length) {
|
|
1577
|
+
return false;
|
|
1578
|
+
}
|
|
1579
|
+
const originalLine = lines[lineIndex];
|
|
1580
|
+
// Try to extract the variable name and replace the value
|
|
1581
|
+
// Pattern: variableName = "secret" or variableName: "secret"
|
|
1582
|
+
const assignmentMatch = originalLine.match(/(\w+)\s*[:=]\s*['"]([^'"]+)['"]/);
|
|
1583
|
+
if (assignmentMatch) {
|
|
1584
|
+
const varName = assignmentMatch[1];
|
|
1585
|
+
const isJson = filePath.endsWith('.json');
|
|
1586
|
+
if (isJson) {
|
|
1587
|
+
// For JSON files, replace with environment variable reference comment
|
|
1588
|
+
const newLine = originalLine.replace(/['"]([^'"]+)['"]/, `"${envVarName}" // TODO: Replace with process.env.${envVarName} in code`);
|
|
1589
|
+
lines[lineIndex] = newLine;
|
|
1590
|
+
}
|
|
1591
|
+
else {
|
|
1592
|
+
// For code files, replace with process.env reference
|
|
1593
|
+
const isNext = filePath.match(/\.(ts|tsx|js|jsx)$/);
|
|
1594
|
+
const envRef = isNext ? `process.env.${envVarName}` : `process.env.${envVarName}`;
|
|
1595
|
+
const newLine = originalLine.replace(/['"]([^'"]+)['"]/, `\`\${${envRef}}\``);
|
|
1596
|
+
lines[lineIndex] = newLine;
|
|
1597
|
+
}
|
|
1598
|
+
content = lines.join('\n');
|
|
1599
|
+
await fs_1.promises.writeFile(filePath, content, 'utf-8');
|
|
1600
|
+
return true;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
catch {
|
|
1604
|
+
return false;
|
|
1605
|
+
}
|
|
1606
|
+
return false;
|
|
1607
|
+
}
|
|
1608
|
+
async function addToEnvFile(cwd, envVarName, secretValue) {
|
|
1609
|
+
const envPath = (0, path_1.join)(cwd, '.env.local');
|
|
1610
|
+
let envContent = '';
|
|
1611
|
+
try {
|
|
1612
|
+
envContent = await fs_1.promises.readFile(envPath, 'utf-8');
|
|
1613
|
+
}
|
|
1614
|
+
catch {
|
|
1615
|
+
// File doesn't exist, create it
|
|
1616
|
+
}
|
|
1617
|
+
// Check if variable already exists
|
|
1618
|
+
if (envContent.includes(`${envVarName}=`)) {
|
|
1619
|
+
return; // Already exists
|
|
1620
|
+
}
|
|
1621
|
+
envContent += `\n${envVarName}=${secretValue}\n`;
|
|
1622
|
+
await fs_1.promises.writeFile(envPath, envContent.trim() + '\n', 'utf-8');
|
|
1623
|
+
}
|
|
1624
|
+
async function securityScan(options = {}) {
|
|
1625
|
+
const cwd = options.cwd || process.cwd();
|
|
1626
|
+
const findings = [];
|
|
1627
|
+
const errors = [];
|
|
1628
|
+
let fixed = 0;
|
|
1629
|
+
let totalFilesScanned = 0;
|
|
1630
|
+
try {
|
|
1631
|
+
// 1. Scan current files
|
|
1632
|
+
const files = await getAllFiles(cwd, cwd);
|
|
1633
|
+
totalFilesScanned = files.length;
|
|
1634
|
+
for (const file of files) {
|
|
1635
|
+
const fileFindings = await scanFileForSecrets(file, cwd);
|
|
1636
|
+
findings.push(...fileFindings);
|
|
1637
|
+
}
|
|
1638
|
+
// 2. Scan git history if requested
|
|
1639
|
+
if (options.scanGitHistory) {
|
|
1640
|
+
try {
|
|
1641
|
+
const gitFindings = await scanGitHistory(cwd);
|
|
1642
|
+
findings.push(...gitFindings);
|
|
1643
|
+
}
|
|
1644
|
+
catch (error) {
|
|
1645
|
+
errors.push(`Git history scan failed: ${error.message}`);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
// 3. Validate .env files are properly gitignored
|
|
1649
|
+
if (options.checkEnvFiles !== false) {
|
|
1650
|
+
try {
|
|
1651
|
+
const envIssues = await validateEnvFiles(cwd);
|
|
1652
|
+
if (envIssues.length > 0) {
|
|
1653
|
+
errors.push(...envIssues);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
catch (error) {
|
|
1657
|
+
errors.push(`Env file validation failed: ${error.message}`);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
if (options.fix && findings.length > 0) {
|
|
1661
|
+
// Group findings by file for efficient processing
|
|
1662
|
+
const findingsByFile = new Map();
|
|
1663
|
+
for (const finding of findings) {
|
|
1664
|
+
const fullPath = (0, path_1.join)(cwd, finding.file);
|
|
1665
|
+
if (!findingsByFile.has(fullPath)) {
|
|
1666
|
+
findingsByFile.set(fullPath, []);
|
|
1667
|
+
}
|
|
1668
|
+
findingsByFile.get(fullPath).push(finding);
|
|
1669
|
+
}
|
|
1670
|
+
// Ensure .gitignore includes .env files
|
|
1671
|
+
await ensureGitignore(cwd);
|
|
1672
|
+
// Fix each finding
|
|
1673
|
+
for (const [filePath, fileFindings] of findingsByFile.entries()) {
|
|
1674
|
+
try {
|
|
1675
|
+
const content = await fs_1.promises.readFile(filePath, 'utf-8');
|
|
1676
|
+
const lines = content.split('\n');
|
|
1677
|
+
for (const finding of fileFindings) {
|
|
1678
|
+
// Determine environment variable name
|
|
1679
|
+
const pattern = SECRET_PATTERNS.find(p => p.name === finding.type);
|
|
1680
|
+
if (!pattern || !pattern.envVarName) {
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
// Re-scan the line to get the full secret value
|
|
1684
|
+
const lineIndex = finding.line - 1;
|
|
1685
|
+
if (lineIndex >= lines.length) {
|
|
1686
|
+
continue;
|
|
1687
|
+
}
|
|
1688
|
+
const line = lines[lineIndex];
|
|
1689
|
+
const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
|
|
1690
|
+
const match = regex.exec(line);
|
|
1691
|
+
if (match) {
|
|
1692
|
+
// Extract the secret value
|
|
1693
|
+
// For ALL_CAPS pattern: match[1]=varName, match[2]=value
|
|
1694
|
+
// For other patterns: match[1]=value
|
|
1695
|
+
let secretValue;
|
|
1696
|
+
if (match.length > 2 && match[2] !== undefined && match[2] !== '') {
|
|
1697
|
+
secretValue = match[2];
|
|
1698
|
+
}
|
|
1699
|
+
else if (match[1] !== undefined && match[1] !== '') {
|
|
1700
|
+
secretValue = match[1];
|
|
1701
|
+
}
|
|
1702
|
+
else {
|
|
1703
|
+
secretValue = match[0];
|
|
1704
|
+
}
|
|
1705
|
+
const envVarName = pattern.envVarName(match);
|
|
1706
|
+
// Add to .env file
|
|
1707
|
+
await addToEnvFile(cwd, envVarName, secretValue);
|
|
1708
|
+
// Fix in source file
|
|
1709
|
+
if (await fixSecretInFile(filePath, finding, envVarName, cwd)) {
|
|
1710
|
+
fixed++;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
catch (error) {
|
|
1716
|
+
errors.push(`Failed to fix ${filePath}: ${error.message}`);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
catch (error) {
|
|
1722
|
+
errors.push(`Scan error: ${error.message}`);
|
|
1723
|
+
}
|
|
1724
|
+
// Install pre-commit hook if requested
|
|
1725
|
+
if (options.installHook) {
|
|
1726
|
+
try {
|
|
1727
|
+
await installPreCommitHook(cwd, options.hookPath);
|
|
1728
|
+
}
|
|
1729
|
+
catch (error) {
|
|
1730
|
+
errors.push(`Failed to install pre-commit hook: ${error.message}`);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
// Generate summary
|
|
1734
|
+
const summary = {
|
|
1735
|
+
totalFilesScanned,
|
|
1736
|
+
criticalFindings: findings.filter(f => f.severity === 'critical').length,
|
|
1737
|
+
highFindings: findings.filter(f => f.severity === 'high').length,
|
|
1738
|
+
mediumFindings: findings.filter(f => f.severity === 'medium').length,
|
|
1739
|
+
gitHistoryFindings: findings.filter(f => f.file.startsWith('git:')).length,
|
|
1740
|
+
envFileIssues: errors.filter(e => e.includes('.env') || e.includes('gitignore')).length,
|
|
1741
|
+
};
|
|
1742
|
+
// Export report if requested
|
|
1743
|
+
if (options.exportPath) {
|
|
1744
|
+
const format = options.exportFormat || (options.sarif ? 'sarif' : 'json');
|
|
1745
|
+
let content;
|
|
1746
|
+
if (format === 'sarif') {
|
|
1747
|
+
content = JSON.stringify(generateSarifReport({ findings, fixed, errors, summary }, cwd), null, 2);
|
|
1748
|
+
}
|
|
1749
|
+
else {
|
|
1750
|
+
content = JSON.stringify({ findings, fixed, errors, summary }, null, 2);
|
|
1751
|
+
}
|
|
1752
|
+
await fs_1.promises.writeFile(options.exportPath, content, 'utf-8');
|
|
1753
|
+
console.log(`✅ Report exported to: ${options.exportPath}`);
|
|
1754
|
+
}
|
|
1755
|
+
return { findings, fixed, errors, summary };
|
|
1756
|
+
}
|
|
1757
|
+
// Scan git history for secrets
|
|
1758
|
+
async function scanGitHistory(cwd) {
|
|
1759
|
+
const findings = [];
|
|
1760
|
+
try {
|
|
1761
|
+
// Check if git is available
|
|
1762
|
+
const { execSync } = require('child_process');
|
|
1763
|
+
// Get all commits
|
|
1764
|
+
const commits = execSync('git log --all --pretty=format:"%H"', {
|
|
1765
|
+
cwd,
|
|
1766
|
+
encoding: 'utf-8',
|
|
1767
|
+
stdio: 'pipe'
|
|
1768
|
+
}).trim().split('\n').filter(Boolean);
|
|
1769
|
+
// Scan each commit's diff
|
|
1770
|
+
for (const commit of commits.slice(0, 100)) { // Limit to last 100 commits for performance
|
|
1771
|
+
try {
|
|
1772
|
+
const diff = execSync(`git show ${commit}`, {
|
|
1773
|
+
cwd,
|
|
1774
|
+
encoding: 'utf-8',
|
|
1775
|
+
stdio: 'pipe',
|
|
1776
|
+
maxBuffer: 10 * 1024 * 1024 // 10MB buffer
|
|
1777
|
+
});
|
|
1778
|
+
// Scan diff for secrets
|
|
1779
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
1780
|
+
const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
|
|
1781
|
+
let match;
|
|
1782
|
+
while ((match = regex.exec(diff)) !== null) {
|
|
1783
|
+
if (match.index === undefined)
|
|
1784
|
+
continue;
|
|
1785
|
+
findings.push({
|
|
1786
|
+
file: `git:${commit.substring(0, 8)}`,
|
|
1787
|
+
line: 0,
|
|
1788
|
+
column: 0,
|
|
1789
|
+
type: `${pattern.name} (in git history)`,
|
|
1790
|
+
severity: pattern.severity,
|
|
1791
|
+
value: (match[1] || match[0]).substring(0, 20) + '...',
|
|
1792
|
+
suggestion: `Secret found in git history. Consider: 1) Rotating the secret, 2) Using git-filter-repo to remove it, 3) Using BFG Repo-Cleaner`,
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
catch (error) {
|
|
1798
|
+
// Skip commits that can't be read
|
|
1799
|
+
continue;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
catch (error) {
|
|
1804
|
+
// Git not available or not a git repo
|
|
1805
|
+
throw new Error(`Git history scan failed: ${error.message}`);
|
|
1806
|
+
}
|
|
1807
|
+
return findings;
|
|
1808
|
+
}
|
|
1809
|
+
// Validate .env files are properly gitignored
|
|
1810
|
+
async function validateEnvFiles(cwd) {
|
|
1811
|
+
const issues = [];
|
|
1812
|
+
try {
|
|
1813
|
+
const gitignorePath = (0, path_1.join)(cwd, '.gitignore');
|
|
1814
|
+
// Check if .gitignore exists
|
|
1815
|
+
let gitignoreContent = '';
|
|
1816
|
+
try {
|
|
1817
|
+
gitignoreContent = await fs_1.promises.readFile(gitignorePath, 'utf-8');
|
|
1818
|
+
}
|
|
1819
|
+
catch {
|
|
1820
|
+
issues.push('⚠️ No .gitignore file found. Create one to protect .env files.');
|
|
1821
|
+
return issues;
|
|
1822
|
+
}
|
|
1823
|
+
// Check if .env files are tracked by git
|
|
1824
|
+
try {
|
|
1825
|
+
const allFiles = (0, child_process_1.execSync)('git ls-files', {
|
|
1826
|
+
cwd,
|
|
1827
|
+
encoding: 'utf-8',
|
|
1828
|
+
stdio: 'pipe'
|
|
1829
|
+
}).trim().split('\n');
|
|
1830
|
+
const trackedEnvFiles = allFiles.filter((f) => f.includes('.env') && !f.includes('.env.example'));
|
|
1831
|
+
if (trackedEnvFiles.length > 0) {
|
|
1832
|
+
issues.push(`🚨 CRITICAL: .env files are tracked in git: ${trackedEnvFiles.join(', ')}`);
|
|
1833
|
+
issues.push(' Action required: Remove from git with: git rm --cached <file>');
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
catch {
|
|
1837
|
+
// Git not available
|
|
1838
|
+
}
|
|
1839
|
+
// Check if .env files exist but aren't in .gitignore
|
|
1840
|
+
const envFiles = ['.env', '.env.local', '.env.production', '.env.development'];
|
|
1841
|
+
for (const envFile of envFiles) {
|
|
1842
|
+
const envPath = (0, path_1.join)(cwd, envFile);
|
|
1843
|
+
try {
|
|
1844
|
+
await fs_1.promises.access(envPath);
|
|
1845
|
+
// File exists, check if it's in .gitignore
|
|
1846
|
+
// Check for explicit filename, or patterns like .env*, .env.*, .env.local, etc.
|
|
1847
|
+
const isExplicitlyIgnored = gitignoreContent.includes(envFile);
|
|
1848
|
+
const hasEnvPattern = gitignoreContent.includes('.env*') || gitignoreContent.includes('.env.*');
|
|
1849
|
+
const isEnvFile = envFile.startsWith('.env');
|
|
1850
|
+
// Use git check-ignore to verify (most accurate)
|
|
1851
|
+
let isActuallyIgnored = false;
|
|
1852
|
+
try {
|
|
1853
|
+
const checkResult = (0, child_process_1.execSync)(`git check-ignore "${envFile}"`, {
|
|
1854
|
+
cwd,
|
|
1855
|
+
encoding: 'utf-8',
|
|
1856
|
+
stdio: 'pipe'
|
|
1857
|
+
}).trim();
|
|
1858
|
+
isActuallyIgnored = checkResult === envFile;
|
|
1859
|
+
}
|
|
1860
|
+
catch {
|
|
1861
|
+
// git check-ignore returns non-zero if file is NOT ignored
|
|
1862
|
+
isActuallyIgnored = false;
|
|
1863
|
+
}
|
|
1864
|
+
// Only warn if file exists AND is not actually ignored by git
|
|
1865
|
+
if (!isActuallyIgnored && !isExplicitlyIgnored && !hasEnvPattern) {
|
|
1866
|
+
issues.push(`⚠️ ${envFile} exists but may not be in .gitignore`);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
catch {
|
|
1870
|
+
// File doesn't exist, that's fine
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
catch (error) {
|
|
1875
|
+
// Not a git repo or git not available
|
|
1876
|
+
issues.push(`⚠️ Could not validate .env files: ${error.message}`);
|
|
1877
|
+
}
|
|
1878
|
+
return issues;
|
|
1879
|
+
}
|
|
1880
|
+
const ALLOWLIST_FILE = '.lattice/security-allowlist.json';
|
|
1881
|
+
async function loadAllowlist(cwd) {
|
|
1882
|
+
const allowlistPath = (0, path_1.join)(cwd, ALLOWLIST_FILE);
|
|
1883
|
+
try {
|
|
1884
|
+
const content = await fs_1.promises.readFile(allowlistPath, 'utf-8');
|
|
1885
|
+
return JSON.parse(content);
|
|
1886
|
+
}
|
|
1887
|
+
catch {
|
|
1888
|
+
return { patterns: [] };
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
async function saveAllowlist(cwd, allowlist) {
|
|
1892
|
+
const allowlistPath = (0, path_1.join)(cwd, ALLOWLIST_FILE);
|
|
1893
|
+
const allowlistDir = (0, path_1.dirname)(allowlistPath);
|
|
1894
|
+
await fs_1.promises.mkdir(allowlistDir, { recursive: true });
|
|
1895
|
+
await fs_1.promises.writeFile(allowlistPath, JSON.stringify(allowlist, null, 2));
|
|
1896
|
+
}
|
|
1897
|
+
function isAllowlisted(finding, allowlist, cwd) {
|
|
1898
|
+
const relativeFile = (0, path_1.relative)(cwd, finding.file);
|
|
1899
|
+
return allowlist.patterns.some(pattern => {
|
|
1900
|
+
return pattern.file === relativeFile &&
|
|
1901
|
+
pattern.line === finding.line &&
|
|
1902
|
+
pattern.type === finding.type;
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
async function addToAllowlist(cwd, finding, reason) {
|
|
1906
|
+
const allowlist = await loadAllowlist(cwd);
|
|
1907
|
+
const relativeFile = (0, path_1.relative)(cwd, finding.file);
|
|
1908
|
+
allowlist.patterns.push({
|
|
1909
|
+
file: relativeFile,
|
|
1910
|
+
line: finding.line,
|
|
1911
|
+
type: finding.type,
|
|
1912
|
+
reason,
|
|
1913
|
+
addedBy: process.env.USER || process.env.USERNAME || 'unknown',
|
|
1914
|
+
addedAt: new Date().toISOString(),
|
|
1915
|
+
});
|
|
1916
|
+
await saveAllowlist(cwd, allowlist);
|
|
1917
|
+
}
|
|
1918
|
+
// Install pre-commit hook
|
|
1919
|
+
async function installPreCommitHook(cwd, hookPath) {
|
|
1920
|
+
const gitHooksDir = (0, path_1.join)(cwd, '.git', 'hooks');
|
|
1921
|
+
const hookFile = hookPath || (0, path_1.join)(gitHooksDir, 'pre-commit');
|
|
1922
|
+
try {
|
|
1923
|
+
// Check if .git/hooks exists
|
|
1924
|
+
await fs_1.promises.access(gitHooksDir);
|
|
1925
|
+
}
|
|
1926
|
+
catch {
|
|
1927
|
+
throw new Error('Not a git repository. Run "git init" first.');
|
|
1928
|
+
}
|
|
1929
|
+
// Get the CLI path - try to find it relative to the hook location
|
|
1930
|
+
// In a published package, this would be in node_modules/@m2-ai/lattice-cli/dist/index.js
|
|
1931
|
+
// For development, it's in packages/cli/dist/index.js
|
|
1932
|
+
let cliPath;
|
|
1933
|
+
try {
|
|
1934
|
+
// Try to find the CLI in node_modules first (when installed as package)
|
|
1935
|
+
const nodeModulesPath = (0, path_1.join)(cwd, 'node_modules', '@latticeai', 'cli', 'dist', 'index.js');
|
|
1936
|
+
await fs_1.promises.access(nodeModulesPath);
|
|
1937
|
+
cliPath = nodeModulesPath;
|
|
1938
|
+
}
|
|
1939
|
+
catch {
|
|
1940
|
+
// Fall back to npx/latticeai command
|
|
1941
|
+
cliPath = 'latticeai';
|
|
1942
|
+
}
|
|
1943
|
+
const hookContent = process.platform === 'win32'
|
|
1944
|
+
? `@echo off
|
|
1945
|
+
REM Lattice Security Scan Pre-commit Hook
|
|
1946
|
+
REM Generated by: lattice security-scan --install-hook
|
|
1947
|
+
|
|
1948
|
+
${cliPath === 'latticeai' ? 'npx' : 'node'} "${cliPath}" security-scan --scan-git-history
|
|
1949
|
+
|
|
1950
|
+
if %errorlevel% neq 0 (
|
|
1951
|
+
echo.
|
|
1952
|
+
echo ❌ Pre-commit hook failed: Security scan detected secrets
|
|
1953
|
+
echo 💡 Tip: Run 'lattice security-scan --fix' to fix issues
|
|
1954
|
+
exit /b 1
|
|
1955
|
+
)
|
|
1956
|
+
`
|
|
1957
|
+
: `#!/bin/sh
|
|
1958
|
+
# Lattice Security Scan Pre-commit Hook
|
|
1959
|
+
# Generated by: lattice security-scan --install-hook
|
|
1960
|
+
|
|
1961
|
+
${cliPath === 'latticeai' ? 'npx' : 'node'} "${cliPath}" security-scan --scan-git-history
|
|
1962
|
+
|
|
1963
|
+
# Exit with error if scan fails
|
|
1964
|
+
if [ $? -ne 0 ]; then
|
|
1965
|
+
echo ""
|
|
1966
|
+
echo "❌ Pre-commit hook failed: Security scan detected secrets"
|
|
1967
|
+
echo "💡 Tip: Run 'lattice security-scan --fix' to fix issues"
|
|
1968
|
+
exit 1
|
|
1969
|
+
fi
|
|
1970
|
+
`;
|
|
1971
|
+
await fs_1.promises.writeFile(hookFile, hookContent, { mode: process.platform === 'win32' ? undefined : 0o755 });
|
|
1972
|
+
console.log(`✅ Pre-commit hook installed at: ${hookFile}`);
|
|
1973
|
+
console.log(` The hook will run security scans before each commit.`);
|
|
1974
|
+
}
|
|
1975
|
+
function generateSarifReport(result, cwd) {
|
|
1976
|
+
const severityMap = {
|
|
1977
|
+
critical: 'error',
|
|
1978
|
+
high: 'error',
|
|
1979
|
+
medium: 'warning',
|
|
1980
|
+
};
|
|
1981
|
+
return {
|
|
1982
|
+
version: '2.1.0',
|
|
1983
|
+
$schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
|
|
1984
|
+
runs: [{
|
|
1985
|
+
tool: {
|
|
1986
|
+
driver: {
|
|
1987
|
+
name: 'Lattice Security Scan',
|
|
1988
|
+
version: '0.7.1',
|
|
1989
|
+
},
|
|
1990
|
+
},
|
|
1991
|
+
results: result.findings.map(finding => ({
|
|
1992
|
+
ruleId: finding.type.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_]/g, '_'),
|
|
1993
|
+
level: severityMap[finding.severity] || 'warning',
|
|
1994
|
+
message: {
|
|
1995
|
+
text: `${finding.type} found: ${finding.value}`,
|
|
1996
|
+
},
|
|
1997
|
+
locations: [{
|
|
1998
|
+
physicalLocation: {
|
|
1999
|
+
artifactLocation: {
|
|
2000
|
+
uri: finding.file.replace(/\\/g, '/'), // Normalize path separators
|
|
2001
|
+
},
|
|
2002
|
+
region: {
|
|
2003
|
+
startLine: finding.line || 1,
|
|
2004
|
+
startColumn: finding.column || 1,
|
|
2005
|
+
},
|
|
2006
|
+
},
|
|
2007
|
+
}],
|
|
2008
|
+
})),
|
|
2009
|
+
}],
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
//# sourceMappingURL=commands.js.map
|