@m2-ai/lattice-cli 0.0.0-stage → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DISCLOSURE +1 -0
- package/README.md +118 -2
- package/bundles/expo-mobile/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/expo-mobile/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/expo-mobile/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-mobile/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/expo-mobile/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/expo-mobile/.claude/hooks/README.md +9 -0
- package/bundles/expo-mobile/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/expo-mobile/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/expo-mobile/.claude/rules/lattice-core.md +20 -0
- package/bundles/expo-mobile/.claude/settings.json +38 -0
- package/bundles/expo-mobile/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-mobile/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/expo-mobile/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/expo-mobile/.cursor/mcp.json +34 -0
- package/bundles/expo-mobile/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/expo-mobile/.cursor/rules.md +94 -0
- package/bundles/expo-mobile/.env.example +19 -0
- package/bundles/expo-mobile/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/expo-mobile/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/expo-mobile/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/expo-mobile/.github/copilot-instructions.md +37 -0
- package/bundles/expo-mobile/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/expo-mobile/.github/workflows/ci.yml +26 -0
- package/bundles/expo-mobile/.gitmessage +24 -0
- package/bundles/expo-mobile/.lattice/config.json +9 -0
- package/bundles/expo-mobile/.lattice/dependency-security.json +91 -0
- package/bundles/expo-mobile/.lattice/prompts/01-feature.md +36 -0
- package/bundles/expo-mobile/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/expo-mobile/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/expo-mobile/.lattice/prompts/04-test.md +31 -0
- package/bundles/expo-mobile/.lattice/prompts/05-polish.md +38 -0
- package/bundles/expo-mobile/.lattice/prompts/06-security.md +32 -0
- package/bundles/expo-mobile/.lattice/release-workflow.json +55 -0
- package/bundles/expo-mobile/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/expo-mobile/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/expo-mobile/.mcp.json +35 -0
- package/bundles/expo-mobile/AGENTS.md +58 -0
- package/bundles/expo-mobile/CLAUDE.md +75 -0
- package/bundles/expo-mobile/PROJECT_GUIDE.md +299 -0
- package/bundles/expo-mobile/README.md +39 -0
- package/bundles/expo-mobile/__tests__/home.test.tsx +22 -0
- package/bundles/expo-mobile/app/_layout.tsx +20 -0
- package/bundles/expo-mobile/app/index.tsx +72 -0
- package/bundles/expo-mobile/app.json +33 -0
- package/bundles/expo-mobile/components/ui/Button.tsx +75 -0
- package/bundles/expo-mobile/components/ui/Input.tsx +50 -0
- package/bundles/expo-mobile/components/ui/index.ts +2 -0
- package/bundles/expo-mobile/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/expo-mobile/docs/rules/codex.md +62 -0
- package/bundles/expo-mobile/eslint.config.js +7 -0
- package/bundles/expo-mobile/lib/supabase/client.ts +22 -0
- package/bundles/expo-mobile/lib/supabase/realtime.ts +10 -0
- package/bundles/expo-mobile/lib/supabase/storage.ts +6 -0
- package/bundles/expo-mobile/package.json +65 -0
- package/bundles/expo-mobile/plugins/with-lattice-commons-io.cjs +42 -0
- package/bundles/expo-mobile/prompts/01-feature-implementation.md +48 -0
- package/bundles/expo-mobile/prompts/02-bug-fix.md +43 -0
- package/bundles/expo-mobile/prompts/03-safe-refactor.md +38 -0
- package/bundles/expo-mobile/prompts/04-test-first.md +41 -0
- package/bundles/expo-mobile/prompts/05-ui-polish.md +44 -0
- package/bundles/expo-mobile/prompts/06-security-review.md +39 -0
- package/bundles/expo-mobile/prompts/quality.md +143 -0
- package/bundles/expo-mobile/supabase/migrations/001_initial_schema.sql +66 -0
- package/bundles/expo-mobile/tsconfig.json +21 -0
- package/bundles/expo-mobile/types/database.types.ts +76 -0
- package/bundles/expo-mobile/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/expo-supabase/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/expo-supabase/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/expo-supabase/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-supabase/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/expo-supabase/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/expo-supabase/.claude/hooks/README.md +9 -0
- package/bundles/expo-supabase/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/expo-supabase/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/expo-supabase/.claude/rules/lattice-core.md +20 -0
- package/bundles/expo-supabase/.claude/settings.json +38 -0
- package/bundles/expo-supabase/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/expo-supabase/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/expo-supabase/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/expo-supabase/.cursor/mcp.json +34 -0
- package/bundles/expo-supabase/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/expo-supabase/.cursor/rules.md +94 -0
- package/bundles/expo-supabase/.env.example +12 -0
- package/bundles/expo-supabase/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/expo-supabase/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/expo-supabase/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/expo-supabase/.github/copilot-instructions.md +37 -0
- package/bundles/expo-supabase/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/expo-supabase/.github/workflows/ci.yml +26 -0
- package/bundles/expo-supabase/.gitmessage +24 -0
- package/bundles/expo-supabase/.lattice/config.json +9 -0
- package/bundles/expo-supabase/.lattice/dependency-security.json +91 -0
- package/bundles/expo-supabase/.lattice/prompts/01-feature.md +36 -0
- package/bundles/expo-supabase/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/expo-supabase/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/expo-supabase/.lattice/prompts/04-test.md +31 -0
- package/bundles/expo-supabase/.lattice/prompts/05-polish.md +38 -0
- package/bundles/expo-supabase/.lattice/prompts/06-security.md +32 -0
- package/bundles/expo-supabase/.lattice/release-workflow.json +55 -0
- package/bundles/expo-supabase/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/expo-supabase/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/expo-supabase/.mcp.json +35 -0
- package/bundles/expo-supabase/AGENTS.md +58 -0
- package/bundles/expo-supabase/CLAUDE.md +75 -0
- package/bundles/expo-supabase/PROJECT_GUIDE.md +201 -0
- package/bundles/expo-supabase/README.md +38 -0
- package/bundles/expo-supabase/__tests__/home.test.tsx +22 -0
- package/bundles/expo-supabase/app/_layout.tsx +14 -0
- package/bundles/expo-supabase/app/index.tsx +72 -0
- package/bundles/expo-supabase/app.json +33 -0
- package/bundles/expo-supabase/components/ui/Button.tsx +75 -0
- package/bundles/expo-supabase/components/ui/Input.tsx +50 -0
- package/bundles/expo-supabase/components/ui/index.ts +2 -0
- package/bundles/expo-supabase/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/expo-supabase/docs/rules/codex.md +62 -0
- package/bundles/expo-supabase/eslint.config.js +7 -0
- package/bundles/expo-supabase/lib/supabase/client.ts +22 -0
- package/bundles/expo-supabase/lib/supabase/realtime.ts +10 -0
- package/bundles/expo-supabase/lib/supabase/storage.ts +6 -0
- package/bundles/expo-supabase/package.json +60 -0
- package/bundles/expo-supabase/plugins/with-lattice-commons-io.cjs +42 -0
- package/bundles/expo-supabase/prompts/01-feature-implementation.md +8 -0
- package/bundles/expo-supabase/prompts/02-bug-fix.md +9 -0
- package/bundles/expo-supabase/prompts/03-safe-refactor.md +8 -0
- package/bundles/expo-supabase/prompts/04-test-first.md +7 -0
- package/bundles/expo-supabase/prompts/05-ui-polish.md +7 -0
- package/bundles/expo-supabase/prompts/06-security-review.md +7 -0
- package/bundles/expo-supabase/prompts/quality.md +143 -0
- package/bundles/expo-supabase/supabase/migrations/001_initial_schema.sql +66 -0
- package/bundles/expo-supabase/tsconfig.json +21 -0
- package/bundles/expo-supabase/types/database.types.ts +76 -0
- package/bundles/expo-supabase/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/nextjs-landing/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/nextjs-landing/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/nextjs-landing/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-landing/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/nextjs-landing/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/nextjs-landing/.claude/hooks/README.md +9 -0
- package/bundles/nextjs-landing/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/nextjs-landing/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/nextjs-landing/.claude/rules/lattice-core.md +20 -0
- package/bundles/nextjs-landing/.claude/settings.json +38 -0
- package/bundles/nextjs-landing/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-landing/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/nextjs-landing/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/nextjs-landing/.cursor/mcp.json +34 -0
- package/bundles/nextjs-landing/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/nextjs-landing/.cursor/rules.md +95 -0
- package/bundles/nextjs-landing/.env.example +24 -0
- package/bundles/nextjs-landing/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/nextjs-landing/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/nextjs-landing/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/nextjs-landing/.github/copilot-instructions.md +37 -0
- package/bundles/nextjs-landing/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/nextjs-landing/.github/workflows/ci.yml +26 -0
- package/bundles/nextjs-landing/.gitmessage +24 -0
- package/bundles/nextjs-landing/.lattice/config.json +9 -0
- package/bundles/nextjs-landing/.lattice/dependency-security.json +46 -0
- package/bundles/nextjs-landing/.lattice/prompts/01-feature.md +36 -0
- package/bundles/nextjs-landing/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/nextjs-landing/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/nextjs-landing/.lattice/prompts/04-test.md +31 -0
- package/bundles/nextjs-landing/.lattice/prompts/05-polish.md +38 -0
- package/bundles/nextjs-landing/.lattice/prompts/06-security.md +32 -0
- package/bundles/nextjs-landing/.lattice/release-workflow.json +55 -0
- package/bundles/nextjs-landing/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/nextjs-landing/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/nextjs-landing/.mcp.json +35 -0
- package/bundles/nextjs-landing/AGENTS.md +58 -0
- package/bundles/nextjs-landing/CLAUDE.md +76 -0
- package/bundles/nextjs-landing/PROJECT_GUIDE.md +216 -0
- package/bundles/nextjs-landing/README.md +39 -0
- package/bundles/nextjs-landing/app/api/example/[id]/route.ts +34 -0
- package/bundles/nextjs-landing/app/api/example/route.ts +24 -0
- package/bundles/nextjs-landing/app/api/health/route.ts +8 -0
- package/bundles/nextjs-landing/app/global-error.tsx +17 -0
- package/bundles/nextjs-landing/app/globals.css +55 -0
- package/bundles/nextjs-landing/app/layout.tsx +22 -0
- package/bundles/nextjs-landing/app/page.tsx +42 -0
- package/bundles/nextjs-landing/components/ui/button.tsx +39 -0
- package/bundles/nextjs-landing/components/ui/card.tsx +53 -0
- package/bundles/nextjs-landing/components/ui/index.ts +4 -0
- package/bundles/nextjs-landing/components/ui/input.tsx +23 -0
- package/bundles/nextjs-landing/components/ui/spinner.tsx +22 -0
- package/bundles/nextjs-landing/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/nextjs-landing/docs/rules/codex.md +62 -0
- package/bundles/nextjs-landing/eslint.config.mjs +16 -0
- package/bundles/nextjs-landing/instrumentation-client.ts +30 -0
- package/bundles/nextjs-landing/instrumentation.ts +12 -0
- package/bundles/nextjs-landing/jest.config.mjs +16 -0
- package/bundles/nextjs-landing/jest.setup.js +1 -0
- package/bundles/nextjs-landing/lib/utils.ts +6 -0
- package/bundles/nextjs-landing/next-env.d.ts +5 -0
- package/bundles/nextjs-landing/next.config.ts +15 -0
- package/bundles/nextjs-landing/package.json +48 -0
- package/bundles/nextjs-landing/postcss.config.mjs +7 -0
- package/bundles/nextjs-landing/prompts/01-feature-implementation.md +9 -0
- package/bundles/nextjs-landing/prompts/02-bug-fix.md +10 -0
- package/bundles/nextjs-landing/prompts/03-safe-refactor.md +8 -0
- package/bundles/nextjs-landing/prompts/04-test-first.md +8 -0
- package/bundles/nextjs-landing/prompts/05-ui-polish.md +8 -0
- package/bundles/nextjs-landing/prompts/06-security-review.md +7 -0
- package/bundles/nextjs-landing/prompts/quality.md +269 -0
- package/bundles/nextjs-landing/sentry.edge.config.ts +15 -0
- package/bundles/nextjs-landing/sentry.server.config.ts +15 -0
- package/bundles/nextjs-landing/tsconfig.json +42 -0
- package/bundles/nextjs-landing/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/nextjs-saas/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/nextjs-saas/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/nextjs-saas/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-saas/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/nextjs-saas/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/nextjs-saas/.claude/hooks/README.md +9 -0
- package/bundles/nextjs-saas/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/nextjs-saas/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/nextjs-saas/.claude/rules/lattice-core.md +20 -0
- package/bundles/nextjs-saas/.claude/settings.json +38 -0
- package/bundles/nextjs-saas/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-saas/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/nextjs-saas/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/nextjs-saas/.cursor/mcp.json +34 -0
- package/bundles/nextjs-saas/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/nextjs-saas/.cursor/rules.md +95 -0
- package/bundles/nextjs-saas/.env.example +48 -0
- package/bundles/nextjs-saas/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/nextjs-saas/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/nextjs-saas/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/nextjs-saas/.github/copilot-instructions.md +37 -0
- package/bundles/nextjs-saas/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/nextjs-saas/.github/workflows/ci.yml +26 -0
- package/bundles/nextjs-saas/.gitmessage +24 -0
- package/bundles/nextjs-saas/.lattice/config.json +9 -0
- package/bundles/nextjs-saas/.lattice/dependency-security.json +46 -0
- package/bundles/nextjs-saas/.lattice/prompts/01-feature.md +36 -0
- package/bundles/nextjs-saas/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/nextjs-saas/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/nextjs-saas/.lattice/prompts/04-test.md +31 -0
- package/bundles/nextjs-saas/.lattice/prompts/05-polish.md +38 -0
- package/bundles/nextjs-saas/.lattice/prompts/06-security.md +32 -0
- package/bundles/nextjs-saas/.lattice/prompts/quality.md +269 -0
- package/bundles/nextjs-saas/.lattice/release-workflow.json +55 -0
- package/bundles/nextjs-saas/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/nextjs-saas/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/nextjs-saas/.mcp.json +35 -0
- package/bundles/nextjs-saas/AGENTS.md +58 -0
- package/bundles/nextjs-saas/CLAUDE.md +76 -0
- package/bundles/nextjs-saas/PROJECT_GUIDE.md +279 -0
- package/bundles/nextjs-saas/README.md +42 -0
- package/bundles/nextjs-saas/app/(auth)/forgot-password/page.tsx +101 -0
- package/bundles/nextjs-saas/app/(auth)/layout.tsx +7 -0
- package/bundles/nextjs-saas/app/(auth)/login/page.tsx +108 -0
- package/bundles/nextjs-saas/app/(auth)/reset-password/page.tsx +135 -0
- package/bundles/nextjs-saas/app/(auth)/signup/page.tsx +144 -0
- package/bundles/nextjs-saas/app/(dashboard)/dashboard/page.tsx +80 -0
- package/bundles/nextjs-saas/app/(dashboard)/layout.tsx +49 -0
- package/bundles/nextjs-saas/app/api/billing/checkout/route.ts +47 -0
- package/bundles/nextjs-saas/app/api/billing/portal/route.ts +29 -0
- package/bundles/nextjs-saas/app/api/example/[id]/route.ts +93 -0
- package/bundles/nextjs-saas/app/api/example/route.ts +61 -0
- package/bundles/nextjs-saas/app/api/health/route.ts +8 -0
- package/bundles/nextjs-saas/app/api/stripe/webhook/route.ts +57 -0
- package/bundles/nextjs-saas/app/api/user/route.ts +49 -0
- package/bundles/nextjs-saas/app/auth/callback/route.ts +16 -0
- package/bundles/nextjs-saas/app/auth/signout/route.ts +10 -0
- package/bundles/nextjs-saas/app/global-error.tsx +17 -0
- package/bundles/nextjs-saas/app/globals.css +55 -0
- package/bundles/nextjs-saas/app/layout.tsx +22 -0
- package/bundles/nextjs-saas/app/page.tsx +84 -0
- package/bundles/nextjs-saas/components/ui/button.tsx +39 -0
- package/bundles/nextjs-saas/components/ui/card.tsx +53 -0
- package/bundles/nextjs-saas/components/ui/index.ts +4 -0
- package/bundles/nextjs-saas/components/ui/input.tsx +23 -0
- package/bundles/nextjs-saas/components/ui/spinner.tsx +22 -0
- package/bundles/nextjs-saas/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/nextjs-saas/docs/rules/codex.md +62 -0
- package/bundles/nextjs-saas/eslint.config.mjs +16 -0
- package/bundles/nextjs-saas/instrumentation-client.ts +30 -0
- package/bundles/nextjs-saas/instrumentation.ts +12 -0
- package/bundles/nextjs-saas/jest.config.mjs +16 -0
- package/bundles/nextjs-saas/jest.setup.js +1 -0
- package/bundles/nextjs-saas/lib/auth/password-recovery.ts +94 -0
- package/bundles/nextjs-saas/lib/stripe.ts +10 -0
- package/bundles/nextjs-saas/lib/supabase/admin.ts +14 -0
- package/bundles/nextjs-saas/lib/supabase/client.ts +8 -0
- package/bundles/nextjs-saas/lib/supabase/server.ts +27 -0
- package/bundles/nextjs-saas/lib/utils.ts +6 -0
- package/bundles/nextjs-saas/next-env.d.ts +5 -0
- package/bundles/nextjs-saas/next.config.ts +15 -0
- package/bundles/nextjs-saas/package.json +50 -0
- package/bundles/nextjs-saas/postcss.config.mjs +7 -0
- package/bundles/nextjs-saas/prompts/01-feature-implementation.md +92 -0
- package/bundles/nextjs-saas/prompts/02-bug-fix.md +78 -0
- package/bundles/nextjs-saas/prompts/03-safe-refactor.md +82 -0
- package/bundles/nextjs-saas/prompts/04-test-first.md +80 -0
- package/bundles/nextjs-saas/prompts/05-ui-polish.md +82 -0
- package/bundles/nextjs-saas/prompts/06-security-review.md +74 -0
- package/bundles/nextjs-saas/proxy.ts +61 -0
- package/bundles/nextjs-saas/sentry.edge.config.ts +15 -0
- package/bundles/nextjs-saas/sentry.server.config.ts +15 -0
- package/bundles/nextjs-saas/supabase/migrations/001_initial_schema.sql +66 -0
- package/bundles/nextjs-saas/supabase/migrations/002_billing.sql +17 -0
- package/bundles/nextjs-saas/tsconfig.json +42 -0
- package/bundles/nextjs-saas/types/database.types.ts +76 -0
- package/bundles/nextjs-saas/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/bundles/nextjs-trpc/.agents/skills/lattice-feature-handoff/SKILL.md +31 -0
- package/bundles/nextjs-trpc/.agents/skills/lattice-release-manager/SKILL.md +29 -0
- package/bundles/nextjs-trpc/.agents/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-trpc/.claude/agents/lattice-feature-agent.md +10 -0
- package/bundles/nextjs-trpc/.claude/agents/lattice-release-manager.md +9 -0
- package/bundles/nextjs-trpc/.claude/hooks/README.md +9 -0
- package/bundles/nextjs-trpc/.claude/hooks/pre-commit-verify.mjs +10 -0
- package/bundles/nextjs-trpc/.claude/hooks/pre-tool-use.mjs +51 -0
- package/bundles/nextjs-trpc/.claude/rules/lattice-core.md +20 -0
- package/bundles/nextjs-trpc/.claude/settings.json +38 -0
- package/bundles/nextjs-trpc/.claude/skills/lattice-verify/SKILL.md +27 -0
- package/bundles/nextjs-trpc/.codex/agents/lattice-feature-agent.toml +9 -0
- package/bundles/nextjs-trpc/.codex/agents/lattice-release-manager.toml +9 -0
- package/bundles/nextjs-trpc/.cursor/mcp.json +34 -0
- package/bundles/nextjs-trpc/.cursor/rules/lattice-core.mdc +35 -0
- package/bundles/nextjs-trpc/.cursor/rules.md +95 -0
- package/bundles/nextjs-trpc/.env.example +21 -0
- package/bundles/nextjs-trpc/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/bundles/nextjs-trpc/.github/agents/lattice-feature-agent.agent.md +7 -0
- package/bundles/nextjs-trpc/.github/agents/lattice-release-manager.agent.md +7 -0
- package/bundles/nextjs-trpc/.github/copilot-instructions.md +37 -0
- package/bundles/nextjs-trpc/.github/instructions/lattice-core.instructions.md +11 -0
- package/bundles/nextjs-trpc/.github/workflows/ci.yml +26 -0
- package/bundles/nextjs-trpc/.gitmessage +24 -0
- package/bundles/nextjs-trpc/.lattice/config.json +9 -0
- package/bundles/nextjs-trpc/.lattice/dependency-security.json +46 -0
- package/bundles/nextjs-trpc/.lattice/prompts/01-feature.md +36 -0
- package/bundles/nextjs-trpc/.lattice/prompts/02-bugfix.md +32 -0
- package/bundles/nextjs-trpc/.lattice/prompts/03-refactor.md +30 -0
- package/bundles/nextjs-trpc/.lattice/prompts/04-test.md +31 -0
- package/bundles/nextjs-trpc/.lattice/prompts/05-polish.md +38 -0
- package/bundles/nextjs-trpc/.lattice/prompts/06-security.md +32 -0
- package/bundles/nextjs-trpc/.lattice/release-workflow.json +55 -0
- package/bundles/nextjs-trpc/.lattice/schemas/feature-handoff.schema.json +264 -0
- package/bundles/nextjs-trpc/.lattice/templates/feature-handoff.json +52 -0
- package/bundles/nextjs-trpc/.mcp.json +35 -0
- package/bundles/nextjs-trpc/AGENTS.md +58 -0
- package/bundles/nextjs-trpc/CLAUDE.md +76 -0
- package/bundles/nextjs-trpc/PROJECT_GUIDE.md +187 -0
- package/bundles/nextjs-trpc/README.md +49 -0
- package/bundles/nextjs-trpc/app/api/trpc/[trpc]/route.ts +12 -0
- package/bundles/nextjs-trpc/app/global-error.tsx +17 -0
- package/bundles/nextjs-trpc/app/globals.css +55 -0
- package/bundles/nextjs-trpc/app/layout.tsx +19 -0
- package/bundles/nextjs-trpc/app/page.tsx +16 -0
- package/bundles/nextjs-trpc/app/providers.tsx +24 -0
- package/bundles/nextjs-trpc/components/ui/button.tsx +39 -0
- package/bundles/nextjs-trpc/components/ui/card.tsx +53 -0
- package/bundles/nextjs-trpc/components/ui/index.ts +4 -0
- package/bundles/nextjs-trpc/components/ui/input.tsx +23 -0
- package/bundles/nextjs-trpc/components/ui/spinner.tsx +22 -0
- package/bundles/nextjs-trpc/components/user-list.tsx +17 -0
- package/bundles/nextjs-trpc/docs/LATTICE_RELEASE_WORKFLOW.md +53 -0
- package/bundles/nextjs-trpc/docs/rules/codex.md +62 -0
- package/bundles/nextjs-trpc/eslint.config.mjs +16 -0
- package/bundles/nextjs-trpc/instrumentation-client.ts +20 -0
- package/bundles/nextjs-trpc/instrumentation.ts +12 -0
- package/bundles/nextjs-trpc/jest.config.mjs +16 -0
- package/bundles/nextjs-trpc/jest.setup.js +1 -0
- package/bundles/nextjs-trpc/lib/db.ts +17 -0
- package/bundles/nextjs-trpc/lib/trpc/react.tsx +7 -0
- package/bundles/nextjs-trpc/lib/utils.ts +6 -0
- package/bundles/nextjs-trpc/next-env.d.ts +5 -0
- package/bundles/nextjs-trpc/next.config.ts +15 -0
- package/bundles/nextjs-trpc/package.json +62 -0
- package/bundles/nextjs-trpc/postcss.config.mjs +7 -0
- package/bundles/nextjs-trpc/prisma/schema.prisma +41 -0
- package/bundles/nextjs-trpc/prisma.config.ts +10 -0
- package/bundles/nextjs-trpc/prompts/01-feature-implementation.md +9 -0
- package/bundles/nextjs-trpc/prompts/02-bug-fix.md +9 -0
- package/bundles/nextjs-trpc/prompts/03-safe-refactor.md +8 -0
- package/bundles/nextjs-trpc/prompts/04-test-first.md +8 -0
- package/bundles/nextjs-trpc/prompts/05-ui-polish.md +7 -0
- package/bundles/nextjs-trpc/prompts/06-security-review.md +7 -0
- package/bundles/nextjs-trpc/prompts/quality.md +269 -0
- package/bundles/nextjs-trpc/sentry.edge.config.ts +15 -0
- package/bundles/nextjs-trpc/sentry.server.config.ts +15 -0
- package/bundles/nextjs-trpc/server/routers/_app.test.ts +60 -0
- package/bundles/nextjs-trpc/server/routers/_app.ts +25 -0
- package/bundles/nextjs-trpc/server/trpc.ts +12 -0
- package/bundles/nextjs-trpc/tsconfig.json +42 -0
- package/bundles/nextjs-trpc/vendor/m2ai-braces-3.0.3-m2ai.1.tar +0 -0
- package/dist/agent-install-session.d.ts +17 -0
- package/dist/agent-install-session.d.ts.map +1 -0
- package/dist/agent-install-session.js +106 -0
- package/dist/agent-install-session.js.map +1 -0
- package/dist/agent-install-session.test.d.ts +2 -0
- package/dist/agent-install-session.test.d.ts.map +1 -0
- package/dist/agent-install-session.test.js +62 -0
- package/dist/agent-install-session.test.js.map +1 -0
- package/dist/agent-install.d.ts +41 -0
- package/dist/agent-install.d.ts.map +1 -0
- package/dist/agent-install.js +42 -0
- package/dist/agent-install.js.map +1 -0
- package/dist/agent-install.test.d.ts +2 -0
- package/dist/agent-install.test.d.ts.map +1 -0
- package/dist/agent-install.test.js +58 -0
- package/dist/agent-install.test.js.map +1 -0
- package/dist/apply-merge.test.d.ts +2 -0
- package/dist/apply-merge.test.d.ts.map +1 -0
- package/dist/apply-merge.test.js +123 -0
- package/dist/apply-merge.test.js.map +1 -0
- package/dist/bundle-download.d.ts +23 -0
- package/dist/bundle-download.d.ts.map +1 -0
- package/dist/bundle-download.js +180 -0
- package/dist/bundle-download.js.map +1 -0
- package/dist/bundle-download.test.d.ts +2 -0
- package/dist/bundle-download.test.d.ts.map +1 -0
- package/dist/bundle-download.test.js +200 -0
- package/dist/bundle-download.test.js.map +1 -0
- package/dist/bundle-rollback.test.d.ts +2 -0
- package/dist/bundle-rollback.test.d.ts.map +1 -0
- package/dist/bundle-rollback.test.js +127 -0
- package/dist/bundle-rollback.test.js.map +1 -0
- package/dist/bundle-scaffold.test.d.ts +2 -0
- package/dist/bundle-scaffold.test.d.ts.map +1 -0
- package/dist/bundle-scaffold.test.js +105 -0
- package/dist/bundle-scaffold.test.js.map +1 -0
- package/dist/commands.d.ts +132 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +2012 -0
- package/dist/commands.js.map +1 -0
- package/dist/credential-store.d.ts +27 -0
- package/dist/credential-store.d.ts.map +1 -0
- package/dist/credential-store.js +48 -0
- package/dist/credential-store.js.map +1 -0
- package/dist/credential-store.test.d.ts +2 -0
- package/dist/credential-store.test.d.ts.map +1 -0
- package/dist/credential-store.test.js +80 -0
- package/dist/credential-store.test.js.map +1 -0
- package/dist/delivered-project-files.d.ts +31 -0
- package/dist/delivered-project-files.d.ts.map +1 -0
- package/dist/delivered-project-files.js +158 -0
- package/dist/delivered-project-files.js.map +1 -0
- package/dist/delivered-project-files.test.d.ts +2 -0
- package/dist/delivered-project-files.test.d.ts.map +1 -0
- package/dist/delivered-project-files.test.js +107 -0
- package/dist/delivered-project-files.test.js.map +1 -0
- package/dist/doctor-agents.test.d.ts +2 -0
- package/dist/doctor-agents.test.d.ts.map +1 -0
- package/dist/doctor-agents.test.js +65 -0
- package/dist/doctor-agents.test.js.map +1 -0
- package/dist/hermes-install.d.ts +102 -0
- package/dist/hermes-install.d.ts.map +1 -0
- package/dist/hermes-install.js +641 -0
- package/dist/hermes-install.js.map +1 -0
- package/dist/hermes-install.test.d.ts +2 -0
- package/dist/hermes-install.test.d.ts.map +1 -0
- package/dist/hermes-install.test.js +866 -0
- package/dist/hermes-install.test.js.map +1 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5311 -0
- package/dist/index.js.map +1 -0
- package/dist/openclaw-install.d.ts +67 -0
- package/dist/openclaw-install.d.ts.map +1 -0
- package/dist/openclaw-install.js +358 -0
- package/dist/openclaw-install.js.map +1 -0
- package/dist/openclaw-install.test.d.ts +2 -0
- package/dist/openclaw-install.test.d.ts.map +1 -0
- package/dist/openclaw-install.test.js +296 -0
- package/dist/openclaw-install.test.js.map +1 -0
- package/dist/password-recovery-bundle.test.d.ts +2 -0
- package/dist/password-recovery-bundle.test.d.ts.map +1 -0
- package/dist/password-recovery-bundle.test.js +132 -0
- package/dist/password-recovery-bundle.test.js.map +1 -0
- package/dist/project-bundle-env.d.ts +19 -0
- package/dist/project-bundle-env.d.ts.map +1 -0
- package/dist/project-bundle-env.js +111 -0
- package/dist/project-bundle-env.js.map +1 -0
- package/dist/project-bundle-env.test.d.ts +2 -0
- package/dist/project-bundle-env.test.d.ts.map +1 -0
- package/dist/project-bundle-env.test.js +93 -0
- package/dist/project-bundle-env.test.js.map +1 -0
- package/dist/project-path-confinement.test.d.ts +2 -0
- package/dist/project-path-confinement.test.d.ts.map +1 -0
- package/dist/project-path-confinement.test.js +222 -0
- package/dist/project-path-confinement.test.js.map +1 -0
- package/dist/security/api.d.ts +18 -0
- package/dist/security/api.d.ts.map +1 -0
- package/dist/security/api.js +238 -0
- package/dist/security/api.js.map +1 -0
- package/dist/security/api.test.d.ts +2 -0
- package/dist/security/api.test.d.ts.map +1 -0
- package/dist/security/api.test.js +220 -0
- package/dist/security/api.test.js.map +1 -0
- package/dist/security/artifact.d.ts +10 -0
- package/dist/security/artifact.d.ts.map +1 -0
- package/dist/security/artifact.js +283 -0
- package/dist/security/artifact.js.map +1 -0
- package/dist/security/artifact.test.d.ts +2 -0
- package/dist/security/artifact.test.d.ts.map +1 -0
- package/dist/security/artifact.test.js +229 -0
- package/dist/security/artifact.test.js.map +1 -0
- package/dist/security/commands.d.ts +42 -0
- package/dist/security/commands.d.ts.map +1 -0
- package/dist/security/commands.js +825 -0
- package/dist/security/commands.js.map +1 -0
- package/dist/security/commands.test.d.ts +2 -0
- package/dist/security/commands.test.d.ts.map +1 -0
- package/dist/security/commands.test.js +1051 -0
- package/dist/security/commands.test.js.map +1 -0
- package/dist/security/connection.d.ts +19 -0
- package/dist/security/connection.d.ts.map +1 -0
- package/dist/security/connection.js +145 -0
- package/dist/security/connection.js.map +1 -0
- package/dist/security/connection.test.d.ts +2 -0
- package/dist/security/connection.test.d.ts.map +1 -0
- package/dist/security/connection.test.js +62 -0
- package/dist/security/connection.test.js.map +1 -0
- package/dist/security/index-routing.test.d.ts +2 -0
- package/dist/security/index-routing.test.d.ts.map +1 -0
- package/dist/security/index-routing.test.js +169 -0
- package/dist/security/index-routing.test.js.map +1 -0
- package/dist/security/journal.d.ts +26 -0
- package/dist/security/journal.d.ts.map +1 -0
- package/dist/security/journal.js +441 -0
- package/dist/security/journal.js.map +1 -0
- package/dist/security/journal.test.d.ts +2 -0
- package/dist/security/journal.test.d.ts.map +1 -0
- package/dist/security/journal.test.js +226 -0
- package/dist/security/journal.test.js.map +1 -0
- package/dist/security/package-verifier.d.ts +15 -0
- package/dist/security/package-verifier.d.ts.map +1 -0
- package/dist/security/package-verifier.js +65 -0
- package/dist/security/package-verifier.js.map +1 -0
- package/dist/security/package-verifier.test.d.ts +2 -0
- package/dist/security/package-verifier.test.d.ts.map +1 -0
- package/dist/security/package-verifier.test.js +57 -0
- package/dist/security/package-verifier.test.js.map +1 -0
- package/dist/security/runtime.d.ts +43 -0
- package/dist/security/runtime.d.ts.map +1 -0
- package/dist/security/runtime.js +81 -0
- package/dist/security/runtime.js.map +1 -0
- package/dist/security/runtime.test.d.ts +2 -0
- package/dist/security/runtime.test.d.ts.map +1 -0
- package/dist/security/runtime.test.js +88 -0
- package/dist/security/runtime.test.js.map +1 -0
- package/dist/security/types.d.ts +169 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security/types.js +3 -0
- package/dist/security/types.js.map +1 -0
- package/dist/security-runtime/LICENSE +202 -0
- package/dist/security-runtime/NOTICE +16 -0
- package/dist/security-runtime/artifact-manifest.json +960 -0
- package/dist/security-runtime/bin/vsg.mjs +5 -0
- package/dist/security-runtime/lattice-catalog.json +176585 -0
- package/dist/security-runtime/lattice-runtime.mjs +90 -0
- package/dist/security-runtime/licensing/candidates/apache-source-license-evidence.json +478 -0
- package/dist/security-runtime/licensing/candidates/betterleaks-generic-api-key.json +76 -0
- package/dist/security-runtime/licensing/candidates/betterleaks-jwt.json +74 -0
- package/dist/security-runtime/licensing/candidates/blackberry-additional-source-declaration-evidence.json +276 -0
- package/dist/security-runtime/licensing/candidates/blackberry-source-declaration-evidence.json +369 -0
- package/dist/security-runtime/licensing/candidates/cado-source-declaration-evidence.json +283 -0
- package/dist/security-runtime/licensing/candidates/commercial-rule-candidate.json +280 -0
- package/dist/security-runtime/licensing/candidates/commercial-runtime-release.json +389 -0
- package/dist/security-runtime/licensing/candidates/data-feed-evidence.json +259 -0
- package/dist/security-runtime/licensing/candidates/gitlab-sast-rule-identities.json +178 -0
- package/dist/security-runtime/licensing/candidates/gitlab-sast-source-evidence.json +1824 -0
- package/dist/security-runtime/licensing/candidates/gitlab-sast-source-notice-bundle.json +45 -0
- package/dist/security-runtime/licensing/candidates/malpedia-source-license-evidence.json +41 -0
- package/dist/security-runtime/licensing/candidates/rule-license-evidence.json +376 -0
- package/dist/security-runtime/licensing/candidates/runner-image-composition.json +1500 -0
- package/dist/security-runtime/licensing/candidates/selected-runtime-coverage.json +91 -0
- package/dist/security-runtime/licensing/candidates/yara-forge-20260927.json +58502 -0
- package/dist/security-runtime/licensing/commercial-components.json +379 -0
- package/dist/security-runtime/licensing/notices/betterleaks-1.9.0-MIT.txt +21 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/28a9529c7d0bb4dc51f4bf5c116a3d16ef247a052f7591466768ddf563fd1cf5.txt +428 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/33acf42afdd03163d75fe856cb431e70710e0b5deede3362e6d1c4697cfe1c6a.txt +28 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986.txt +674 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/4fecb8fcaf80fd9a6315bca5225ee43377015b46a0459c2925495be885cab19a.txt +21 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/811515f8723aa4007de2fbb75a364127fdec51cda534b87a7ea8f2fef2f96dd7.txt +24 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/814442c3987a2c0411171ad8ad43118e8c08f85c9c5d1dcdecc9787c7ca8689b.txt +664 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/8a959b2413d0c83ca1b3ce9153076c649abf2e4bbfebbb493fb24c2074a1eb8a.txt +38 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/995182e185856186fbbe49f150409ae026aa4cda7e94f83c6ed737cbb77fa9c7.txt +23 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/9b8e1305e46d1dd4011b6ed933c9f6223e08c9c4625001c5fd75a34fa930f616.txt +202 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a01f1a28424bfe7f49a23d339a01847cfa9ace3864d5e7295051772a4bae2271.txt +38 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a5681bf9b05db14d86776930017c647ad9e6e56ff6bbcfdf21e5848288dfaf1b.txt +165 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/a5b8bd9ae96c630bbf229d61c5c5def20274e00be39abfd2fab4d7879ceab462.txt +24 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/ada32b3747ce108d5e8bab7918af94abbd03f2fe2a46304f74c6333bbfd914ed.txt +8 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4.txt +201 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/c738ae413cf561f174e34f6961f8ca458aae2369a73640dda6234c629b98bcc4.txt +2732 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/ce41c9a2c986277f7f9ab146a2195c1888a5d8da062e22d7c9c88666bb7c38cd.txt +17 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/d5f4c36a0528e01cfdd9e4fc60879201d3af49b41c520200643d321fe53da5f9.txt +17 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/e08c368ccd455f85c7fb9e04830e730f26e9dbb5c0f77bc88e09a5b0cb37df5b.txt +38 -0
- package/dist/security-runtime/licensing/notices/candidate-sources/sha256/e9efb2f7daf11bb6379c06c16b870dddcecc4a10f8704bb8aad807529c8b3c97.txt +8 -0
- package/dist/security-runtime/licensing/notices/gitleaks-generic-origin-782f3104-MIT.txt +21 -0
- package/dist/security-runtime/package.json +17 -0
- package/dist/security-runtime/playbook/README.md +76 -0
- package/dist/security-runtime/playbook/access-control.md +56 -0
- package/dist/security-runtime/playbook/ai-features.md +53 -0
- package/dist/security-runtime/playbook/desktop-python.md +59 -0
- package/dist/security-runtime/playbook/input-handling.md +59 -0
- package/dist/security-runtime/playbook/mobile-expo.md +59 -0
- package/dist/security-runtime/playbook/payments.md +53 -0
- package/dist/security-runtime/playbook/privacy-account-recovery.md +59 -0
- package/dist/security-runtime/playbook/secrets-config.md +47 -0
- package/dist/security-runtime/playbook/supabase.md +48 -0
- package/dist/security-runtime/playbook/supply-chain.md +62 -0
- package/dist/security-runtime/playbook/webhooks-integrations.md +52 -0
- package/dist/security-runtime/rules/vsg-ai-key-in-client.yaml +41 -0
- package/dist/security-runtime/rules/vsg-ai-output-raw-html.yaml +35 -0
- package/dist/security-runtime/rules/vsg-angular-sanitizer-bypass.yaml +23 -0
- package/dist/security-runtime/rules/vsg-expo-token-in-asyncstorage.yaml +22 -0
- package/dist/security-runtime/rules/vsg-flask-public-bind.yaml +32 -0
- package/dist/security-runtime/rules/vsg-nextjs-action-write-no-auth.yaml +144 -0
- package/dist/security-runtime/rules/vsg-nextjs-inline-action-write-no-auth.yaml +161 -0
- package/dist/security-runtime/rules/vsg-nextjs-route-write-no-auth.yaml +193 -0
- package/dist/security-runtime/rules/vsg-nosql-where-injection.yaml +36 -0
- package/dist/security-runtime/rules/vsg-python-pickle-load.yaml +26 -0
- package/dist/security-runtime/rules/vsg-python-requests-verify-false.yaml +34 -0
- package/dist/security-runtime/rules/vsg-python-subprocess-shell.yaml +23 -0
- package/dist/security-runtime/rules/vsg-stripe-client-price.yaml +51 -0
- package/dist/security-runtime/rules/vsg-stripe-webhook-no-signature.yaml +31 -0
- package/dist/security-runtime/rules/vsg-stripe-webhook-pages-no-signature.yaml +51 -0
- package/dist/security-runtime/rules/vsg-supabase-service-role-in-client.yaml +27 -0
- package/dist/security-runtime/rules/vsg-supabase-service-role-public-env.yaml +24 -0
- package/dist/security-runtime/rules/vsg-xml-external-entities.yaml +19 -0
- package/dist/security-runtime/src/agent/brief.mjs +95 -0
- package/dist/security-runtime/src/agent/discover.mjs +108 -0
- package/dist/security-runtime/src/agent/exec.mjs +64 -0
- package/dist/security-runtime/src/agent/ingest.mjs +213 -0
- package/dist/security-runtime/src/agent/report.mjs +94 -0
- package/dist/security-runtime/src/agent/schema.mjs +62 -0
- package/dist/security-runtime/src/agent/tokenize.mjs +38 -0
- package/dist/security-runtime/src/checks/README.md +8 -0
- package/dist/security-runtime/src/checks/_lib/gitfiles.mjs +35 -0
- package/dist/security-runtime/src/checks/_lib/lockfiles.mjs +135 -0
- package/dist/security-runtime/src/checks/_lib/osv.mjs +6 -0
- package/dist/security-runtime/src/checks/_lib/secrets.mjs +381 -0
- package/dist/security-runtime/src/checks/_lib/targets.mjs +79 -0
- package/dist/security-runtime/src/checks/agent-review/index.mjs +52 -0
- package/dist/security-runtime/src/checks/agent-surface/config.mjs +223 -0
- package/dist/security-runtime/src/checks/agent-surface/index.mjs +113 -0
- package/dist/security-runtime/src/checks/agent-surface/instructions.mjs +87 -0
- package/dist/security-runtime/src/checks/agent-surface/patterns.mjs +68 -0
- package/dist/security-runtime/src/checks/agent-surface/unicode.mjs +88 -0
- package/dist/security-runtime/src/checks/code/auth-helpers.mjs +659 -0
- package/dist/security-runtime/src/checks/code/index.mjs +617 -0
- package/dist/security-runtime/src/checks/code/opengrep-container.mjs +85 -0
- package/dist/security-runtime/src/checks/code/opengrep-runner.mjs +86 -0
- package/dist/security-runtime/src/checks/code/selected-opengrep-runner.mjs +112 -0
- package/dist/security-runtime/src/checks/dep-change/guarddog.mjs +142 -0
- package/dist/security-runtime/src/checks/dep-change/index.mjs +316 -0
- package/dist/security-runtime/src/checks/dep-change/registries.mjs +199 -0
- package/dist/security-runtime/src/checks/deps-vuln/index.mjs +305 -0
- package/dist/security-runtime/src/checks/desktop-config/index.mjs +112 -0
- package/dist/security-runtime/src/checks/dns-mail/index.mjs +71 -0
- package/dist/security-runtime/src/checks/iac/index.mjs +83 -0
- package/dist/security-runtime/src/checks/install-posture/index.mjs +123 -0
- package/dist/security-runtime/src/checks/licenses/index.mjs +143 -0
- package/dist/security-runtime/src/checks/live-basic/index.mjs +77 -0
- package/dist/security-runtime/src/checks/live-js-secrets/index.mjs +100 -0
- package/dist/security-runtime/src/checks/malware/clamav.mjs +17 -0
- package/dist/security-runtime/src/checks/malware/defender.mjs +43 -0
- package/dist/security-runtime/src/checks/malware/index.mjs +186 -0
- package/dist/security-runtime/src/checks/malware/targets.mjs +147 -0
- package/dist/security-runtime/src/checks/malware/yara-runner.mjs +201 -0
- package/dist/security-runtime/src/checks/malware/yara.mjs +124 -0
- package/dist/security-runtime/src/checks/mobile-config/index.mjs +173 -0
- package/dist/security-runtime/src/checks/mobile-config/mobsf.mjs +105 -0
- package/dist/security-runtime/src/checks/posture/index.mjs +133 -0
- package/dist/security-runtime/src/checks/project-scripts/index.mjs +187 -0
- package/dist/security-runtime/src/checks/sbom/index.mjs +98 -0
- package/dist/security-runtime/src/checks/secrets-build/index.mjs +63 -0
- package/dist/security-runtime/src/checks/secrets-history/index.mjs +112 -0
- package/dist/security-runtime/src/checks/secrets-tree/env.mjs +120 -0
- package/dist/security-runtime/src/checks/secrets-tree/index.mjs +73 -0
- package/dist/security-runtime/src/checks/supabase-static/index.mjs +240 -0
- package/dist/security-runtime/src/checks/supabase-static/sql.mjs +557 -0
- package/dist/security-runtime/src/checks/workflows/index.mjs +93 -0
- package/dist/security-runtime/src/cli/deep.mjs +13 -0
- package/dist/security-runtime/src/cli/doctor.mjs +82 -0
- package/dist/security-runtime/src/cli/index.mjs +60 -0
- package/dist/security-runtime/src/cli/live-common.mjs +27 -0
- package/dist/security-runtime/src/cli/parse.mjs +56 -0
- package/dist/security-runtime/src/cli/portfolio.mjs +34 -0
- package/dist/security-runtime/src/cli/release.mjs +28 -0
- package/dist/security-runtime/src/cli/report.mjs +18 -0
- package/dist/security-runtime/src/cli/run-common.mjs +54 -0
- package/dist/security-runtime/src/cli/scan.mjs +12 -0
- package/dist/security-runtime/src/cli/setup.mjs +59 -0
- package/dist/security-runtime/src/cli/url.mjs +38 -0
- package/dist/security-runtime/src/core/accept.mjs +141 -0
- package/dist/security-runtime/src/core/archive.mjs +114 -0
- package/dist/security-runtime/src/core/base-ref.mjs +43 -0
- package/dist/security-runtime/src/core/checks.mjs +39 -0
- package/dist/security-runtime/src/core/detect.mjs +90 -0
- package/dist/security-runtime/src/core/errors.mjs +5 -0
- package/dist/security-runtime/src/core/fetch.mjs +139 -0
- package/dist/security-runtime/src/core/findings.mjs +177 -0
- package/dist/security-runtime/src/core/gate.mjs +131 -0
- package/dist/security-runtime/src/core/installer.mjs +255 -0
- package/dist/security-runtime/src/core/lock.mjs +38 -0
- package/dist/security-runtime/src/core/nested.mjs +107 -0
- package/dist/security-runtime/src/core/paths.mjs +20 -0
- package/dist/security-runtime/src/core/pipeline.mjs +196 -0
- package/dist/security-runtime/src/core/registry.mjs +81 -0
- package/dist/security-runtime/src/core/report.mjs +128 -0
- package/dist/security-runtime/src/core/runner.mjs +86 -0
- package/dist/security-runtime/src/core/sarif.mjs +141 -0
- package/dist/security-runtime/src/lattice/artifact.mjs +1201 -0
- package/dist/security-runtime/src/lattice/execution.mjs +45 -0
- package/dist/security-runtime/src/lattice/export.mjs +215 -0
- package/dist/security-runtime/src/lattice/provenance.mjs +70 -0
- package/dist/security-runtime/src/lattice/runtime.mjs +158 -0
- package/dist/security-runtime/src/lattice/selected-runtime.mjs +189 -0
- package/dist/security-runtime/src/lattice/source-acquirer.mjs +51 -0
- package/dist/security-runtime/src/lattice/source-fetcher.mjs +88 -0
- package/dist/security-runtime/src/lattice/transport.mjs +232 -0
- package/dist/security-runtime/src/live/analyze.mjs +101 -0
- package/dist/security-runtime/src/live/exposure.mjs +79 -0
- package/dist/security-runtime/src/live/secrets.mjs +31 -0
- package/dist/security-runtime/src/live/session.mjs +98 -0
- package/dist/security-runtime/src/live/targets.mjs +81 -0
- package/dist/security-runtime/src/portfolio/index.mjs +116 -0
- package/dist/security-runtime/tools/registry/betterleaks.json +29 -0
- package/dist/security-runtime/tools/registry/guarddog.json +9 -0
- package/dist/security-runtime/tools/registry/mobsf.json +10 -0
- package/dist/security-runtime/tools/registry/opengrep.json +28 -0
- package/dist/security-runtime/tools/registry/osv-scanner.json +14 -0
- package/dist/security-runtime/tools/registry/semgrep-rules.json +21 -0
- package/dist/security-runtime/tools/registry/trivy.json +30 -0
- package/dist/security-runtime/tools/registry/yara-forge-exclusions.json +14 -0
- package/dist/security-runtime/tools/registry/yara-forge.json +22 -0
- package/dist/security-runtime/tools/registry/yara-runner.json +10 -0
- package/dist/security-runtime/tools/registry/yara-x.json +24 -0
- package/dist/security-runtime/tools/registry/zizmor.json +30 -0
- package/dist/spinner.d.ts +16 -0
- package/dist/spinner.d.ts.map +1 -0
- package/dist/spinner.js +72 -0
- package/dist/spinner.js.map +1 -0
- package/dist/verify.test.d.ts +2 -0
- package/dist/verify.test.d.ts.map +1 -0
- package/dist/verify.test.js +98 -0
- package/dist/verify.test.js.map +1 -0
- package/package.json +73 -4
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Host side of the YARA scan. YARA-X and the YARA Forge package are pinned registry entries; the work happens in yara-runner.mjs:
|
|
2
|
+
// - Linux host: the runner runs natively; filtered and compiled rules are cached in the tools cache (never in the scanned repository).
|
|
3
|
+
// - Any other host (Windows): the runner runs in the pinned Linux container with the YARA-X linux build, the rules zip, the vsg source
|
|
4
|
+
// and the scan target mounted read-only, no network, a read-only root and a tmpfs /tmp. Nothing is written to a host mount, so no
|
|
5
|
+
// compiled rules and no extracted rule source ever reach the host disk (where Defender quarantines them).
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { resolveTool } from '../../core/installer.mjs';
|
|
11
|
+
import { defaultRegistryDir, packageRoot, platformKey, toolsDir } from '../../core/paths.mjs';
|
|
12
|
+
import { YARA_MAX_FILE_BYTES } from './targets.mjs';
|
|
13
|
+
import { assertStandaloneExecution } from '../../lattice/execution.mjs';
|
|
14
|
+
import { withTransientDockerSource } from '../../lattice/source-acquirer.mjs';
|
|
15
|
+
|
|
16
|
+
const RUNNER = path.join(packageRoot, 'src', 'checks', 'malware', 'yara-runner.mjs');
|
|
17
|
+
export const YARA_TIMEOUT_SEC = 900;
|
|
18
|
+
|
|
19
|
+
/** Rules recorded as excluded for the pinned YARA Forge release (tools/registry/yara-forge-exclusions.json). */
|
|
20
|
+
export function loadExclusions(forgeVersion, dir = defaultRegistryDir) {
|
|
21
|
+
try {
|
|
22
|
+
const data = JSON.parse(fs.readFileSync(path.join(dir, 'yara-forge-exclusions.json'), 'utf8'));
|
|
23
|
+
return data.forgeVersion === forgeVersion && Array.isArray(data.excluded) ? data.excluded : [];
|
|
24
|
+
} catch { return []; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const lastJson = (stdout) => {
|
|
28
|
+
for (const line of String(stdout).split('\n').reverse()) {
|
|
29
|
+
if (!line.startsWith('{')) continue;
|
|
30
|
+
try { return JSON.parse(line); } catch { /* keep looking */ }
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Runs YARA over root-relative `files`. Returns
|
|
37
|
+
* { status: 'ran', mode, forge, matches, scanned, scanErrors, durationMs } or { status: 'not_run', reason }.
|
|
38
|
+
* Extra rules (a host file path) come from `extraRules`, used by tests and by VSG_YARA_EXTRA_RULES.
|
|
39
|
+
*/
|
|
40
|
+
export async function runYara(ctx, { root, files, extraRules, timeoutSec = YARA_TIMEOUT_SEC }) {
|
|
41
|
+
assertStandaloneExecution({ allowQualification: true });
|
|
42
|
+
if (ctx.selectedRuntime?.kind === 'lattice-artifact') {
|
|
43
|
+
const selected = ctx.selectedRuntime.selectedRuntime?.yara;
|
|
44
|
+
if (!selected) return { status: 'not_run', reason: 'The selected YARA runtime contract is missing; mixed-pack fallback is refused.' };
|
|
45
|
+
if (extraRules !== undefined || process.env.VSG_YARA_EXTRA_RULES) return { status: 'not_run', reason: 'Selected YARA runtime refuses caller and environment extra rules.' };
|
|
46
|
+
const yaraX = ctx.registry?.get('yara-x');
|
|
47
|
+
const runner = ctx.registry?.get('yara-runner');
|
|
48
|
+
const yr = yaraX ? resolveTool(yaraX) : null;
|
|
49
|
+
if (!yr || !runner?.image) return { status: 'not_run', reason: 'The verified YARA-X binary or pinned Linux runner image is unavailable. Run vsg setup --repair.' };
|
|
50
|
+
const timeoutMs = (timeoutSec + 240) * 1000;
|
|
51
|
+
try {
|
|
52
|
+
return await withTransientDockerSource(ctx, { image: runner.image, source: selected.source, label: 'yara' }, async ({ volume }) => {
|
|
53
|
+
const name = `vsg-selected-yara-${process.pid}-${crypto.randomBytes(4).toString('hex')}`;
|
|
54
|
+
const inventory = path.join(ctx.selectedRuntime.root, ...selected.inventory.path.split('/'));
|
|
55
|
+
const args = [
|
|
56
|
+
'run', '--pull', 'never', '--rm', '--init', '-i', '--name', name, '--network', 'none', '--read-only', '--tmpfs', '/tmp:rw,exec,size=2g', '--memory', '4g', '--cap-drop', 'ALL', '--security-opt', 'no-new-privileges',
|
|
57
|
+
'-v', `${path.join(packageRoot, 'src')}:/vsg/src:ro`, '-v', `${yr.path}:/vsg/in/yr:ro`,
|
|
58
|
+
'-v', `${inventory}:/vsg/in/inventory.json:ro`, '-v', `${volume}:/vsg/selected-source:ro`, '-v', `${root}:/scan:ro`,
|
|
59
|
+
'-w', '/scan', runner.image, 'node', '/vsg/src/checks/malware/yara-runner.mjs',
|
|
60
|
+
'--yr', '/vsg/in/yr', '--forge', '/vsg/selected-source/source.bin', '--inventory', '/vsg/in/inventory.json', '--root', '/scan', '--work', '/tmp',
|
|
61
|
+
];
|
|
62
|
+
const input = JSON.stringify({
|
|
63
|
+
files, excludeRules: selected.excludedRules, selectionDigest: selected.selectionDigest, holdDigest: selected.holdDigest,
|
|
64
|
+
maxFileBytes: YARA_MAX_FILE_BYTES, timeoutSec,
|
|
65
|
+
});
|
|
66
|
+
const result = await ctx.runProcess({ command: 'docker', args, input, timeoutMs, maxOutputBytes: 256 * 1024 * 1024 });
|
|
67
|
+
if (result.status !== 'ran') {
|
|
68
|
+
await ctx.runProcess({ command: 'docker', args: ['rm', '-f', name], timeoutMs: 30_000 });
|
|
69
|
+
return { status: 'not_run', reason: `Selected YARA ${result.reason}` };
|
|
70
|
+
}
|
|
71
|
+
const out = lastJson(result.stdout);
|
|
72
|
+
if (!out) return { status: 'not_run', reason: `Selected YARA runner produced no result (exit ${result.exitCode}).` };
|
|
73
|
+
if (!out.ok) return { status: 'not_run', reason: `Selected YARA runner failed: ${out.error}` };
|
|
74
|
+
if (out.forge?.selectionDigest !== selected.selectionDigest || out.forge?.holdDigest !== selected.holdDigest) {
|
|
75
|
+
return { status: 'not_run', reason: 'Selected YARA runner returned a different selection identity.' };
|
|
76
|
+
}
|
|
77
|
+
return { status: 'ran', mode: 'selected-container', forge: out.forge, matches: out.matches, scanned: out.scanned, scanErrors: out.scanErrors, durationMs: out.durationMs };
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return { status: 'not_run', reason: `Selected YARA ${error.message}` };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (extraRules === undefined) extraRules = process.env.VSG_YARA_EXTRA_RULES;
|
|
84
|
+
const yaraX = ctx.registry?.get('yara-x');
|
|
85
|
+
const forge = ctx.registry?.get('yara-forge');
|
|
86
|
+
if (!yaraX || !forge) return { status: 'not_run', reason: 'YARA-X or the YARA Forge package is not in the tool registry.' };
|
|
87
|
+
const yr = resolveTool(yaraX);
|
|
88
|
+
const zip = resolveTool(forge);
|
|
89
|
+
if (!yr || !zip) return { status: 'not_run', reason: `${!yr ? 'YARA-X' : 'The YARA Forge package'} is not installed. Run vsg setup.` };
|
|
90
|
+
const input = JSON.stringify({ files, excludeRules: loadExclusions(forge.version), maxFileBytes: YARA_MAX_FILE_BYTES, timeoutSec });
|
|
91
|
+
const timeoutMs = (timeoutSec + 120) * 1000;
|
|
92
|
+
let r;
|
|
93
|
+
let mode;
|
|
94
|
+
if (yr.platform === platformKey() && process.platform === 'linux') {
|
|
95
|
+
mode = 'native';
|
|
96
|
+
// Compiled rules are derived, mutable cache data. Keep them outside the
|
|
97
|
+
// verified immutable install tree so tampering with the cache cannot make
|
|
98
|
+
// the installed rules package appear trusted.
|
|
99
|
+
const cache = path.join(toolsDir(), 'cache', 'yara-forge', forge.version, 'compiled');
|
|
100
|
+
const args = [RUNNER, '--yr', yr.path, '--forge', zip.path, '--root', root, '--cache', cache, '--work', os.tmpdir(), ...(extraRules ? ['--extra', extraRules] : [])];
|
|
101
|
+
r = await ctx.runProcess({ command: process.execPath, args, cwd: root, input, timeoutMs });
|
|
102
|
+
} else {
|
|
103
|
+
mode = 'container';
|
|
104
|
+
const runner = ctx.registry.get('yara-runner');
|
|
105
|
+
if (!runner?.image) return { status: 'not_run', reason: 'The yara-runner container image is not in the tool registry.' };
|
|
106
|
+
const docker = await ctx.runProcess({ command: 'docker', args: ['version', '--format', '{{.Server.Version}}'], timeoutMs: 20_000 });
|
|
107
|
+
if (docker.status !== 'ran' || docker.exitCode !== 0) return { status: 'not_run', reason: 'Docker is not available (the engine is not running), so YARA cannot run on this host. Start Docker and rerun.' };
|
|
108
|
+
const name = `vsg-yara-${process.pid}-${crypto.randomBytes(4).toString('hex')}`;
|
|
109
|
+
const args = [
|
|
110
|
+
'run', '--rm', '-i', '--name', name, '--network', 'none', '--read-only', '--tmpfs', '/tmp:rw,exec,size=2g', '--memory', '4g', '--cap-drop', 'ALL', '--security-opt', 'no-new-privileges',
|
|
111
|
+
'-v', `${path.join(packageRoot, 'src')}:/vsg/src:ro`, '-v', `${yr.path}:/vsg/in/yr:ro`, '-v', `${zip.path}:/vsg/in/forge.zip:ro`, '-v', `${root}:/scan:ro`,
|
|
112
|
+
...(extraRules ? ['-v', `${path.resolve(extraRules)}:/vsg/in/extra.yar:ro`] : []),
|
|
113
|
+
'-w', '/scan', runner.image, 'node', '/vsg/src/checks/malware/yara-runner.mjs', '--yr', '/vsg/in/yr', '--forge', '/vsg/in/forge.zip', '--root', '/scan', '--work', '/tmp',
|
|
114
|
+
...(extraRules ? ['--extra', '/vsg/in/extra.yar'] : []),
|
|
115
|
+
];
|
|
116
|
+
r = await ctx.runProcess({ command: 'docker', args, input, timeoutMs });
|
|
117
|
+
if (r.status !== 'ran') await ctx.runProcess({ command: 'docker', args: ['rm', '-f', name], timeoutMs: 30_000 }); // a timed-out client leaves the container running
|
|
118
|
+
}
|
|
119
|
+
if (r.status !== 'ran') return { status: 'not_run', reason: `YARA ${r.reason}` };
|
|
120
|
+
const out = lastJson(r.stdout);
|
|
121
|
+
if (!out) return { status: 'not_run', reason: `YARA runner produced no result (exit ${r.exitCode}): ${String(r.stderr).trim().split('\n')[0]?.slice(0, 200) ?? ''}` };
|
|
122
|
+
if (!out.ok) return { status: 'not_run', reason: `YARA runner failed: ${out.error}` };
|
|
123
|
+
return { status: 'ran', mode, forge: out.forge, matches: out.matches, scanned: out.scanned, scanErrors: out.scanErrors, durationMs: out.durationMs };
|
|
124
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// mobile-config (REQ-032): static review of Expo / React Native configuration, plus optional MobSF over a built APK.
|
|
2
|
+
// app.json and app.config.json are parsed as data. app.config.js/.ts/.mjs/.cjs are only read as text (regex heuristics): vsg never executes repository code.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { listFiles } from '../../core/detect.mjs';
|
|
6
|
+
import { importMobsf, runMobsf } from './mobsf.mjs';
|
|
7
|
+
|
|
8
|
+
export const id = 'mobile-config';
|
|
9
|
+
export const title = 'Mobile (Expo) configuration';
|
|
10
|
+
export const modes = ['scan', 'deep', 'release'];
|
|
11
|
+
export const requiredIn = [];
|
|
12
|
+
export const timeoutSec = 1000;
|
|
13
|
+
|
|
14
|
+
const base = (f) => f.slice(f.lastIndexOf('/') + 1);
|
|
15
|
+
const dirOf = (f) => (f.includes('/') ? f.slice(0, f.lastIndexOf('/')) : '');
|
|
16
|
+
const readText = (repo, rel) => { try { return fs.readFileSync(path.join(repo, rel), 'utf8'); } catch { return ''; } };
|
|
17
|
+
const readJson = (repo, rel) => { try { return JSON.parse(readText(repo, rel)); } catch { return null; } };
|
|
18
|
+
const CONFIG_JSON = new Set(['app.json', 'app.config.json']);
|
|
19
|
+
const CONFIG_TEXT = /^app\.config\.(?:js|ts|mjs|cjs)$/;
|
|
20
|
+
|
|
21
|
+
export const HIGH_RISK_PERMISSIONS = {
|
|
22
|
+
READ_SMS: 'reads text messages',
|
|
23
|
+
RECEIVE_SMS: 'receives text messages',
|
|
24
|
+
READ_CALL_LOG: 'reads the call log',
|
|
25
|
+
SYSTEM_ALERT_WINDOW: 'draws over other apps (a common overlay-attack permission)',
|
|
26
|
+
REQUEST_INSTALL_PACKAGES: 'can prompt to install other apps',
|
|
27
|
+
MANAGE_EXTERNAL_STORAGE: 'all-files access to shared storage',
|
|
28
|
+
ACCESS_BACKGROUND_LOCATION: 'reads location while the app is in the background',
|
|
29
|
+
};
|
|
30
|
+
const PERM_RE = new RegExp(`\\b(?:android\\.permission\\.)?(${Object.keys(HIGH_RISK_PERMISSIONS).join('|')})\\b`, 'g');
|
|
31
|
+
|
|
32
|
+
/** Dependencies of the package.json in this directory or the nearest parent. */
|
|
33
|
+
function depsNear(repo, dir) {
|
|
34
|
+
for (let d = dir; ; d = dirOf(d)) {
|
|
35
|
+
const j = readJson(repo, d ? `${d}/package.json` : 'package.json');
|
|
36
|
+
if (j) return new Set(['dependencies', 'devDependencies', 'optionalDependencies'].flatMap((k) => Object.keys(j[k] ?? {})));
|
|
37
|
+
if (!d) return new Set();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const quoted = (s) => [...String(s).matchAll(/['"`]([^'"`]+)['"`]/g)].map((m) => m[1]);
|
|
42
|
+
|
|
43
|
+
/** Facts from a parsed Expo config object. */
|
|
44
|
+
function factsFromJson(raw) {
|
|
45
|
+
const e = raw?.expo && typeof raw.expo === 'object' ? raw.expo : raw ?? {};
|
|
46
|
+
const plugins = Array.isArray(e.plugins) ? e.plugins : [];
|
|
47
|
+
const bp = plugins.filter((p) => Array.isArray(p) && p[0] === 'expo-build-properties').map((p) => p[1] ?? {});
|
|
48
|
+
const ats = e.ios?.infoPlist?.NSAppTransportSecurity ?? {};
|
|
49
|
+
const filters = Array.isArray(e.android?.intentFilters) ? e.android.intentFilters : [];
|
|
50
|
+
return {
|
|
51
|
+
updatesSigned: !!e.updates?.codeSigningCertificate,
|
|
52
|
+
cleartext: e.android?.usesCleartextTraffic === true || bp.some((p) => p.android?.usesCleartextTraffic === true),
|
|
53
|
+
atsArbitrary: Object.entries(ats).some(([k, v]) => /^NSAllowsArbitraryLoads/.test(k) && v === true),
|
|
54
|
+
atsDomains: Object.keys(ats.NSExceptionDomains ?? {}),
|
|
55
|
+
permissions: [...new Set((Array.isArray(e.android?.permissions) ? e.android.permissions : []).map((p) => String(p).replace(/^android\.permission\./, '')).filter((p) => HIGH_RISK_PERMISSIONS[p]))],
|
|
56
|
+
schemes: [].concat(e.scheme ?? []).map(String),
|
|
57
|
+
intentFilters: filters.flatMap((f) => [].concat(f.data ?? []).map((d) => `${d.scheme ?? '*'}://${d.host ?? ''}${d.pathPrefix ?? d.path ?? ''}`)).concat(filters.length && !filters.some((f) => f.data) ? ['(no data element)'] : []),
|
|
58
|
+
hasFilters: filters.length > 0,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Same facts from the text of a JS/TS config (regex only; the file is never loaded). */
|
|
63
|
+
function factsFromText(text) {
|
|
64
|
+
const scheme = /\bscheme\s*:\s*(\[[^\]]*\]|['"`][^'"`]+['"`])/.exec(text);
|
|
65
|
+
return {
|
|
66
|
+
updatesSigned: /codeSigningCertificate/.test(text),
|
|
67
|
+
cleartext: /usesCleartextTraffic\s*:\s*true/.test(text),
|
|
68
|
+
atsArbitrary: /NSAllowsArbitraryLoads\w*\s*:\s*true/.test(text),
|
|
69
|
+
atsDomains: /NSExceptionDomains\s*:\s*\{\s*[^}\s]/.test(text) ? ['(see file)'] : [],
|
|
70
|
+
permissions: [...new Set([...text.matchAll(PERM_RE)].map((m) => m[1]))],
|
|
71
|
+
schemes: scheme ? quoted(scheme[1]) : [],
|
|
72
|
+
intentFilters: [],
|
|
73
|
+
hasFilters: /intentFilters\s*:/.test(text),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const lineOf = (text, re) => { const i = text.search(re); return i < 0 ? undefined : text.slice(0, i).split('\n').length; };
|
|
78
|
+
|
|
79
|
+
function findingsFor(file, text, facts, { heuristic, hasUpdates }) {
|
|
80
|
+
const confidence = heuristic ? 'low' : 'high';
|
|
81
|
+
const how = heuristic ? ' (found by reading the file as text; vsg does not run JavaScript or TypeScript configs, so confirm it)' : '';
|
|
82
|
+
const mk = (key, severity, title, description, fix, re) => ({
|
|
83
|
+
category: 'config', severity, confidence, tool: 'vsg', ruleId: `vsg/mobile/${key}`, key: `mobile:${key}`, title,
|
|
84
|
+
description: description + how, location: { file, line: lineOf(text, re) }, evidence: `file: ${file}`, fix,
|
|
85
|
+
});
|
|
86
|
+
const out = [];
|
|
87
|
+
if (hasUpdates && !facts.updatesSigned) {
|
|
88
|
+
out.push(mk('updates-unsigned', 'medium', 'expo-updates is installed but over-the-air updates are not code-signed',
|
|
89
|
+
'The app depends on expo-updates and the config has no updates.codeSigningCertificate, so the app accepts any update the update server returns. Anyone who can alter the update host or the CDN path can ship code to every installation.',
|
|
90
|
+
'Generate a code-signing certificate and key (npx expo-updates codesigning:generate), set updates.codeSigningCertificate and updates.codeSigningMetadata in the app config, and sign each update when publishing. Keep the private key out of the repository.', /expo-updates|"updates"|updates\s*:/));
|
|
91
|
+
}
|
|
92
|
+
if (facts.cleartext) {
|
|
93
|
+
out.push(mk('android-cleartext', 'medium', 'Android cleartext (HTTP) traffic is allowed',
|
|
94
|
+
'android.usesCleartextTraffic is true, so the Android app may send and receive data over plain HTTP that anyone on the network can read or change.',
|
|
95
|
+
'Remove usesCleartextTraffic (or set it to false) and serve every endpoint over HTTPS. If one development host needs HTTP, scope it with a network security config for debug builds only.', /usesCleartextTraffic/));
|
|
96
|
+
}
|
|
97
|
+
if (facts.atsArbitrary) {
|
|
98
|
+
out.push(mk('ios-ats-disabled', 'medium', 'iOS App Transport Security is disabled (NSAllowsArbitraryLoads)',
|
|
99
|
+
'NSAppTransportSecurity allows arbitrary loads, which turns off the HTTPS requirement for the whole iOS app.',
|
|
100
|
+
'Remove NSAllowsArbitraryLoads and move every endpoint to HTTPS. If one legacy host cannot, use a narrow NSExceptionDomains entry for that host only.', /NSAllowsArbitraryLoads/));
|
|
101
|
+
}
|
|
102
|
+
if (facts.atsDomains.length) {
|
|
103
|
+
out.push(mk('ios-ats-exceptions', 'medium', `iOS App Transport Security exception domains: ${facts.atsDomains.join(', ')}`,
|
|
104
|
+
'NSExceptionDomains weakens the HTTPS requirement for the listed hosts (cleartext HTTP or older TLS).',
|
|
105
|
+
'Remove each exception once the host serves modern TLS. Keep the list to hosts you control and review it before each release.', /NSExceptionDomains/));
|
|
106
|
+
}
|
|
107
|
+
for (const p of facts.permissions) {
|
|
108
|
+
out.push(mk(`permission-${p}`, 'low', `High-risk Android permission requested: ${p}`,
|
|
109
|
+
`The app asks for ${p}, which ${HIGH_RISK_PERMISSIONS[p]}. Google Play restricts several of these, and a compromised app with them can do real harm.`,
|
|
110
|
+
`Confirm the feature needs ${p}. If it does not, remove it from android.permissions (and from any plugin that adds it).`, new RegExp(p)));
|
|
111
|
+
}
|
|
112
|
+
if (facts.schemes.length) {
|
|
113
|
+
out.push(mk('deep-link-schemes', 'info', `Deep link scheme(s): ${facts.schemes.join(', ')}`,
|
|
114
|
+
'Custom URL schemes can be claimed by any other app on the device. Listed so the agent review can read the handlers (Linking, Expo Router routes) and check that links never carry tokens or trigger sensitive actions without confirmation.',
|
|
115
|
+
'Prefer HTTPS app links or universal links for sign-in and payment redirects; treat every link parameter as untrusted input. See playbook/mobile-expo.md.', /\bscheme\b/));
|
|
116
|
+
}
|
|
117
|
+
if (facts.hasFilters) {
|
|
118
|
+
out.push(mk('intent-filters', 'info', `Android intent filters declared${facts.intentFilters.length ? `: ${facts.intentFilters.slice(0, 5).join(', ')}` : ''}`,
|
|
119
|
+
'Intent filters make the app reachable from links and other apps. Listed so the agent review can read the handlers behind them.',
|
|
120
|
+
'Verify autoVerify app links where possible and validate all incoming data. See playbook/mobile-expo.md.', /intentFilters/));
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const isReactNative = (repo, detection) => detection.packageJsons.some((pj) => {
|
|
126
|
+
const j = readJson(repo, pj);
|
|
127
|
+
return j && ['dependencies', 'devDependencies'].some((k) => j[k]?.['react-native']);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export const applies = (ctx) => (ctx.detection.expo || isReactNative(ctx.repo, ctx.detection)
|
|
131
|
+
? true
|
|
132
|
+
: { applies: false, reason: 'no Expo or React Native project was detected' });
|
|
133
|
+
|
|
134
|
+
export async function run(ctx, deps = {}) {
|
|
135
|
+
const repo = ctx.repo;
|
|
136
|
+
const files = listFiles(repo);
|
|
137
|
+
const findings = [];
|
|
138
|
+
const configs = [];
|
|
139
|
+
for (const f of files) {
|
|
140
|
+
const b = base(f);
|
|
141
|
+
const deps_ = () => depsNear(repo, dirOf(f));
|
|
142
|
+
if (CONFIG_JSON.has(b)) {
|
|
143
|
+
const j = readJson(repo, f);
|
|
144
|
+
if (!j || typeof j !== 'object') { configs.push({ file: f, mode: 'unreadable' }); continue; }
|
|
145
|
+
const d = deps_();
|
|
146
|
+
if (!j.expo && !d.has('expo') && !d.has('react-native')) continue; // an app.json from some other tool
|
|
147
|
+
configs.push({ file: f, mode: 'json' });
|
|
148
|
+
findings.push(...findingsFor(f, readText(repo, f), factsFromJson(j), { heuristic: false, hasUpdates: d.has('expo-updates') }));
|
|
149
|
+
} else if (CONFIG_TEXT.test(b)) {
|
|
150
|
+
const text = readText(repo, f); // read as text only; never imported or executed
|
|
151
|
+
configs.push({ file: f, mode: 'text-heuristic' });
|
|
152
|
+
findings.push(...findingsFor(f, text, factsFromText(text), { heuristic: true, hasUpdates: deps_().has('expo-updates') }));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let mobsf;
|
|
157
|
+
const apk = ctx.config?.mobile?.apk ?? null;
|
|
158
|
+
if (!apk) mobsf = { status: 'skipped', reason: 'No mobile.apk is set in vsg.config.json, so MobSF did not run.' };
|
|
159
|
+
else {
|
|
160
|
+
const r = await runMobsf({ repo, apk, image: ctx.registry?.get('mobsf')?.image, runProcess: deps.runProcess ?? ctx.runProcess, fetchFn: deps.fetch });
|
|
161
|
+
if (r.status === 'ran') {
|
|
162
|
+
const imported = importMobsf(r.report, apk.replace(/\\/g, '/'));
|
|
163
|
+
findings.push(...imported);
|
|
164
|
+
mobsf = { status: 'ran', apk, imported: imported.length, image: ctx.registry?.get('mobsf')?.image };
|
|
165
|
+
} else mobsf = { status: r.status, apk, reason: r.reason };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const evidence = { configs, mobsf, note: configs.length ? undefined : 'No Expo config file (app.json, app.config.json, app.config.js/ts) was found.' };
|
|
169
|
+
const reason = mobsf.status === 'failed' ? `Config checks ran; MobSF failed: ${mobsf.reason}` : '';
|
|
170
|
+
return { status: 'ran', reason, findings, evidence };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export default { id, title, modes, requiredIn, timeoutSec, applies, run };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// MobSF (optional): static analysis of a built APK through the pinned container's REST API.
|
|
2
|
+
// Container: --rm, published on 127.0.0.1 only (Docker chooses the port), per-run API key passed through the environment, never in argv.
|
|
3
|
+
// API (MobSF v4.5.3): POST /api/v1/upload (multipart "file"), POST /api/v1/scan (form: hash, scan_type, file_name; returns the report),
|
|
4
|
+
// POST /api/v1/report_json (form: hash). Auth: the key in the Authorization header.
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
export const MOBSF_TIMEOUT_MS = 15 * 60 * 1000;
|
|
10
|
+
const MAX_FINDINGS = 100;
|
|
11
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
12
|
+
|
|
13
|
+
const clean = (s, n = 400) => String(s ?? '').replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, n);
|
|
14
|
+
|
|
15
|
+
/** High findings of a MobSF report as vsg raw findings (Medium, non-blocking, category config). */
|
|
16
|
+
export function importMobsf(report, apkRel) {
|
|
17
|
+
const out = [];
|
|
18
|
+
const add = (kind, rule, title, description) => {
|
|
19
|
+
if (out.length >= MAX_FINDINGS) return;
|
|
20
|
+
out.push({
|
|
21
|
+
category: 'config', severity: 'medium', confidence: 'medium', tool: 'MobSF', ruleId: `mobsf/${kind}/${rule}`, key: `mobsf:${kind}:${rule}`,
|
|
22
|
+
title: `MobSF (high): ${clean(title, 160) || rule}`,
|
|
23
|
+
description: `${clean(description)} MobSF rates this High; vsg reports it as Medium because static APK analysis has false positives. Confirm it in the app code before acting.`,
|
|
24
|
+
location: { file: apkRel }, evidence: `source: MobSF ${kind}\nrule: ${rule}`,
|
|
25
|
+
fix: 'Review the MobSF finding against the app source and fix or document it. See playbook/mobile-expo.md.',
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const manifest = report?.manifest_analysis;
|
|
29
|
+
for (const m of Array.isArray(manifest) ? manifest : manifest?.manifest_findings ?? []) {
|
|
30
|
+
if (m?.severity === 'high') add('manifest', m.rule ?? m.title, m.title, m.description);
|
|
31
|
+
}
|
|
32
|
+
for (const n of report?.network_security?.network_findings ?? []) {
|
|
33
|
+
if (n?.severity === 'high') add('network', crypto.createHash('sha256').update(String(n.description)).digest('hex').slice(0, 8), 'Network security configuration', n.description);
|
|
34
|
+
}
|
|
35
|
+
const code = report?.code_analysis;
|
|
36
|
+
for (const [rule, v] of Object.entries(code?.findings ?? code ?? {})) {
|
|
37
|
+
if (v?.metadata?.severity === 'high') add('code', rule, rule.replace(/_/g, ' '), `${v.metadata.description ?? ''} Files: ${Object.keys(v.files ?? {}).slice(0, 5).join(', ')}.`);
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Runs the whole flow. deps (tests): { runProcess, fetch }. Returns
|
|
44
|
+
* { status: 'ran', report } | { status: 'skipped' | 'failed', reason }. The container is always stopped afterwards.
|
|
45
|
+
*/
|
|
46
|
+
export async function runMobsf({ repo, apk, image, runProcess, fetchFn = fetch, timeoutMs = MOBSF_TIMEOUT_MS }) {
|
|
47
|
+
const abs = path.resolve(repo, apk);
|
|
48
|
+
let st;
|
|
49
|
+
try { st = fs.statSync(abs); } catch { return { status: 'failed', reason: `mobile.apk "${apk}" does not exist.` }; }
|
|
50
|
+
if (!st.isFile() || !/\.apk$/i.test(abs)) return { status: 'failed', reason: `mobile.apk "${apk}" is not an .apk file.` };
|
|
51
|
+
if (!image) return { status: 'skipped', reason: 'The MobSF image is not in the tool registry.' };
|
|
52
|
+
const docker = await runProcess({ command: 'docker', args: ['version', '--format', '{{.Server.Version}}'], timeoutMs: 20_000 });
|
|
53
|
+
if (docker.status !== 'ran' || docker.exitCode !== 0) return { status: 'skipped', reason: 'Docker is not available (not installed or the engine is not running), so MobSF did not run.' };
|
|
54
|
+
|
|
55
|
+
const deadline = Date.now() + timeoutMs;
|
|
56
|
+
const key = crypto.randomBytes(24).toString('hex');
|
|
57
|
+
const name = `vsg-mobsf-${process.pid}-${crypto.randomBytes(4).toString('hex')}`;
|
|
58
|
+
const left = () => Math.max(1000, deadline - Date.now());
|
|
59
|
+
// fetch's own abort timer is unref'd and a refused connection holds no handle: keep the event loop alive until the container is stopped
|
|
60
|
+
const keepAlive = setInterval(() => {}, 1000);
|
|
61
|
+
try {
|
|
62
|
+
const started = await runProcess({
|
|
63
|
+
command: 'docker', timeoutMs: Math.min(left(), 10 * 60_000), extraEnv: { MOBSF_API_KEY: key },
|
|
64
|
+
args: ['run', '-d', '--rm', '--name', name, '-p', '127.0.0.1::8000', '--security-opt', 'no-new-privileges', '-e', 'MOBSF_API_KEY', image],
|
|
65
|
+
});
|
|
66
|
+
if (started.status !== 'ran' || started.exitCode !== 0) return { status: 'failed', reason: `The MobSF container did not start: ${started.reason ?? String(started.stderr).trim().split('\n').pop()}` };
|
|
67
|
+
const portOut = await runProcess({ command: 'docker', args: ['port', name, '8000/tcp'], timeoutMs: 20_000 });
|
|
68
|
+
const port = /:(\d+)\s*$/m.exec(String(portOut.stdout).trim())?.[1];
|
|
69
|
+
if (!port) return { status: 'failed', reason: 'Could not read the published MobSF port.' };
|
|
70
|
+
const base = `http://127.0.0.1:${port}`;
|
|
71
|
+
const call = async (p, init, ms = Infinity) => {
|
|
72
|
+
const r = await fetchFn(`${base}${p}`, { ...init, headers: { Authorization: key, ...(init?.headers ?? {}) }, signal: AbortSignal.timeout(Math.min(left(), ms)) });
|
|
73
|
+
const text = await r.text();
|
|
74
|
+
let json; try { json = JSON.parse(text); } catch { /* not JSON */ }
|
|
75
|
+
return { ok: r.ok, status: r.status, json, text };
|
|
76
|
+
};
|
|
77
|
+
// ready: the API answers (401 without a key proves the app is up; with the key a bad request is fine)
|
|
78
|
+
let ready = false;
|
|
79
|
+
while (Date.now() < deadline && !ready) {
|
|
80
|
+
try { const r = await call('/api/v1/scans', { method: 'GET' }, 5000); ready = r.status < 500; } catch { /* not up yet */ }
|
|
81
|
+
if (!ready) await sleep(3000);
|
|
82
|
+
}
|
|
83
|
+
if (!ready) return { status: 'failed', reason: 'MobSF did not become ready before the 15 minute limit.' };
|
|
84
|
+
|
|
85
|
+
const form = new FormData();
|
|
86
|
+
form.append('file', new Blob([fs.readFileSync(abs)]), path.basename(abs));
|
|
87
|
+
const up = await call('/api/v1/upload', { method: 'POST', body: form });
|
|
88
|
+
if (!up.ok || !up.json?.hash) return { status: 'failed', reason: `MobSF upload failed (HTTP ${up.status}): ${clean(up.json?.error ?? up.text, 150)}` };
|
|
89
|
+
const scan = await call('/api/v1/scan', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ hash: up.json.hash, scan_type: up.json.scan_type ?? 'apk', file_name: up.json.file_name ?? path.basename(abs) }).toString() });
|
|
90
|
+
if (!scan.ok) return { status: 'failed', reason: `MobSF scan failed (HTTP ${scan.status}): ${clean(scan.json?.error ?? scan.text, 150)}` };
|
|
91
|
+
let report = scan.json;
|
|
92
|
+
if (!report?.manifest_analysis && !report?.code_analysis) {
|
|
93
|
+
const rep = await call('/api/v1/report_json', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ hash: up.json.hash }).toString() });
|
|
94
|
+
if (!rep.ok) return { status: 'failed', reason: `MobSF report failed (HTTP ${rep.status}).` };
|
|
95
|
+
report = rep.json;
|
|
96
|
+
}
|
|
97
|
+
return { status: 'ran', report };
|
|
98
|
+
} catch (e) {
|
|
99
|
+
return { status: 'failed', reason: `MobSF ${e.name === 'TimeoutError' ? 'timed out (15 minute limit)' : `failed: ${e.message}`}` };
|
|
100
|
+
} finally {
|
|
101
|
+
clearInterval(keepAlive);
|
|
102
|
+
const stop = await runProcess({ command: 'docker', args: ['stop', '-t', '5', name], timeoutMs: 60_000 });
|
|
103
|
+
if (stop.status !== 'ran' || stop.exitCode !== 0) await runProcess({ command: 'docker', args: ['rm', '-f', name], timeoutMs: 60_000 });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// posture (REQ-021): repository settings on GitHub. Warning only: findings are category "posture", which never blocks, and the check
|
|
2
|
+
// is never required. Needs a token (GH_TOKEN or GITHUB_TOKEN); without one it is not_run: no token. The token is sent only to
|
|
3
|
+
// api.github.com through the fetch wrapper and never appears in findings, evidence or logs.
|
|
4
|
+
export const id = 'posture';
|
|
5
|
+
export const title = 'GitHub repository posture';
|
|
6
|
+
export const modes = ['scan', 'deep', 'release'];
|
|
7
|
+
export const requiredIn = [];
|
|
8
|
+
export const timeoutSec = 180;
|
|
9
|
+
|
|
10
|
+
export const API = 'https://api.github.com';
|
|
11
|
+
const tokenFromEnv = () => process.env.GH_TOKEN || process.env.GITHUB_TOKEN || '';
|
|
12
|
+
|
|
13
|
+
/** Parses a github.com remote (https, ssh or scp-style, with or without credentials). Returns { owner, repo } or null. */
|
|
14
|
+
export function parseGithubRemote(url) {
|
|
15
|
+
const s = String(url ?? '').trim();
|
|
16
|
+
const m = /^(?:https?:\/\/(?:[^@/]+@)?|ssh:\/\/(?:[^@/]+@)?|git@)github\.com[/:]([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/i.exec(s);
|
|
17
|
+
return m ? { owner: m[1], repo: m[2] } : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function originOf(ctx) {
|
|
21
|
+
const r = await ctx.runProcess({ command: 'git', args: ['-C', ctx.repo, 'remote', 'get-url', 'origin'], cwd: ctx.repo, timeoutMs: 15_000 });
|
|
22
|
+
return r.status === 'ran' && r.exitCode === 0 ? parseGithubRemote(r.stdout) : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function applies(ctx) {
|
|
26
|
+
return (await originOf(ctx)) ? true : { applies: false, reason: 'no github.com "origin" remote' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const finding = (key, title, description, fix, severity = 'medium') => ({
|
|
30
|
+
tool: 'github-api', ruleId: key, key, category: 'posture', severity, confidence: 'high', title, description, fix,
|
|
31
|
+
location: { url: 'https://github.com' },
|
|
32
|
+
});
|
|
33
|
+
const unchecked = (what, why) => finding(`unchecked-${what}`, `Could not check ${what}`, `${why}. This setting was not verified.`, 'Run vsg with a token that can read repository settings (a fine-grained token with Administration: read).', 'info');
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Runs the four checks. `get(path)` returns { status, json }. Returns { findings, checked: { item: 'ok' | 'finding' | 'could not check' } }.
|
|
37
|
+
* Exported for tests with recorded responses.
|
|
38
|
+
*/
|
|
39
|
+
export async function checkPosture({ owner, repo, get }) {
|
|
40
|
+
const findings = [];
|
|
41
|
+
const checked = {};
|
|
42
|
+
const base = `/repos/${owner}/${repo}`;
|
|
43
|
+
const repoRes = await get(base);
|
|
44
|
+
if (repoRes.status !== 200) return { fatal: `GitHub returned HTTP ${repoRes.status} for the repository (check the token and that the repository exists)`, findings, checked };
|
|
45
|
+
const branch = repoRes.json?.default_branch;
|
|
46
|
+
|
|
47
|
+
// (a) default-branch protection. Classic protection only: a 404 "Branch not protected" means none; other statuses mean we could not tell.
|
|
48
|
+
if (!branch) {
|
|
49
|
+
checked.branchProtection = 'could not check';
|
|
50
|
+
findings.push(unchecked('default-branch protection', 'GitHub did not report a default branch'));
|
|
51
|
+
} else {
|
|
52
|
+
const r = await get(`${base}/branches/${encodeURIComponent(branch)}/protection`);
|
|
53
|
+
if (r.status === 200) checked.branchProtection = 'ok';
|
|
54
|
+
else if (r.status === 404 && /not protected/i.test(r.json?.message ?? '')) {
|
|
55
|
+
checked.branchProtection = 'finding';
|
|
56
|
+
findings.push(finding('branch-protection', `Default branch "${branch}" has no branch protection`,
|
|
57
|
+
'Anyone with write access can push directly to the default branch, or force-push it, without review or required checks. Only classic branch protection rules were checked; repository rulesets were not.',
|
|
58
|
+
'Enable branch protection or a ruleset on the default branch: require a pull request and the vsg status check, and block force pushes.'));
|
|
59
|
+
} else {
|
|
60
|
+
checked.branchProtection = 'could not check';
|
|
61
|
+
findings.push(unchecked('default-branch protection', `GitHub returned HTTP ${r.status}`));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// (b) secret scanning and push protection, from security_and_analysis (only returned to tokens with admin access)
|
|
66
|
+
const sa = repoRes.json?.security_and_analysis;
|
|
67
|
+
for (const [key, label, fix] of [
|
|
68
|
+
['secret_scanning', 'secret scanning', 'Enable secret scanning under Settings, Code security.'],
|
|
69
|
+
['secret_scanning_push_protection', 'secret scanning push protection', 'Enable push protection under Settings, Code security, so commits containing secrets are rejected.'],
|
|
70
|
+
]) {
|
|
71
|
+
const status = sa?.[key]?.status;
|
|
72
|
+
if (status === 'enabled') checked[key] = 'ok';
|
|
73
|
+
else if (status === 'disabled') {
|
|
74
|
+
checked[key] = 'finding';
|
|
75
|
+
findings.push(finding(key, `${label[0].toUpperCase()}${label.slice(1)} is disabled`, `GitHub ${label} is turned off for this repository.`, fix));
|
|
76
|
+
} else {
|
|
77
|
+
checked[key] = 'could not check';
|
|
78
|
+
findings.push(unchecked(label, 'GitHub did not return security_and_analysis for this token'));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// (c) Dependabot alerts: 204 enabled, 404 disabled
|
|
83
|
+
const alerts = await get(`${base}/vulnerability-alerts`);
|
|
84
|
+
if (alerts.status === 204) checked.dependabotAlerts = 'ok';
|
|
85
|
+
else if (alerts.status === 404) {
|
|
86
|
+
checked.dependabotAlerts = 'finding';
|
|
87
|
+
findings.push(finding('dependabot-alerts', 'Dependabot alerts are disabled', 'GitHub is not alerting on vulnerable dependencies in this repository.', 'Enable Dependabot alerts under Settings, Code security.'));
|
|
88
|
+
} else {
|
|
89
|
+
checked.dependabotAlerts = 'could not check';
|
|
90
|
+
findings.push(unchecked('Dependabot alerts', `GitHub returned HTTP ${alerts.status}`));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// (d) default GITHUB_TOKEN permissions for Actions
|
|
94
|
+
const perms = await get(`${base}/actions/permissions/workflow`);
|
|
95
|
+
if (perms.status === 200) {
|
|
96
|
+
if (perms.json?.default_workflow_permissions === 'write') {
|
|
97
|
+
checked.actionsTokenPermissions = 'finding';
|
|
98
|
+
findings.push(finding('actions-default-write', 'Actions default token permission is read and write',
|
|
99
|
+
'Every workflow that does not set its own permissions gets a GITHUB_TOKEN with write access to the repository.',
|
|
100
|
+
'Under Settings, Actions, General, set Workflow permissions to "Read repository contents and packages permissions", and grant write per job.'));
|
|
101
|
+
} else checked.actionsTokenPermissions = 'ok';
|
|
102
|
+
} else {
|
|
103
|
+
checked.actionsTokenPermissions = 'could not check';
|
|
104
|
+
findings.push(unchecked('default Actions token permissions', `GitHub returned HTTP ${perms.status}`));
|
|
105
|
+
}
|
|
106
|
+
for (const f of findings) f.location = { url: `https://github.com/${owner}/${repo}` };
|
|
107
|
+
return { findings, checked };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function run(ctx) {
|
|
111
|
+
const token = tokenFromEnv();
|
|
112
|
+
if (!token) return { status: 'not_run', reason: 'no token: set GH_TOKEN or GITHUB_TOKEN to check repository posture' };
|
|
113
|
+
const origin = await originOf(ctx);
|
|
114
|
+
if (!origin) return { status: 'not_applicable', reason: 'no github.com "origin" remote' };
|
|
115
|
+
const get = async (p) => {
|
|
116
|
+
const res = await ctx.fetch(`${API}${p}`, {
|
|
117
|
+
timeoutMs: 20_000,
|
|
118
|
+
headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28', 'User-Agent': 'vsg' },
|
|
119
|
+
});
|
|
120
|
+
let json = null;
|
|
121
|
+
try { json = res.body.length ? JSON.parse(res.body.toString('utf8')) : null; } catch { /* not JSON */ }
|
|
122
|
+
return { status: res.status, json };
|
|
123
|
+
};
|
|
124
|
+
const { findings, checked, fatal } = await checkPosture({ ...origin, get });
|
|
125
|
+
if (fatal) return { status: 'not_run', reason: fatal };
|
|
126
|
+
return {
|
|
127
|
+
status: 'ran', reason: '', findings,
|
|
128
|
+
dataVersions: { api: 'GitHub REST 2022-11-28' },
|
|
129
|
+
evidence: { repository: `${origin.owner}/${origin.repo}`, checked },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default { id, title, modes, requiredIn, timeoutSec, applies, run };
|