@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,101 @@
|
|
|
1
|
+
// Pure analyzers for live-basic: security headers, cookie flags and CORS. Each returns raw findings (category "live").
|
|
2
|
+
// Evidence never carries a cookie value or any response body.
|
|
3
|
+
export const UNTRUSTED_ORIGIN = 'https://untrusted.example';
|
|
4
|
+
|
|
5
|
+
const live = (f) => ({ category: 'live', confidence: 'high', tool: 'vsg-live', refs: [], ...f });
|
|
6
|
+
|
|
7
|
+
const HEADER_FIXES = {
|
|
8
|
+
'strict-transport-security': 'Send `Strict-Transport-Security: max-age=31536000; includeSubDomains` on every HTTPS response.',
|
|
9
|
+
'content-security-policy': "Send a Content-Security-Policy that limits script sources, for example `default-src 'self'`, and avoid 'unsafe-inline' for scripts (use nonces or hashes).",
|
|
10
|
+
'x-content-type-options': 'Send `X-Content-Type-Options: nosniff`.',
|
|
11
|
+
'frame-protection': "Send `Content-Security-Policy: frame-ancestors 'self'` (or `X-Frame-Options: SAMEORIGIN`).",
|
|
12
|
+
'referrer-policy': 'Send `Referrer-Policy: strict-origin-when-cross-origin` (or stricter).',
|
|
13
|
+
'permissions-policy': 'Send a Permissions-Policy that turns off features the site does not use, for example `camera=(), microphone=(), geolocation=()`.',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function missingHeader(key, title, severity, url, why) {
|
|
17
|
+
return live({ ruleId: `header-${key}`, key: `header:${key}`, severity, title, location: { url }, description: why, evidence: `Response from ${url} did not include this header.`, fix: HEADER_FIXES[key], cwe: [693] });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** headers: a Headers object from the entry page. */
|
|
21
|
+
export function analyzeHeaders(headers, url) {
|
|
22
|
+
const out = [];
|
|
23
|
+
const https = new URL(url).protocol === 'https:';
|
|
24
|
+
const get = (n) => headers.get(n);
|
|
25
|
+
if (https) {
|
|
26
|
+
const hsts = get('strict-transport-security');
|
|
27
|
+
if (!hsts) out.push(missingHeader('strict-transport-security', 'HSTS header missing', 'medium', url, 'Without Strict-Transport-Security a browser can be downgraded to plain HTTP by a network attacker.'));
|
|
28
|
+
else {
|
|
29
|
+
const age = Number(/max-age\s*=\s*(\d+)/i.exec(hsts)?.[1] ?? 0);
|
|
30
|
+
if (age < 15_552_000) out.push(live({ ruleId: 'header-hsts-short', key: 'header:hsts-short', severity: 'low', title: 'HSTS max-age is under 180 days', location: { url }, description: 'A short HSTS lifetime leaves returning visitors unprotected once it expires.', evidence: `Strict-Transport-Security: ${hsts}`, fix: HEADER_FIXES['strict-transport-security'], cwe: [319] }));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const csp = get('content-security-policy');
|
|
34
|
+
if (!csp) {
|
|
35
|
+
out.push(missingHeader('content-security-policy', 'Content-Security-Policy header missing', 'medium', url, 'A Content-Security-Policy limits what an injected script can do. Without one, any cross-site scripting flaw has full reach.'));
|
|
36
|
+
} else {
|
|
37
|
+
const directives = new Map(csp.split(';').map((d) => d.trim()).filter(Boolean).map((d) => { const [name, ...v] = d.split(/\s+/); return [name.toLowerCase(), v]; }));
|
|
38
|
+
const scripts = directives.get('script-src') ?? directives.get('default-src') ?? [];
|
|
39
|
+
if (scripts.includes("'unsafe-inline'") && !scripts.some((v) => /^'(nonce-|sha(256|384|512)-)/.test(v) || v === "'strict-dynamic'")) {
|
|
40
|
+
out.push(live({ ruleId: 'header-csp-unsafe-inline', key: 'header:csp-unsafe-inline', severity: 'low', title: "Content-Security-Policy allows 'unsafe-inline' scripts", location: { url }, description: "'unsafe-inline' lets injected inline scripts run, which removes most of the protection of the policy.", evidence: `script-src: ${scripts.join(' ')}`, fix: HEADER_FIXES['content-security-policy'], cwe: [693] }));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const frameAncestors = csp && /(^|;)\s*frame-ancestors\b/i.test(csp);
|
|
44
|
+
if (!get('x-frame-options') && !frameAncestors) out.push(missingHeader('frame-protection', 'No clickjacking protection (X-Frame-Options or frame-ancestors)', 'low', url, 'Another site can embed this page in a frame and trick visitors into clicking hidden controls.'));
|
|
45
|
+
if (!/nosniff/i.test(get('x-content-type-options') ?? '')) out.push(missingHeader('x-content-type-options', 'X-Content-Type-Options header missing', 'low', url, 'Browsers may guess content types and run uploaded files as scripts.'));
|
|
46
|
+
if (!get('referrer-policy')) out.push(missingHeader('referrer-policy', 'Referrer-Policy header missing', 'low', url, 'Full URLs, including private paths and query strings, can leak to other sites through the Referer header.'));
|
|
47
|
+
if (!get('permissions-policy')) out.push(missingHeader('permissions-policy', 'Permissions-Policy header missing', 'low', url, 'Browser features such as camera, microphone and geolocation are not restricted for this page or embedded content.'));
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const SESSION_LIKE = /(sess|sid\b|token|auth|jwt|login|sb-|csrf|xsrf|connect\.sid)/i;
|
|
52
|
+
|
|
53
|
+
/** setCookies: array of Set-Cookie header strings. Reports flags per cookie; the value is never read into a finding. */
|
|
54
|
+
export function analyzeCookies(setCookies, url) {
|
|
55
|
+
const out = [];
|
|
56
|
+
for (const raw of setCookies) {
|
|
57
|
+
const [pair, ...attrs] = raw.split(';').map((s) => s.trim());
|
|
58
|
+
const name = pair.split('=')[0];
|
|
59
|
+
const flags = new Set(attrs.map((a) => a.split('=')[0].toLowerCase()));
|
|
60
|
+
const sameSite = attrs.find((a) => /^samesite=/i.test(a))?.split('=')[1]?.toLowerCase();
|
|
61
|
+
const sessionLike = SESSION_LIKE.test(name);
|
|
62
|
+
const missing = [];
|
|
63
|
+
if (!flags.has('secure')) missing.push('Secure');
|
|
64
|
+
if (sessionLike && !flags.has('httponly')) missing.push('HttpOnly');
|
|
65
|
+
if (!sameSite) missing.push('SameSite');
|
|
66
|
+
else if (sameSite === 'none' && !flags.has('secure')) missing.push('Secure (required with SameSite=None)');
|
|
67
|
+
if (!missing.length) continue;
|
|
68
|
+
out.push(live({
|
|
69
|
+
ruleId: 'cookie-flags', key: `cookie:${name}`, severity: sessionLike && (missing.includes('Secure') || missing.includes('HttpOnly')) ? 'medium' : 'low',
|
|
70
|
+
title: `Cookie "${name}" is missing ${missing.join(', ')}`, location: { url },
|
|
71
|
+
description: `${sessionLike ? 'This looks like a session or token cookie. ' : ''}Without these flags the cookie can be read by page scripts, sent over plain HTTP, or sent on cross-site requests.`,
|
|
72
|
+
evidence: `Cookie ${name}: attributes set = ${[...flags].join(', ') || 'none'}; SameSite = ${sameSite ?? 'not set'}; value not recorded.`,
|
|
73
|
+
fix: 'Set `Secure; HttpOnly; SameSite=Lax` (or Strict) on session cookies, and at least `Secure; SameSite=Lax` on the others.', cwe: [614, 1004, 1275],
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** headers: response to a request that carried `Origin: https://untrusted.example`. */
|
|
80
|
+
export function analyzeCors(headers, url) {
|
|
81
|
+
const acao = headers.get('access-control-allow-origin');
|
|
82
|
+
const creds = (headers.get('access-control-allow-credentials') ?? '').toLowerCase() === 'true';
|
|
83
|
+
if (!acao) return [];
|
|
84
|
+
const fix = 'Allow only an explicit list of trusted origins, never echo the Origin header, and never combine a broad origin policy with Access-Control-Allow-Credentials.';
|
|
85
|
+
if (acao === UNTRUSTED_ORIGIN && creds) {
|
|
86
|
+
return [live({ ruleId: 'cors-reflected-credentials', key: 'cors:reflected-credentials', severity: 'high', confidence: 'confirmed', title: 'CORS reflects any origin and allows credentials', location: { url },
|
|
87
|
+
description: 'Any website can make credentialed requests to this site from a visitor\'s browser and read the responses, including account data.',
|
|
88
|
+
evidence: `Request Origin: ${UNTRUSTED_ORIGIN} returned Access-Control-Allow-Origin: ${acao} and Access-Control-Allow-Credentials: true.`, fix, cwe: [942] })];
|
|
89
|
+
}
|
|
90
|
+
if (acao === '*') {
|
|
91
|
+
return [live({ ruleId: 'cors-wildcard', key: 'cors:wildcard', severity: 'medium', title: 'CORS allows any origin (wildcard)', location: { url },
|
|
92
|
+
description: 'Any website can read responses from this URL. That is fine for public data and wrong for anything user-specific.',
|
|
93
|
+
evidence: `Access-Control-Allow-Origin: *${creds ? ' with Access-Control-Allow-Credentials: true (browsers reject this combination)' : ''}`, fix, cwe: [942] })];
|
|
94
|
+
}
|
|
95
|
+
if (acao === UNTRUSTED_ORIGIN) {
|
|
96
|
+
return [live({ ruleId: 'cors-reflected', key: 'cors:reflected', severity: 'medium', title: 'CORS echoes any requested origin', location: { url },
|
|
97
|
+
description: 'The server reflects the Origin header, so any website can read responses that do not need credentials.',
|
|
98
|
+
evidence: `Request Origin: ${UNTRUSTED_ORIGIN} returned Access-Control-Allow-Origin: ${acao}.`, fix, cwe: [942] })];
|
|
99
|
+
}
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Sensitive-file probes for live-basic: a fixed list of at most 15 well-known paths, read with ordinary GETs.
|
|
2
|
+
// A hit needs a content signature (an HTML error page is never a hit) and must differ from a random-path baseline,
|
|
3
|
+
// so sites that answer every path with 200 (soft 404) are not flagged. Evidence never contains file content beyond key names.
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import { sha256 } from '../core/findings.mjs';
|
|
6
|
+
|
|
7
|
+
const text = (b) => b.toString('utf8');
|
|
8
|
+
const looksHtml = (b, ct) => /html/i.test(ct ?? '') || /^\s*(<!doctype|<html|<head|<body)/i.test(text(b.subarray(0, 512)));
|
|
9
|
+
const ENV_LINE = /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_.]*\s*=/;
|
|
10
|
+
|
|
11
|
+
function envKeys(b) {
|
|
12
|
+
const lines = text(b).split(/\r?\n/).filter((l) => l.trim() && !l.trim().startsWith('#'));
|
|
13
|
+
const keys = lines.filter((l) => ENV_LINE.test(l)).map((l) => l.split('=')[0].replace(/^\s*export\s+/, '').trim());
|
|
14
|
+
return lines.length && keys.length / lines.length >= 0.6 ? keys : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const envCheck = (b, ct) => { const k = looksHtml(b, ct) ? null : envKeys(b); return k && `${k.length} variable name(s): ${k.slice(0, 8).join(', ')}${k.length > 8 ? ', ...' : ''} (values not recorded)`; };
|
|
18
|
+
const zipCheck = (b) => b.length > 4 && b[0] === 0x50 && b[1] === 0x4b && b[2] === 3 && b[3] === 4 && 'ZIP archive';
|
|
19
|
+
const sqlCheck = (b, ct) => !looksHtml(b, ct) && /(CREATE TABLE|INSERT INTO|DROP TABLE|-- MySQL dump|-- PostgreSQL database dump)/i.test(text(b)) && 'SQL dump statements';
|
|
20
|
+
|
|
21
|
+
/** The reviewed list. `check(buf, contentType)` returns a short description when the content has the file's signature. */
|
|
22
|
+
export const SENSITIVE_FILES = [
|
|
23
|
+
{ path: '/.env', kind: 'env', severity: 'high', what: 'environment file', check: envCheck },
|
|
24
|
+
{ path: '/.env.local', kind: 'env', severity: 'high', what: 'environment file', check: envCheck },
|
|
25
|
+
{ path: '/.env.production', kind: 'env', severity: 'high', what: 'environment file', check: envCheck },
|
|
26
|
+
{ path: '/.git/HEAD', kind: 'vcs', severity: 'high', what: 'Git repository metadata', check: (b) => /^(ref:\s*refs\/\S+|[0-9a-f]{40})\s*$/.test(text(b.subarray(0, 200))) && 'Git HEAD pointer' },
|
|
27
|
+
{ path: '/.git/config', kind: 'vcs', severity: 'high', what: 'Git repository configuration', check: (b, ct) => !looksHtml(b, ct) && /\[core\]/.test(text(b)) && /repositoryformatversion|\[remote |\[branch /.test(text(b)) && 'Git config sections' },
|
|
28
|
+
{ path: '/.svn/entries', kind: 'vcs', severity: 'high', what: 'Subversion metadata', check: (b) => /^\d+\r?\n(\r?\n)?(dir|file|svn:)/.test(text(b.subarray(0, 100))) && 'Subversion entries file' },
|
|
29
|
+
{ path: '/.DS_Store', kind: 'metadata', severity: 'medium', what: 'macOS folder metadata (lists file names)', check: (b) => b.length > 8 && b.readUInt32BE(0) === 1 && text(b.subarray(4, 8)) === 'Bud1' && 'DS_Store header' },
|
|
30
|
+
{ path: '/backup.zip', kind: 'backup', severity: 'high', what: 'backup archive', check: zipCheck },
|
|
31
|
+
{ path: '/backup.tar.gz', kind: 'backup', severity: 'high', what: 'backup archive', check: (b) => b.length > 2 && b[0] === 0x1f && b[1] === 0x8b && 'gzip archive' },
|
|
32
|
+
{ path: '/site.zip', kind: 'backup', severity: 'high', what: 'site archive', check: zipCheck },
|
|
33
|
+
{ path: '/www.zip', kind: 'backup', severity: 'high', what: 'site archive', check: zipCheck },
|
|
34
|
+
{ path: '/backup.sql', kind: 'backup', severity: 'high', what: 'database dump', check: sqlCheck },
|
|
35
|
+
{ path: '/db.sql', kind: 'backup', severity: 'high', what: 'database dump', check: sqlCheck },
|
|
36
|
+
{ path: '/dump.sql', kind: 'backup', severity: 'high', what: 'database dump', check: sqlCheck },
|
|
37
|
+
{ path: '/.htpasswd', kind: 'credentials', severity: 'high', what: 'password file', check: (b, ct) => !looksHtml(b, ct) && /^[^\s:]+:(\$apr1\$|\$2[aby]\$|\{SHA\}|\$5\$|\$6\$)/m.test(text(b)) && 'password hash entries' },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const FIX = {
|
|
41
|
+
env: 'Remove the file from the web root and the deployment, then rotate every value it held. Keep environment files out of anything the web server can serve.',
|
|
42
|
+
vcs: 'Remove the repository metadata from the deployed site (deploy build output only) and block dot-directories at the web server. Assume the source and history were copied; rotate any secret that was ever committed.',
|
|
43
|
+
metadata: 'Delete the file from the deployment and add it to .gitignore.',
|
|
44
|
+
backup: 'Remove the file from the web root. Treat its contents as exposed and rotate any credential or review any data it held.',
|
|
45
|
+
credentials: 'Remove the file from the web root and change every password it lists.',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Returns { findings, probed, baseline } using session.get. Baseline is a random path that cannot exist. */
|
|
49
|
+
export async function probeSensitiveFiles(session) {
|
|
50
|
+
const randomPath = `/vsg-${crypto.randomBytes(8).toString('hex')}`;
|
|
51
|
+
let baselineHash = null;
|
|
52
|
+
let baselineStatus = null;
|
|
53
|
+
try {
|
|
54
|
+
const base = await session.get(randomPath, { maxBytes: 65_536 });
|
|
55
|
+
baselineStatus = base.status;
|
|
56
|
+
baselineHash = sha256(base.body);
|
|
57
|
+
} catch { /* an unreadable baseline only removes the extra comparison; content signatures still apply */ }
|
|
58
|
+
const findings = [];
|
|
59
|
+
const probed = [];
|
|
60
|
+
for (const f of SENSITIVE_FILES) {
|
|
61
|
+
let res;
|
|
62
|
+
try { res = await session.get(f.path, { maxBytes: 65_536 }); } catch (e) { probed.push({ path: f.path, error: e.message }); continue; }
|
|
63
|
+
probed.push({ path: f.path, status: res.status });
|
|
64
|
+
if (res.status !== 200 && res.status !== 206) continue;
|
|
65
|
+
if (baselineHash && sha256(res.body) === baselineHash) continue; // same page as a random path: soft 404
|
|
66
|
+
const ct = res.headers.get('content-type') ?? '';
|
|
67
|
+
const sig = f.check(res.body, ct);
|
|
68
|
+
if (!sig) continue;
|
|
69
|
+
const url = new URL(f.path, session.origin).href;
|
|
70
|
+
findings.push({
|
|
71
|
+
category: 'live', tool: 'vsg-live', ruleId: `exposed-${f.kind}`, key: `exposed:${f.path}`, severity: f.severity, confidence: 'confirmed',
|
|
72
|
+
title: `${f.what[0].toUpperCase()}${f.what.slice(1)} is publicly readable: ${f.path}`, location: { url },
|
|
73
|
+
description: `${url} served its content (${f.what}) to an anonymous request. Anyone can download it.`,
|
|
74
|
+
evidence: `HTTP ${res.status}, ${ct || 'no content type'}, ${res.truncated ? 'over 64 KB' : `${res.body.length} bytes`}; ${sig}.${baselineStatus ? ` A random path returned HTTP ${baselineStatus}.` : ''}`,
|
|
75
|
+
fix: FIX[f.kind], refs: [], cwe: [538, 540],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return { findings, probed, baselineStatus };
|
|
79
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// JWT and Supabase inspection for live-js-secrets. Tokens are decoded in memory only; results carry redactions and a short hash.
|
|
2
|
+
import { redact, sha256 } from '../core/findings.mjs';
|
|
3
|
+
|
|
4
|
+
const JWT = /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g;
|
|
5
|
+
const SUPABASE_URL = /https?:\/\/([a-z0-9]{20})\.supabase\.(?:co|in)\b/gi;
|
|
6
|
+
|
|
7
|
+
const decode = (part) => { try { return JSON.parse(Buffer.from(part, 'base64url').toString('utf8')); } catch { return null; } };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Finds JWT-looking strings and decodes them. Returns [{ hash, redacted, role, ref, iss, token }]; `token` is for hashing and
|
|
11
|
+
* matching only and must not be written anywhere. Strings that do not decode to a JSON header and payload are ignored.
|
|
12
|
+
*/
|
|
13
|
+
export function inspectJwts(text) {
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const [token] of String(text).matchAll(JWT)) {
|
|
17
|
+
if (seen.has(token)) continue;
|
|
18
|
+
seen.add(token);
|
|
19
|
+
const [h, p] = token.split('.');
|
|
20
|
+
const header = decode(h);
|
|
21
|
+
const payload = decode(p);
|
|
22
|
+
if (!header || typeof header !== 'object' || !payload || typeof payload !== 'object') continue;
|
|
23
|
+
out.push({ token, hash: sha256(token).slice(0, 12), redacted: redact(token), role: typeof payload.role === 'string' ? payload.role : null, ref: typeof payload.ref === 'string' ? payload.ref : null, iss: typeof payload.iss === 'string' ? payload.iss : null });
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Supabase project URLs (as project refs) found in a document. */
|
|
29
|
+
export function supabaseRefs(text) {
|
|
30
|
+
return [...new Set([...String(text).matchAll(SUPABASE_URL)].map((m) => m[1].toLowerCase()))];
|
|
31
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// A live session: the only way live checks touch the network. Every request is an ordinary GET or HEAD to the one registered
|
|
2
|
+
// origin, passes one shared rate limiter (including retries and redirects), and has a 20 s timeout. It also shares the entry page
|
|
3
|
+
// and the same-origin script files between checks so each is fetched once.
|
|
4
|
+
import { FetchError } from '../core/errors.mjs';
|
|
5
|
+
import { readCapped, vfetch } from '../core/fetch.mjs';
|
|
6
|
+
import { scrubUrl } from '../core/findings.mjs';
|
|
7
|
+
|
|
8
|
+
const TIMEOUT_MS = 20_000;
|
|
9
|
+
export const MAX_SCRIPT_FILES = 200;
|
|
10
|
+
export const MAX_SCRIPT_BYTES = 50 * 1024 * 1024;
|
|
11
|
+
const MAX_ONE_SCRIPT = 15 * 1024 * 1024;
|
|
12
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
13
|
+
const NO_BODY = new Set([101, 204, 205, 304]);
|
|
14
|
+
|
|
15
|
+
const SCRIPT_TAG = /<script\b[^>]*?\bsrc\s*=\s*["']?([^"'\s>]+)/gi;
|
|
16
|
+
const LINK_JS = /<link\b[^>]*?\bhref\s*=\s*["']?([^"'\s>]+?\.m?js(?:\?[^"'\s>]*)?)["']?/gi;
|
|
17
|
+
const NEXT_STATIC = /_next\/static\/[A-Za-z0-9_\-./%[\]~@]+?\.js/g;
|
|
18
|
+
|
|
19
|
+
/** Candidate script URLs in a document, as absolute same-origin URLs. */
|
|
20
|
+
export function scriptUrlsIn(text, baseUrl, origin) {
|
|
21
|
+
const found = new Set();
|
|
22
|
+
const add = (raw) => {
|
|
23
|
+
try {
|
|
24
|
+
const u = new URL(raw.replace(/&/g, '&'), baseUrl);
|
|
25
|
+
if (u.origin !== origin || !/^https?:$/.test(u.protocol)) return;
|
|
26
|
+
u.hash = '';
|
|
27
|
+
found.add(u.href);
|
|
28
|
+
} catch { /* not a URL */ }
|
|
29
|
+
};
|
|
30
|
+
for (const re of [SCRIPT_TAG, LINK_JS]) for (const m of text.matchAll(re)) add(m[1]);
|
|
31
|
+
for (const m of text.matchAll(NEXT_STATIC)) add(`/${m[0]}`);
|
|
32
|
+
return [...found];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* opts: { origin, url, environment, rps, fetchImpl, resolver }.
|
|
37
|
+
* get(pathOrUrl, { headers, maxBytes, method }) returns { status, headers, body, truncated, url } or throws FetchError.
|
|
38
|
+
*/
|
|
39
|
+
export function createSession({ origin, url, displayUrl = scrubUrl(url), environment = 'staging', rps = 2, fetchImpl = globalThis.fetch, resolver = null }) {
|
|
40
|
+
const originUrl = new URL(origin);
|
|
41
|
+
const interval = 1000 / rps;
|
|
42
|
+
let nextSlot = 0;
|
|
43
|
+
const requests = [];
|
|
44
|
+
|
|
45
|
+
async function slot() {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const at = Math.max(now, nextSlot);
|
|
48
|
+
nextSlot = at + interval; // reserved synchronously, so concurrent callers queue behind each other
|
|
49
|
+
if (at > now) await sleep(at - now);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function get(target, { headers = {}, maxBytes = 2 * 1024 * 1024, method = 'GET' } = {}) {
|
|
53
|
+
if (method !== 'GET' && method !== 'HEAD') throw new FetchError(`Live checks only send GET or HEAD, not ${method}.`);
|
|
54
|
+
const u = new URL(target, url);
|
|
55
|
+
if (u.origin !== origin) throw new FetchError(`Refusing ${u.origin}: outside the registered origin ${origin}.`);
|
|
56
|
+
let truncated = false;
|
|
57
|
+
const limited = async (reqUrl, init) => {
|
|
58
|
+
await slot();
|
|
59
|
+
const res = await fetchImpl(reqUrl, init);
|
|
60
|
+
requests.push({ method: init.method, path: new URL(reqUrl).pathname, status: res.status });
|
|
61
|
+
const { buf, truncated: t } = await readCapped(res, maxBytes);
|
|
62
|
+
if (t) truncated = true;
|
|
63
|
+
return new Response(NO_BODY.has(res.status) ? null : buf, { status: res.status, statusText: res.statusText, headers: res.headers });
|
|
64
|
+
};
|
|
65
|
+
const res = await vfetch(u.href, { method, headers: { 'user-agent': 'vsg-passive-check', accept: '*/*', ...headers }, timeoutMs: TIMEOUT_MS, allowHosts: [originUrl.host], strictHosts: true, fetchImpl: limited });
|
|
66
|
+
return { status: res.status, headers: res.headers, body: res.body, truncated, url: u.href };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let pagePromise;
|
|
70
|
+
/** The entry page, fetched once. Throws FetchError when it cannot be read. */
|
|
71
|
+
const page = () => (pagePromise ??= get(url, { maxBytes: 5 * 1024 * 1024 }).then((r) => ({ ...r, text: r.body.toString('utf8') })));
|
|
72
|
+
|
|
73
|
+
let scriptsPromise;
|
|
74
|
+
/** Same-origin scripts found in the entry page and, for Next.js chunks, inside other scripts. Capped at 200 files and 50 MB. */
|
|
75
|
+
const scripts = () => (scriptsPromise ??= (async () => {
|
|
76
|
+
const p = await page();
|
|
77
|
+
const queue = scriptUrlsIn(p.text, p.url, origin);
|
|
78
|
+
const seen = new Set(queue);
|
|
79
|
+
const items = [];
|
|
80
|
+
const failed = [];
|
|
81
|
+
let bytes = 0;
|
|
82
|
+
let capped = false;
|
|
83
|
+
let notScanned = 0;
|
|
84
|
+
while (queue.length) {
|
|
85
|
+
const next = queue.shift();
|
|
86
|
+
if (items.length >= MAX_SCRIPT_FILES || bytes >= MAX_SCRIPT_BYTES) { capped = true; notScanned = queue.length + 1; break; }
|
|
87
|
+
let r;
|
|
88
|
+
try { r = await get(next, { maxBytes: Math.min(MAX_ONE_SCRIPT, MAX_SCRIPT_BYTES - bytes) }); } catch (e) { failed.push({ url: next, reason: e.message }); continue; }
|
|
89
|
+
if (r.status !== 200) { failed.push({ url: next, reason: `HTTP ${r.status}` }); continue; }
|
|
90
|
+
bytes += r.body.length;
|
|
91
|
+
items.push({ url: next, body: r.body, headers: r.headers, truncated: r.truncated });
|
|
92
|
+
for (const more of scriptUrlsIn(r.body.toString('latin1'), next, origin)) if (!seen.has(more)) { seen.add(more); queue.push(more); }
|
|
93
|
+
}
|
|
94
|
+
return { items, failed, bytes, capped, notScanned };
|
|
95
|
+
})());
|
|
96
|
+
|
|
97
|
+
return { origin, url, displayUrl, host: originUrl.host, hostname: originUrl.hostname, environment, rps, resolver, requests, get, page, scripts };
|
|
98
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Target registration (REQ-024, DEC-004). A live check runs only for an origin listed in ~/.vsg/targets.json (honoring VSG_HOME)
|
|
2
|
+
// or in the product's vsg.config.json "urls". Authorization happens before any request is made.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { UsageError } from '../core/errors.mjs';
|
|
6
|
+
import { scrubUrl } from '../core/findings.mjs';
|
|
7
|
+
|
|
8
|
+
const ENVIRONMENTS = ['local', 'staging', 'production'];
|
|
9
|
+
|
|
10
|
+
/** Parses a target URL. Only http(s), no embedded credentials. */
|
|
11
|
+
export function parseTargetUrl(text) {
|
|
12
|
+
let u;
|
|
13
|
+
try { u = new URL(String(text)); } catch { throw new UsageError(`"${scrubUrl(text)}" is not a valid URL. Pass a full URL such as https://staging.example.com.`); }
|
|
14
|
+
if (u.protocol !== 'http:' && u.protocol !== 'https:') throw new UsageError(`"${u.protocol}" URLs are not supported. Use http or https.`);
|
|
15
|
+
if (u.username || u.password) throw new UsageError('Target URLs must not contain a username or password.');
|
|
16
|
+
return u;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const originOf = (text, where) => {
|
|
20
|
+
try { return parseTargetUrl(text).origin; } catch (e) { throw new UsageError(`${where}: ${e.message}`); }
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Loads the machine-level targets file. Returns { file, entries: [{ product, origins, environment }], notes }. A missing file means no targets. */
|
|
24
|
+
export function loadTargets(home) {
|
|
25
|
+
const file = path.join(home, 'targets.json');
|
|
26
|
+
let text;
|
|
27
|
+
try { text = fs.readFileSync(file, 'utf8'); } catch { return { file, entries: [], notes: [] }; }
|
|
28
|
+
let data;
|
|
29
|
+
try { data = JSON.parse(text); } catch (e) { throw new UsageError(`${file} is not valid JSON (${e.message}). Fix it and rerun.`); }
|
|
30
|
+
const list = Array.isArray(data) ? data : data?.targets;
|
|
31
|
+
if (!Array.isArray(list)) throw new UsageError(`${file} must be an array or { "targets": [] } of { product, origins, environment }.`);
|
|
32
|
+
const notes = [];
|
|
33
|
+
if (data && !Array.isArray(data) && 'active' in data) notes.push(`${file}: the "active" field is ignored. Live checks are passive only.`);
|
|
34
|
+
const entries = list.map((t, i) => {
|
|
35
|
+
const where = `${file} entry ${i + 1}`;
|
|
36
|
+
if (!t || typeof t !== 'object') throw new UsageError(`${where} must be an object.`);
|
|
37
|
+
if (typeof t.product !== 'string' || !t.product.trim()) throw new UsageError(`${where} needs a "product" name.`);
|
|
38
|
+
if (!Array.isArray(t.origins) || !t.origins.length) throw new UsageError(`${where} needs a non-empty "origins" array.`);
|
|
39
|
+
if (!ENVIRONMENTS.includes(t.environment)) throw new UsageError(`${where} needs "environment" set to local, staging or production.`);
|
|
40
|
+
if ('active' in t) notes.push(`${where} ("${t.product}"): the "active" field is ignored. Live checks are passive only.`);
|
|
41
|
+
return { product: t.product, origins: t.origins.map((o) => originOf(o, where)), environment: t.environment };
|
|
42
|
+
});
|
|
43
|
+
return { file, entries, notes };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const isLoopback = (hostname) => hostname === 'localhost' || hostname.endsWith('.localhost') || hostname === '127.0.0.1' || hostname === '[::1]';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Finds the entry that authorizes `urlText`, or throws a UsageError (exit 3) without touching the network.
|
|
50
|
+
* Returns { url, displayUrl, origin, hostname, host, environment, entry, description, notes }.
|
|
51
|
+
*/
|
|
52
|
+
export function authorize(urlText, { home, repoConfig = null, repoPath = null }) {
|
|
53
|
+
const url = parseTargetUrl(urlText);
|
|
54
|
+
const { file, entries, notes } = loadTargets(home);
|
|
55
|
+
const hit = entries.find((e) => e.origins.includes(url.origin));
|
|
56
|
+
const base = { url: url.href, displayUrl: scrubUrl(url.href), origin: url.origin, hostname: url.hostname, host: url.host, notes };
|
|
57
|
+
if (hit) {
|
|
58
|
+
return { ...base, environment: hit.environment, entry: { source: file, product: hit.product },
|
|
59
|
+
description: `${file}: product "${hit.product}", origin ${url.origin}, environment ${hit.environment}` };
|
|
60
|
+
}
|
|
61
|
+
const cfgHit = (repoConfig?.urls ?? []).some((u) => { try { return new URL(u).origin === url.origin; } catch { return false; } });
|
|
62
|
+
if (cfgHit) {
|
|
63
|
+
const where = repoPath ? path.join(repoPath, 'vsg.config.json') : 'vsg.config.json';
|
|
64
|
+
return { ...base, environment: isLoopback(url.hostname) ? 'local' : 'staging', entry: { source: where, product: null },
|
|
65
|
+
description: `${where}: urls entry for ${url.origin}` };
|
|
66
|
+
}
|
|
67
|
+
throw new UsageError(`${url.origin} is not a registered target, so no request was made. Add its origin to ${file} (Richard approves this) or to "urls" in the product's vsg.config.json.`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const MAX_RPS = 2;
|
|
71
|
+
export const MAX_LOCAL_RPS = 100;
|
|
72
|
+
|
|
73
|
+
/** Requests per second: 2 by default. Only a targets.json entry with environment "local" on a loopback host may go faster. */
|
|
74
|
+
export function resolveRate(rateFlag, auth) {
|
|
75
|
+
if (rateFlag === undefined) return MAX_RPS;
|
|
76
|
+
const n = Number(rateFlag);
|
|
77
|
+
if (!Number.isFinite(n) || n <= 0) throw new UsageError('--rate must be a positive number of requests per second.');
|
|
78
|
+
const localEntry = auth.environment === 'local' && isLoopback(auth.hostname) && path.basename(auth.entry.source) === 'targets.json';
|
|
79
|
+
if (n > MAX_RPS && !localEntry) throw new UsageError(`--rate above ${MAX_RPS} requests per second is allowed only for a loopback origin registered as local in targets.json.`);
|
|
80
|
+
return Math.min(n, MAX_LOCAL_RPS);
|
|
81
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Portfolio runner (REQ-030): runs each registered product through the same pipeline, one after another, and writes a summary.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { UsageError } from '../core/errors.mjs';
|
|
5
|
+
import { scrubText, scrubUrl } from '../core/findings.mjs';
|
|
6
|
+
import { EXIT_CODES } from '../core/gate.mjs';
|
|
7
|
+
import { runPipeline } from '../core/pipeline.mjs';
|
|
8
|
+
|
|
9
|
+
export const KEEP_DAYS = 90;
|
|
10
|
+
const RANK = { PASS: 0, INCOMPLETE: 1, BLOCKED: 2 };
|
|
11
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
12
|
+
export const worst = (verdicts) => verdicts.reduce((a, b) => (RANK[b] > RANK[a] ? b : a), 'PASS');
|
|
13
|
+
|
|
14
|
+
/** Reads and validates portfolio.json. Relative product paths resolve against the config file's folder. */
|
|
15
|
+
export function loadPortfolio(file) {
|
|
16
|
+
let data;
|
|
17
|
+
try { data = JSON.parse(fs.readFileSync(file, 'utf8')); } catch (e) {
|
|
18
|
+
throw new UsageError(e.code === 'ENOENT' ? `${file} does not exist. Create it as { "schemaVersion": 1, "products": [{ "name", "path", "url"?, "mode": "scan"|"deep" }] }.` : `${file} is not valid JSON (${e.message}). Fix it and rerun.`);
|
|
19
|
+
}
|
|
20
|
+
if (data?.schemaVersion !== 1 || !Array.isArray(data.products)) throw new UsageError(`${file} must be { "schemaVersion": 1, "products": [...] }.`);
|
|
21
|
+
const names = new Set();
|
|
22
|
+
return data.products.map((p, i) => {
|
|
23
|
+
const where = `${file} product ${i + 1}`;
|
|
24
|
+
if (!p || typeof p !== 'object') throw new UsageError(`${where} must be an object.`);
|
|
25
|
+
if (typeof p.name !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(p.name)) throw new UsageError(`${where} needs a "name" of letters, digits, dot, dash or underscore (it becomes a folder name).`);
|
|
26
|
+
if (names.has(p.name.toLowerCase())) throw new UsageError(`${where}: duplicate name "${p.name}".`);
|
|
27
|
+
names.add(p.name.toLowerCase());
|
|
28
|
+
if (typeof p.path !== 'string' || !p.path.trim()) throw new UsageError(`${where} ("${p.name}") needs a "path".`);
|
|
29
|
+
if (!['scan', 'deep'].includes(p.mode)) throw new UsageError(`${where} ("${p.name}") needs "mode" set to scan or deep.`);
|
|
30
|
+
if (p.url !== undefined && typeof p.url !== 'string') throw new UsageError(`${where} ("${p.name}"): "url" must be a string.`);
|
|
31
|
+
return { name: p.name, path: path.resolve(path.dirname(file), p.path), url: p.url, mode: p.mode };
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const inside = (child, parent) => { const r = path.relative(parent, child); return r === '' || (!r.startsWith('..') && !path.isAbsolute(r)); };
|
|
36
|
+
|
|
37
|
+
/** Removes dated folders (YYYY-MM-DD) older than KEEP_DAYS. Returns the removed names. */
|
|
38
|
+
export function prune(root, now) {
|
|
39
|
+
const removed = [];
|
|
40
|
+
let entries;
|
|
41
|
+
try { entries = fs.readdirSync(root, { withFileTypes: true }); } catch { return removed; }
|
|
42
|
+
for (const e of entries) {
|
|
43
|
+
if (!e.isDirectory() || !/^\d{4}-\d{2}-\d{2}$/.test(e.name)) continue;
|
|
44
|
+
const t = Date.parse(`${e.name}T00:00:00Z`);
|
|
45
|
+
if (!Number.isNaN(t) && now - t > KEEP_DAYS * DAY_MS) { fs.rmSync(path.join(root, e.name), { recursive: true, force: true }); removed.push(e.name); }
|
|
46
|
+
}
|
|
47
|
+
return removed;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const fromResult = (r) => ({ verdict: r.verdict, mustFix: r.counts.blocking, notRun: r.coverage.filter((c) => c.status === 'not_run').map((c) => c.id), report: r.outputs.report });
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Runs every product. deps: { home, checks, registry, now, progress, prepareLive } where prepareLive(url, repo) authorizes a url
|
|
54
|
+
* (throwing on an unregistered origin) and returns { flags, info }.
|
|
55
|
+
*/
|
|
56
|
+
export async function runPortfolio(products, { home, checks, registry, now = () => new Date(), progress = () => {}, prepareLive }) {
|
|
57
|
+
const date = now().toISOString().slice(0, 10);
|
|
58
|
+
const root = path.join(home, 'portfolio');
|
|
59
|
+
const dir = path.join(root, date);
|
|
60
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
61
|
+
const rows = [];
|
|
62
|
+
for (const p of products) {
|
|
63
|
+
const out = path.join(dir, p.name);
|
|
64
|
+
const row = { name: p.name, path: p.path, mode: p.mode, verdict: 'INCOMPLETE', mustFix: 0, notRun: [], report: null, url: null, error: null };
|
|
65
|
+
try {
|
|
66
|
+
if (!fs.existsSync(p.path) || !fs.statSync(p.path).isDirectory()) throw new Error(`path "${p.path}" is not a directory`);
|
|
67
|
+
if (inside(out, p.path)) throw new Error(`the output folder ${out} is inside the product; set VSG_HOME outside the repository`);
|
|
68
|
+
progress(`${p.name}: ${p.mode}`);
|
|
69
|
+
const r = await runPipeline({ mode: p.mode, repo: p.path, out, checks, registry, now, progress });
|
|
70
|
+
Object.assign(row, fromResult(r));
|
|
71
|
+
if (p.url) {
|
|
72
|
+
let live = null;
|
|
73
|
+
try { live = prepareLive(p.url, p.path); } catch (e) {
|
|
74
|
+
row.url = { status: 'not run', reason: scrubText(e.message) };
|
|
75
|
+
row.verdict = worst([row.verdict, 'INCOMPLETE']);
|
|
76
|
+
}
|
|
77
|
+
if (live) {
|
|
78
|
+
progress(`${p.name}: url ${live.info?.target ?? scrubUrl(p.url)}`);
|
|
79
|
+
try {
|
|
80
|
+
const u = await runPipeline({ mode: 'url', repo: p.path, out: path.join(out, 'url'), checks, registry, now, progress,
|
|
81
|
+
flags: { ...live.flags, target: live.info.target, authorization: live.info.authorization } });
|
|
82
|
+
const x = fromResult(u);
|
|
83
|
+
row.url = { status: 'ran', ...x };
|
|
84
|
+
row.verdict = worst([row.verdict, x.verdict]);
|
|
85
|
+
row.mustFix += x.mustFix;
|
|
86
|
+
row.notRun.push(...x.notRun.map((id) => `url:${id}`));
|
|
87
|
+
} catch (e) { row.url = { status: 'failed', reason: e.message }; row.verdict = worst([row.verdict, 'INCOMPLETE']); }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch (e) {
|
|
91
|
+
row.verdict = 'INCOMPLETE';
|
|
92
|
+
row.error = e.message;
|
|
93
|
+
}
|
|
94
|
+
rows.push(row);
|
|
95
|
+
}
|
|
96
|
+
const verdict = worst(rows.map((r) => r.verdict));
|
|
97
|
+
const summary = { schemaVersion: 1, date, verdict, exitCode: EXIT_CODES[verdict], products: rows };
|
|
98
|
+
fs.writeFileSync(path.join(dir, 'summary.json'), `${JSON.stringify(summary, null, 2)}\n`);
|
|
99
|
+
fs.writeFileSync(path.join(dir, 'summary.md'), summaryMarkdown(summary));
|
|
100
|
+
const pruned = prune(root, now().getTime());
|
|
101
|
+
return { summary, dir, pruned, exitCode: summary.exitCode };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const cell = (s) => String(s).replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
|
|
105
|
+
|
|
106
|
+
export function summaryMarkdown(s) {
|
|
107
|
+
const lines = [`# Portfolio ${s.verdict}: ${s.date}`, '', `${s.products.length} product(s). Exit code ${s.exitCode}.`, '',
|
|
108
|
+
'| Product | Mode | Verdict | Must fix | Checks not run | Report |', '| --- | --- | --- | --- | --- | --- |'];
|
|
109
|
+
for (const p of s.products) {
|
|
110
|
+
const notRun = p.error ? `run failed: ${p.error}` : p.notRun.length ? `${p.notRun.length}: ${p.notRun.join(', ')}` : 'none';
|
|
111
|
+
lines.push(`| ${cell(p.name)} | ${p.mode} | ${p.verdict} | ${p.mustFix} | ${cell(notRun)} | ${cell(p.report ?? 'none')} |`);
|
|
112
|
+
}
|
|
113
|
+
const urlNotes = s.products.filter((p) => p.url && p.url.status !== 'ran');
|
|
114
|
+
if (urlNotes.length) lines.push('', '## Live checks that did not run', '', ...urlNotes.map((p) => `- ${p.name}: ${p.url.status}: ${p.url.reason}`));
|
|
115
|
+
return `${lines.join('\n')}\n`;
|
|
116
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "betterleaks",
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"homepage": "https://github.com/betterleaks/betterleaks",
|
|
6
|
+
"releaseUrl": "https://github.com/betterleaks/betterleaks/releases/tag/v1.9.0",
|
|
7
|
+
"checksumsUrl": "https://github.com/betterleaks/betterleaks/releases/download/v1.9.0/checksums.txt",
|
|
8
|
+
"platforms": {
|
|
9
|
+
"win32-x64": {
|
|
10
|
+
"url": "https://github.com/betterleaks/betterleaks/releases/download/v1.9.0/betterleaks_1.9.0_windows_x64.zip",
|
|
11
|
+
"sha256": "b765daec9fda475b031b5a5159798cbe4294a3f2a50683cbc0471226e376d3da",
|
|
12
|
+
"archive": "zip",
|
|
13
|
+
"bin": "betterleaks.exe"
|
|
14
|
+
},
|
|
15
|
+
"linux-x64": {
|
|
16
|
+
"url": "https://github.com/betterleaks/betterleaks/releases/download/v1.9.0/betterleaks_1.9.0_linux_x64.tar.gz",
|
|
17
|
+
"sha256": "f8b185a39ffcece2a1ca82bf3a4e7435cd81963ffd16b7a9128daf75f35f6de7",
|
|
18
|
+
"archive": "tar.gz",
|
|
19
|
+
"bin": "betterleaks"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"verify": {
|
|
23
|
+
"args": [
|
|
24
|
+
"version"
|
|
25
|
+
],
|
|
26
|
+
"expect": "1.9.0"
|
|
27
|
+
},
|
|
28
|
+
"timeoutSec": 600
|
|
29
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "guarddog",
|
|
3
|
+
"version": "3.2.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"homepage": "https://github.com/DataDog/guarddog",
|
|
6
|
+
"releaseUrl": "https://github.com/DataDog/guarddog/releases/tag/v3.2.0",
|
|
7
|
+
"image": "ghcr.io/datadog/guarddog:v3.2.0@sha256:5b0804d480e83405120fc90c62b60ad844310418c836e567055b1ba8843e033d",
|
|
8
|
+
"timeoutSec": 120
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mobsf",
|
|
3
|
+
"version": "v4.5.3",
|
|
4
|
+
"license": "GPL-3.0 (Mobile Security Framework)",
|
|
5
|
+
"homepage": "https://github.com/MobSF/Mobile-Security-Framework-MobSF",
|
|
6
|
+
"releaseUrl": "https://github.com/MobSF/Mobile-Security-Framework-MobSF/releases/tag/v4.5.3",
|
|
7
|
+
"image": "opensecurity/mobile-security-framework-mobsf:v4.5.3@sha256:5113fbad0727445dda01afdccafea5e107bfa8632ead8a169492f5caa8aa1b7c",
|
|
8
|
+
"purpose": "Optional static analysis of a built APK (vsg.config.json mobile.apk). Runs as a --rm container published on 127.0.0.1 only, with a per-run API key (MOBSF_API_KEY). The digest is the multi-arch index digest of tag v4.5.3, read from Docker Hub on 2026-10-06 (linux/amd64 d4e9ca08..., linux/arm64 78fe55cf...).",
|
|
9
|
+
"timeoutSec": 900
|
|
10
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opengrep",
|
|
3
|
+
"version": "1.30.0",
|
|
4
|
+
"license": "LGPL-2.1",
|
|
5
|
+
"homepage": "https://github.com/opengrep/opengrep",
|
|
6
|
+
"releaseUrl": "https://github.com/opengrep/opengrep/releases/tag/v1.30.0",
|
|
7
|
+
"platforms": {
|
|
8
|
+
"win32-x64": {
|
|
9
|
+
"url": "https://github.com/opengrep/opengrep/releases/download/v1.30.0/opengrep_windows_x86.exe",
|
|
10
|
+
"sha256": "b5cf4f8fe9f44e030aab2d579d96bd395c139db1f1ba66633676ff4d5ebc7c39",
|
|
11
|
+
"archive": "none",
|
|
12
|
+
"bin": "opengrep.exe"
|
|
13
|
+
},
|
|
14
|
+
"linux-x64": {
|
|
15
|
+
"url": "https://github.com/opengrep/opengrep/releases/download/v1.30.0/opengrep_manylinux_x86",
|
|
16
|
+
"sha256": "35779bdd72e92129c8df2a77f0c55e8c08356801ea92591ef32108d6b28d564c",
|
|
17
|
+
"archive": "none",
|
|
18
|
+
"bin": "opengrep"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"verify": {
|
|
22
|
+
"args": [
|
|
23
|
+
"--version"
|
|
24
|
+
],
|
|
25
|
+
"expect": "1.30.0"
|
|
26
|
+
},
|
|
27
|
+
"timeoutSec": 900
|
|
28
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "osv-scanner",
|
|
3
|
+
"version": "2.6.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"homepage": "https://github.com/google/osv-scanner",
|
|
6
|
+
"releaseUrl": "https://github.com/google/osv-scanner/releases/tag/v2.6.0",
|
|
7
|
+
"checksumsUrl": "https://github.com/google/osv-scanner/releases/download/v2.6.0/osv-scanner_SHA256SUMS",
|
|
8
|
+
"platforms": {
|
|
9
|
+
"win32-x64": { "url": "https://github.com/google/osv-scanner/releases/download/v2.6.0/osv-scanner_windows_amd64.exe", "sha256": "e0ed7644118b717b028c249ee9d3515024e55e8510747ca08906eb96765354d6", "archive": "none", "bin": "osv-scanner.exe" },
|
|
10
|
+
"linux-x64": { "url": "https://github.com/google/osv-scanner/releases/download/v2.6.0/osv-scanner_linux_amd64", "sha256": "ca69b3d3cd08f889a49dc0a383122f71cc528b83803671df5fd874d97485b108", "archive": "none", "bin": "osv-scanner" }
|
|
11
|
+
},
|
|
12
|
+
"verify": { "args": ["--version"], "expect": "2.6.0" },
|
|
13
|
+
"timeoutSec": 300
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "semgrep-rules",
|
|
3
|
+
"version": "a84ff9cc2453ca91d581380de4b8b3f272f6f4be",
|
|
4
|
+
"license": "Semgrep Rules License v1.0 (internal use)",
|
|
5
|
+
"homepage": "https://github.com/semgrep/semgrep-rules",
|
|
6
|
+
"releaseUrl": "https://github.com/semgrep/semgrep-rules/tree/a84ff9cc2453ca91d581380de4b8b3f272f6f4be",
|
|
7
|
+
"platforms": {
|
|
8
|
+
"win32-x64": {
|
|
9
|
+
"url": "https://codeload.github.com/semgrep/semgrep-rules/tar.gz/a84ff9cc2453ca91d581380de4b8b3f272f6f4be",
|
|
10
|
+
"sha256": "b227c2d234ffd9c84c4dbd6619a5897a7192637c141baeace3bfc37b0715a887",
|
|
11
|
+
"archive": "tar.gz",
|
|
12
|
+
"bin": "semgrep-rules-a84ff9cc2453ca91d581380de4b8b3f272f6f4be/README.md"
|
|
13
|
+
},
|
|
14
|
+
"linux-x64": {
|
|
15
|
+
"url": "https://codeload.github.com/semgrep/semgrep-rules/tar.gz/a84ff9cc2453ca91d581380de4b8b3f272f6f4be",
|
|
16
|
+
"sha256": "b227c2d234ffd9c84c4dbd6619a5897a7192637c141baeace3bfc37b0715a887",
|
|
17
|
+
"archive": "tar.gz",
|
|
18
|
+
"bin": "semgrep-rules-a84ff9cc2453ca91d581380de4b8b3f272f6f4be/README.md"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|