@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,223 @@
|
|
|
1
|
+
// Auto-run configuration (table row 5) and MCP server definitions (row 6).
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { clip, fetchAndRun, lineOf, parseJsonc } from './patterns.mjs';
|
|
4
|
+
|
|
5
|
+
const LIFECYCLE = ['preinstall', 'install', 'postinstall', 'prepare'];
|
|
6
|
+
const DEVCONTAINER_KEYS = ['initializeCommand', 'onCreateCommand', 'updateContentCommand', 'postCreateCommand', 'postStartCommand', 'postAttachCommand'];
|
|
7
|
+
const HOOK_KEYS = new Set(['command', 'commands', 'run', 'script', 'install', 'start', 'setup', 'notify', 'cmd']);
|
|
8
|
+
|
|
9
|
+
const esc = (s) => JSON.stringify(String(s)).slice(1, -1);
|
|
10
|
+
const strings = (v) => (typeof v === 'string' ? [v] : Array.isArray(v) ? v.flatMap(strings) : v && typeof v === 'object' ? Object.values(v).flatMap(strings) : []);
|
|
11
|
+
|
|
12
|
+
/** One auto-run entry: Critical when it fetches or decodes and then executes, otherwise listed as info. */
|
|
13
|
+
function autoRun({ kind, name, command, line }) {
|
|
14
|
+
const hit = fetchAndRun(command);
|
|
15
|
+
const where = `${kind} "${name}"`;
|
|
16
|
+
if (hit) {
|
|
17
|
+
return {
|
|
18
|
+
rule: 'autorun-fetch-exec', key: `${kind}:${name}`, severity: 'critical', line,
|
|
19
|
+
title: `Auto-run ${kind} downloads or decodes content and runs it`,
|
|
20
|
+
description: `${where} runs automatically (on folder open, container start, agent event, git hook or install) and ${hit.via === 'download' ? 'downloads' : 'decodes'} content then executes it ("${hit.fetch}" ... "${hit.exec}"). A hostile repository uses this to run code the moment it is opened or installed.`,
|
|
21
|
+
evidence: clip(command),
|
|
22
|
+
fix: `Remove the download-and-run command from ${where}. Run reviewed, pinned scripts that are committed to the repository instead.`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
rule: 'autorun-present', key: `${kind}:${name}`, severity: 'info', line,
|
|
27
|
+
title: `Auto-run ${kind} is configured`,
|
|
28
|
+
description: `${where} runs automatically. It does not download or decode content and then run it. Listed so a reviewer sees what executes without being asked.`,
|
|
29
|
+
evidence: clip(command), fix: 'No change needed unless the command is unexpected.',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function taskCommands(task) {
|
|
34
|
+
const parts = [task.command, ...(Array.isArray(task.args) ? task.args.map((a) => (typeof a === 'string' ? a : a?.value)) : [])];
|
|
35
|
+
for (const os of ['windows', 'linux', 'osx']) if (task[os]) parts.push(task[os].command, ...(task[os].args ?? []));
|
|
36
|
+
return parts.filter((p) => typeof p === 'string').join(' ');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hookCommands(node, out = []) {
|
|
40
|
+
if (Array.isArray(node)) { for (const n of node) hookCommands(n, out); return out; }
|
|
41
|
+
if (!node || typeof node !== 'object') return out;
|
|
42
|
+
for (const [k, v] of Object.entries(node)) {
|
|
43
|
+
if (k === 'mcpServers' || k === 'servers') continue; // MCP launches are row 6
|
|
44
|
+
if (HOOK_KEYS.has(k)) {
|
|
45
|
+
const joined = typeof v === 'string' ? v : Array.isArray(v) && v.every((x) => typeof x === 'string') ? v.join(' ') : null;
|
|
46
|
+
const args = Array.isArray(node.args) && k === 'command' ? ` ${node.args.filter((a) => typeof a === 'string').join(' ')}` : '';
|
|
47
|
+
if (joined !== null) out.push(joined + args);
|
|
48
|
+
else for (const s of strings(v)) out.push(s);
|
|
49
|
+
} else hookCommands(v, out);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Line-based pass over TOML or shell text: every logical line (backslash continuations joined) is one command. */
|
|
55
|
+
function scriptLines(text) {
|
|
56
|
+
const out = [];
|
|
57
|
+
const lines = text.split(/\r?\n/);
|
|
58
|
+
for (let i = 0; i < lines.length; i++) {
|
|
59
|
+
let line = lines[i];
|
|
60
|
+
const start = i + 1;
|
|
61
|
+
while (/\\\s*$/.test(line) && i + 1 < lines.length) line = `${line.replace(/\\\s*$/, ' ')}${lines[++i]}`;
|
|
62
|
+
if (line.trim() && !line.trim().startsWith('#')) out.push({ line: start, text: line });
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function autoRunIssues(rel, text) {
|
|
68
|
+
const base = path.posix.basename(rel);
|
|
69
|
+
const out = [];
|
|
70
|
+
const json = () => parseJsonc(text);
|
|
71
|
+
if (/(^|\/)\.vscode\/tasks\.json$/.test(rel)) {
|
|
72
|
+
for (const t of json()?.tasks ?? []) {
|
|
73
|
+
if (t?.runOptions?.runOn !== 'folderOpen') continue;
|
|
74
|
+
const command = taskCommands(t);
|
|
75
|
+
out.push(autoRun({ kind: 'VS Code folderOpen task', name: t.label ?? t.command ?? 'task', command, line: lineOf(text, esc(t.command ?? 'folderOpen')) }));
|
|
76
|
+
}
|
|
77
|
+
} else if (/(^|\/)\.devcontainer(\/.*)?\/[^/]+\.json$|(^|\/)\.devcontainer\.json$/.test(rel)) {
|
|
78
|
+
const doc = json() ?? {};
|
|
79
|
+
for (const key of DEVCONTAINER_KEYS) {
|
|
80
|
+
if (doc[key] === undefined) continue;
|
|
81
|
+
const command = (typeof doc[key] === 'object' && !Array.isArray(doc[key]) ? Object.values(doc[key]).map((v) => strings(v).join(' ')) : [strings(doc[key]).join(' ')]).join(' ; ');
|
|
82
|
+
out.push(autoRun({ kind: 'devcontainer lifecycle command', name: key, command, line: lineOf(text, `"${key}"`) }));
|
|
83
|
+
}
|
|
84
|
+
} else if (/(^|\/)(\.claude\/settings[^/]*\.json|\.gemini\/settings\.json|\.cursor\/(?!rules\/)[^/]+\.json|\.codex\/[^/]+\.json)$/.test(rel)) {
|
|
85
|
+
const kind = rel.includes('.claude/') ? 'Claude Code hook' : rel.includes('.gemini/') ? 'Gemini CLI hook' : rel.includes('.codex/') ? 'Codex hook' : 'Cursor hook';
|
|
86
|
+
for (const command of [...new Set(hookCommands(json()))]) out.push(autoRun({ kind, name: clip(command, 60), command, line: lineOf(text, esc(command)) }));
|
|
87
|
+
} else if (/(^|\/)\.codex\/[^/]+\.toml$/.test(rel)) {
|
|
88
|
+
let skip = false;
|
|
89
|
+
for (const { line, text: l } of scriptLines(text)) {
|
|
90
|
+
const header = /^\s*\[+([^\]]+)\]+/.exec(l);
|
|
91
|
+
if (header) { skip = header[1].trim().startsWith('mcp_servers'); continue; }
|
|
92
|
+
const kv = /^\s*([A-Za-z0-9_.-]+)\s*=\s*(.+)$/.exec(l);
|
|
93
|
+
if (!skip && kv && (HOOK_KEYS.has(kv[1]) || /hook/i.test(kv[1]))) out.push(autoRun({ kind: 'Codex hook', name: kv[1], command: kv[2], line }));
|
|
94
|
+
}
|
|
95
|
+
} else if (/(^|\/)\.(husky|githooks)\/(?!_\/)[^/]+$/.test(rel)) {
|
|
96
|
+
const kind = rel.includes('.husky/') ? 'Husky git hook' : 'git hook';
|
|
97
|
+
const lines = scriptLines(text).filter((l) => !l.text.startsWith('#!'));
|
|
98
|
+
const all = lines.map((l) => l.text).join('\n');
|
|
99
|
+
const bad = lines.filter((l) => fetchAndRun(l.text));
|
|
100
|
+
if (bad.length) for (const l of bad) out.push(autoRun({ kind, name: `${base}:${l.line}`, command: l.text, line: l.line }));
|
|
101
|
+
else if (all.trim()) out.push(autoRun({ kind, name: base, command: lines[0].text, line: lines[0].line }));
|
|
102
|
+
} else if (base === 'package.json') {
|
|
103
|
+
const scripts = json()?.scripts;
|
|
104
|
+
for (const key of LIFECYCLE) {
|
|
105
|
+
if (typeof scripts?.[key] === 'string') out.push(autoRun({ kind: 'package.json lifecycle script', name: key, command: scripts[key], line: lineOf(text, `"${key}"`) }));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---- MCP servers ----
|
|
112
|
+
const LOCAL_HOST = /^(localhost|127\.0\.0\.1|\[::1\]|::1|[^.]+\.localhost)$/i;
|
|
113
|
+
const VALUE_FLAGS = new Set(['-p', '--package', '--from', '-c', '--call', '--registry', '--cache', '--userconfig', '--with', '--python', '--index-url']);
|
|
114
|
+
const EXACT_NPM = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.+-]+)?$/;
|
|
115
|
+
const EXACT_PY = /^\d+(?:\.\d+)+(?:[0-9A-Za-z.+!-]*)$/;
|
|
116
|
+
|
|
117
|
+
/** Package spec the launcher runs, or null when the command is not a package launcher. */
|
|
118
|
+
function launchedPackage(tokens) {
|
|
119
|
+
let t = tokens.map(String);
|
|
120
|
+
if (/^cmd(\.exe)?$/i.test(path.basename(t[0] ?? '')) && /^\/[ck]$/i.test(t[1] ?? '')) t = t.slice(2);
|
|
121
|
+
if (/^(ba|z)?sh$/.test(path.basename(t[0] ?? '')) && t[1] === '-c') t = String(t[2] ?? '').split(/\s+/);
|
|
122
|
+
const exe = path.basename(t[0] ?? '').replace(/\.(cmd|exe|ps1)$/i, '');
|
|
123
|
+
let rest;
|
|
124
|
+
let ecosystem = 'npm';
|
|
125
|
+
if (exe === 'npx' || exe === 'bunx' || exe === 'pnpx') rest = t.slice(1);
|
|
126
|
+
else if (exe === 'pnpm' && t[1] === 'dlx') rest = t.slice(2);
|
|
127
|
+
else if (exe === 'uvx') { rest = t.slice(1); ecosystem = 'pypi'; } else return null;
|
|
128
|
+
let explicit = null;
|
|
129
|
+
for (let i = 0; i < rest.length; i++) {
|
|
130
|
+
const a = rest[i];
|
|
131
|
+
const eq = /^(--package|--from)=(.+)$/.exec(a);
|
|
132
|
+
if (eq) { explicit = eq[2]; break; }
|
|
133
|
+
if (a === '-p' || a === '--package' || a === '--from') { explicit = rest[i + 1]; break; }
|
|
134
|
+
if (VALUE_FLAGS.has(a)) { i++; continue; }
|
|
135
|
+
if (a.startsWith('-')) continue;
|
|
136
|
+
return { spec: a, ecosystem };
|
|
137
|
+
}
|
|
138
|
+
return explicit ? { spec: explicit, ecosystem } : null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Returns { name, version, exact } for a package spec; null for paths, URLs and other non-registry specs. */
|
|
142
|
+
export function parseSpec(spec, ecosystem) {
|
|
143
|
+
if (/^(\.{0,2}[\\/]|[A-Za-z]:|file:|git\+|https?:|github:|\w+:\/\/)/.test(spec)) return null;
|
|
144
|
+
if (ecosystem === 'pypi') {
|
|
145
|
+
const m = /^([A-Za-z0-9][A-Za-z0-9._-]*)(?:\[[^\]]*\])?(?:(==|@)(.+))?$/.exec(spec);
|
|
146
|
+
if (!m) return null;
|
|
147
|
+
return { name: m[1], version: m[3] ?? '', exact: !!m[3] && m[3] !== 'latest' && EXACT_PY.test(m[3]) };
|
|
148
|
+
}
|
|
149
|
+
const at = spec.indexOf('@', 1);
|
|
150
|
+
const name = at < 0 ? spec : spec.slice(0, at);
|
|
151
|
+
const version = at < 0 ? '' : spec.slice(at + 1);
|
|
152
|
+
return { name, version, exact: EXACT_NPM.test(version) };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function mcpServerIssues({ name, command, args, urls, line, file }) {
|
|
156
|
+
const out = [];
|
|
157
|
+
const pkg = command ? launchedPackage([command, ...(args ?? [])]) : null;
|
|
158
|
+
const parsed = pkg ? parseSpec(pkg.spec, pkg.ecosystem) : null;
|
|
159
|
+
if (parsed && !parsed.exact) {
|
|
160
|
+
out.push({
|
|
161
|
+
rule: 'mcp-unpinned', key: `mcp:${name}`, severity: 'medium', line,
|
|
162
|
+
title: `MCP server "${name}" launches ${parsed.version === 'latest' ? 'the @latest version of ' : 'an unpinned version of '}${parsed.name}`,
|
|
163
|
+
description: `${file} starts this MCP server with "${clip(`${command} ${(args ?? []).join(' ')}`, 120)}" without an exact version, so every start can run different code from the registry. A compromised release runs with the agent's access.`,
|
|
164
|
+
evidence: clip(`${command} ${(args ?? []).join(' ')}`),
|
|
165
|
+
fix: `Pin an exact version, for example ${parsed.name}@<exact version>${pkg.ecosystem === 'pypi' ? ' (or ==<version>)' : ''}.`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
for (const url of urls) {
|
|
169
|
+
let u;
|
|
170
|
+
try { u = new URL(url); } catch { continue; }
|
|
171
|
+
if (u.protocol === 'http:' && !LOCAL_HOST.test(u.hostname)) {
|
|
172
|
+
out.push({
|
|
173
|
+
rule: 'mcp-plain-http', key: `mcp:${name}:http`, severity: 'high', line,
|
|
174
|
+
title: `MCP server "${name}" uses a plain HTTP remote`,
|
|
175
|
+
description: `${file} connects to ${u.origin} over http://, so anyone on the network path can read or change the tool calls and results the agent exchanges with it.`,
|
|
176
|
+
evidence: clip(url), fix: 'Use an https:// endpoint, or run the server locally and connect to localhost.',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function mcpIssues(rel, text) {
|
|
184
|
+
const out = [];
|
|
185
|
+
if (/(^|\/)\.codex\/config\.toml$/.test(rel)) {
|
|
186
|
+
const servers = new Map();
|
|
187
|
+
const lines = text.split(/\r?\n/);
|
|
188
|
+
let current = null;
|
|
189
|
+
for (let i = 0; i < lines.length; i++) {
|
|
190
|
+
const l = lines[i].trim();
|
|
191
|
+
const header = /^\[\s*mcp_servers\s*\.\s*(?:"([^"]+)"|'([^']+)'|([^\s\].]+))\s*\]$/.exec(l);
|
|
192
|
+
if (header) { current = { name: header[1] ?? header[2] ?? header[3], line: i + 1, args: [], urls: [] }; servers.set(current.name, current); continue; }
|
|
193
|
+
if (/^\[/.test(l)) { current = null; continue; }
|
|
194
|
+
if (!current) continue;
|
|
195
|
+
const kv = /^([A-Za-z_]+)\s*=\s*(.*)$/.exec(l);
|
|
196
|
+
if (!kv) continue;
|
|
197
|
+
let value = kv[2];
|
|
198
|
+
while (/^\[/.test(value) && (value.match(/\[/g) ?? []).length > (value.match(/\]/g) ?? []).length && i + 1 < lines.length) value += ` ${lines[++i].trim()}`;
|
|
199
|
+
const strs = [...value.matchAll(/"((?:[^"\\]|\\.)*)"|'([^']*)'/g)].map((m) => m[1] ?? m[2]);
|
|
200
|
+
if (kv[1] === 'command') current.command = strs[0];
|
|
201
|
+
else if (kv[1] === 'args') current.args = strs;
|
|
202
|
+
else if (kv[1] === 'url') current.urls.push(strs[0]);
|
|
203
|
+
}
|
|
204
|
+
for (const s of servers.values()) out.push(...mcpServerIssues({ ...s, file: rel }));
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
const doc = parseJsonc(text);
|
|
208
|
+
for (const holder of [doc?.mcpServers, doc?.servers, doc?.mcp?.servers]) {
|
|
209
|
+
if (!holder || typeof holder !== 'object') continue;
|
|
210
|
+
for (const [name, s] of Object.entries(holder)) {
|
|
211
|
+
if (!s || typeof s !== 'object') continue;
|
|
212
|
+
const urls = [s.url, s.httpUrl, s.serverUrl].filter((u) => typeof u === 'string');
|
|
213
|
+
const command = typeof s.command === 'string' ? s.command : null;
|
|
214
|
+
const args = Array.isArray(s.args) ? s.args.filter((a) => typeof a === 'string') : [];
|
|
215
|
+
out.push(...mcpServerIssues({ name, command, args, urls, line: lineOf(text, `"${name}"`), file: rel }));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export const isMcpFile = (rel) => /(^|\/)(\.mcp\.json|\.cursor\/mcp\.json|\.vscode\/mcp\.json|\.gemini\/settings\.json|\.codex\/config\.toml)$/.test(rel);
|
|
222
|
+
export const isAutoRunFile = (rel) => /(^|\/)(\.vscode\/tasks\.json|\.devcontainer\.json|\.claude\/settings[^/]*\.json|\.gemini\/settings\.json|\.codex\/[^/]+\.(json|toml)|\.cursor\/(?!rules\/)[^/]+\.json|package\.json)$/.test(rel)
|
|
223
|
+
|| /(^|\/)\.devcontainer\/(.*\/)?[^/]+\.json$/.test(rel) || /(^|\/)\.(husky|githooks)\/(?!_\/)[^/]+$/.test(rel);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// agent-surface (REQ-017): what a hostile repository can use against a developer's coding agent or machine.
|
|
2
|
+
// Hidden Unicode, auto-run configuration that downloads and runs content, MCP servers launching unpinned or plain-HTTP packages,
|
|
3
|
+
// and instruction files that direct agents to fetch and run code, read credentials, or follow hidden instructions.
|
|
4
|
+
// Custom code, no external tool. Reads files only: nothing in the scanned repository is ever executed.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { listFiles } from '../../core/detect.mjs';
|
|
8
|
+
import { gitFileLists } from '../_lib/gitfiles.mjs';
|
|
9
|
+
import { autoRunIssues, isAutoRunFile, isMcpFile, mcpIssues } from './config.mjs';
|
|
10
|
+
import { instructionIssues, isInstructionFile } from './instructions.mjs';
|
|
11
|
+
import { scanUnicode } from './unicode.mjs';
|
|
12
|
+
|
|
13
|
+
export const id = 'agent-surface';
|
|
14
|
+
export const title = 'Agent and developer-environment surface';
|
|
15
|
+
export const modes = ['scan', 'deep', 'release'];
|
|
16
|
+
export const requiredIn = ['scan', 'deep', 'release'];
|
|
17
|
+
export const timeoutSec = 600;
|
|
18
|
+
|
|
19
|
+
const MAX_BYTES = 10 * 1024 * 1024;
|
|
20
|
+
const SKIP_SEGMENTS = new Set(['node_modules', '.git', '.vsg', '.pnpm-store']);
|
|
21
|
+
const LOCKFILES = new Set(['package-lock.json', 'npm-shrinkwrap.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'poetry.lock', 'Pipfile.lock', 'uv.lock', 'Cargo.lock', 'go.sum', 'composer.lock', 'Gemfile.lock']);
|
|
22
|
+
const DOC_EXT = /\.(md|mdx|markdown|txt|rst|mdc)$/i;
|
|
23
|
+
const CONFIG_EXT = /\.(json|jsonc|ya?ml|toml|ini|cfg|conf|env|properties)$/i;
|
|
24
|
+
const base = (f) => f.slice(f.lastIndexOf('/') + 1);
|
|
25
|
+
|
|
26
|
+
export const applies = () => true;
|
|
27
|
+
|
|
28
|
+
/** "lockfile" (hidden Unicode only), "sensitive" (instruction, config and documentation files) or "code". */
|
|
29
|
+
export function classify(rel) {
|
|
30
|
+
const b = base(rel);
|
|
31
|
+
if (LOCKFILES.has(b)) return 'lockfile';
|
|
32
|
+
if (isInstructionFile(rel) || isAutoRunFile(rel) || isMcpFile(rel) || DOC_EXT.test(b) || CONFIG_EXT.test(b) || b.startsWith('.')
|
|
33
|
+
|| /(^|\/)\.(claude|cursor|codex|gemini|vscode|devcontainer|husky|githooks|github)\//.test(rel)) return 'sensitive';
|
|
34
|
+
return 'code';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function candidateFiles(ctx) {
|
|
38
|
+
const walked = listFiles(ctx.repo);
|
|
39
|
+
const lists = await gitFileLists(ctx);
|
|
40
|
+
let files;
|
|
41
|
+
let source;
|
|
42
|
+
if (lists && lists.tracked.length + lists.untracked.length > 0) {
|
|
43
|
+
// tracked and untracked-not-ignored files, plus config paths that exist even when ignored (e.g. .claude/settings.local.json)
|
|
44
|
+
files = new Set([...lists.tracked, ...lists.untracked]);
|
|
45
|
+
for (const f of walked) if (isAutoRunFile(f) || isMcpFile(f) || isInstructionFile(f)) files.add(f);
|
|
46
|
+
source = 'git ls-files';
|
|
47
|
+
} else {
|
|
48
|
+
files = new Set(walked);
|
|
49
|
+
source = 'directory walk';
|
|
50
|
+
}
|
|
51
|
+
const outRel = ctx.out ? path.relative(ctx.repo, ctx.out).split(path.sep).join('/') : '';
|
|
52
|
+
const underOut = (f) => outRel && !outRel.startsWith('..') && !path.isAbsolute(outRel) && (f === outRel || f.startsWith(`${outRel}/`));
|
|
53
|
+
const nested = (ctx.nestedRepos ?? []).map((n) => `${n.path}/`);
|
|
54
|
+
const inNested = (f) => nested.some((n) => f.startsWith(n));
|
|
55
|
+
return { source, files: [...files].filter((f) => !f.split('/').some((s) => SKIP_SEGMENTS.has(s)) && !underOut(f) && !inNested(f)).sort() };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readText(repo, rel, skipped) {
|
|
59
|
+
const full = path.join(repo, rel);
|
|
60
|
+
let st;
|
|
61
|
+
try { st = fs.lstatSync(full); } catch { return null; }
|
|
62
|
+
if (!st.isFile()) return null; // symlinks are never followed
|
|
63
|
+
if (st.size > MAX_BYTES) { skipped.push({ file: rel, reason: 'over 10 MB' }); return null; }
|
|
64
|
+
let buf;
|
|
65
|
+
try { buf = fs.readFileSync(full); } catch { return null; }
|
|
66
|
+
if (buf.subarray(0, 8000).includes(0)) return null; // binary
|
|
67
|
+
return buf.toString('utf8');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const UNICODE_TITLES = {
|
|
71
|
+
'tag-characters': ['Hidden Unicode tag characters', 'Unicode tag characters (U+E0000 to U+E007F) are invisible in editors and reviews but are read by language models, so they can carry a hidden prompt.', 'Delete the characters. Review how they got in: they are almost never legitimate outside emoji flag sequences.'],
|
|
72
|
+
'bidi-controls': ['Hidden bidirectional control characters', 'Bidirectional controls reorder how text is displayed, so what a reviewer reads differs from what is executed or sent to an agent (the "Trojan Source" technique).', 'Delete the control characters.'],
|
|
73
|
+
'zero-width': ['Hidden zero-width characters', 'Zero-width characters are invisible, so they can hide text, split keywords past filters or encode data that a reviewer cannot see.', 'Delete the characters unless they are intentional, and then accept the finding with a reason.'],
|
|
74
|
+
'variation-selectors': ['Hidden variation selectors', 'Variation selectors outside emoji are invisible and can encode hidden data.', 'Delete the characters.'],
|
|
75
|
+
};
|
|
76
|
+
const LOCKFILE_RULES = new Set(['tag-characters', 'bidi-controls']);
|
|
77
|
+
|
|
78
|
+
export async function run(ctx) {
|
|
79
|
+
const { source, files } = await candidateFiles(ctx);
|
|
80
|
+
const skipped = [];
|
|
81
|
+
const findings = [];
|
|
82
|
+
const add = (rel, f) => findings.push({
|
|
83
|
+
category: 'agent-surface', confidence: 'high', tool: 'vsg', refs: [], ...f, ruleId: f.ruleId ?? f.rule, key: f.key ?? f.rule,
|
|
84
|
+
location: { file: rel, line: f.line },
|
|
85
|
+
});
|
|
86
|
+
let scanned = 0;
|
|
87
|
+
for (const rel of files) {
|
|
88
|
+
const text = readText(ctx.repo, rel, skipped);
|
|
89
|
+
if (text === null) continue;
|
|
90
|
+
scanned++;
|
|
91
|
+
const kind = classify(rel);
|
|
92
|
+
for (const u of scanUnicode(text, kind === 'code' ? 'code' : 'sensitive')) {
|
|
93
|
+
if (kind === 'lockfile' && !LOCKFILE_RULES.has(u.rule)) continue;
|
|
94
|
+
const [t, why, fix] = UNICODE_TITLES[u.rule];
|
|
95
|
+
add(rel, { rule: u.rule, severity: u.severity, line: u.line, title: `${t} in ${rel}`, description: `${u.count} occurrence(s). ${why}`, evidence: u.evidence, fix });
|
|
96
|
+
}
|
|
97
|
+
if (kind === 'lockfile') continue;
|
|
98
|
+
const issues = [];
|
|
99
|
+
if (isAutoRunFile(rel)) issues.push(...autoRunIssues(rel, text));
|
|
100
|
+
if (isMcpFile(rel)) issues.push(...mcpIssues(rel, text));
|
|
101
|
+
if (isInstructionFile(rel)) issues.push(...instructionIssues(rel, text));
|
|
102
|
+
for (const i of issues) add(rel, i);
|
|
103
|
+
}
|
|
104
|
+
const oversized = skipped.filter((s) => s.reason === 'over 10 MB');
|
|
105
|
+
return {
|
|
106
|
+
status: 'ran',
|
|
107
|
+
reason: oversized.length ? `${oversized.length} file(s) over 10 MB were skipped (${oversized.slice(0, 3).map((s) => s.file).join(', ')}${oversized.length > 3 ? ', ...' : ''})` : '',
|
|
108
|
+
findings,
|
|
109
|
+
evidence: { fileSource: source, filesListed: files.length, filesScanned: scanned, skipped },
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default { id, title, modes, requiredIn, timeoutSec, applies, run };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Instruction files (table row 7): text that directs an agent to fetch and run code, read or send credentials, or follow hidden instructions.
|
|
2
|
+
import { clip, fetchAndRun } from './patterns.mjs';
|
|
3
|
+
|
|
4
|
+
export const isInstructionFile = (rel) => /(^|\/)(AGENTS\.md|CLAUDE\.md|GEMINI\.md|SKILL\.md|\.cursorrules|\.windsurfrules|\.clinerules|copilot-instructions\.md)$/.test(rel)
|
|
5
|
+
|| /(^|\/)\.cursor\/rules\//.test(rel) || /(^|\/)\.clinerules\//.test(rel);
|
|
6
|
+
|
|
7
|
+
const CRED_PATH = /(?:~|\$HOME|%USERPROFILE%)[\\/]\.(?:ssh|aws|npmrc|netrc|docker|kube)\b|\.ssh[\\/]id_[a-z0-9]+|\.aws[\\/]credentials|\.config[\\/]gcloud|\bid_rsa\b|\bprintenv\b(?!\s+[A-Za-z_])|\benv\s*(?:>>?\s*\S|\|\s*(?:grep|curl|wget|nc|base64|tee|sort|head|xargs)\b)|\bexport\s+-p\b|\bGet-ChildItem\s+env:|json\.dumps\(\s*(?:dict\()?os\.environ|JSON\.stringify\(\s*process\.env/i;
|
|
8
|
+
const SEND_SECRET = /\b(?:send|upload|post(?![-\w])|transmit|exfiltrate|email|forward)\b[^\n]{0,80}?\b(?:tokens?|secrets?|credentials?|passwords?|(?:api|private|ssh|secret|access|signing|encryption|service[ _-]?role)[ _-]?keys?|\.env)\b/i;
|
|
9
|
+
const NEGATION = /\b(?:never|do not|don't|dont|must not|mustn't|should not|shouldn't|cannot|can't|avoid|not)\b[^.;:!?]*$/i;
|
|
10
|
+
const HIDING = /(?:font-size\s*:\s*[01]\s*(?:px|pt|em|rem|%)?\s*(?:[;"']|$)|(?:^|[;"'\s])color\s*:\s*(?:#f{3}(?:f{3})?\b|white\b|rgb\(\s*255\s*,\s*255\s*,\s*255\s*\))|display\s*:\s*none|visibility\s*:\s*hidden|opacity\s*:\s*0(?:\.0+)?\s*(?:[;"']|$)|(?:width|height)\s*:\s*0(?:px)?\s*[;"'])/i;
|
|
11
|
+
const HIDDEN_ELEMENT = /<(\w+)\b([^>]*)>([^<]{10,})/g;
|
|
12
|
+
const COMMENT = new RegExp('<!--([\\s\\S]*?)-->', 'g');
|
|
13
|
+
const DIRECTIVE = [
|
|
14
|
+
/\b(?:ignore|disregard|forget|override)\b[^.\n]{0,40}\b(?:instructions?|rules?|prompts?|above|previous|prior|guidelines)\b/i,
|
|
15
|
+
/\byou\s+(?:must|should|will|shall|need\s+to|have\s+to|are\s+(?:now|to))\b/i,
|
|
16
|
+
/\b(?:ai|llm|agents?|assistants?|claude|copilot|gemini|cursor|codex)\b[^.\n]{0,30}\b(?:must|should|always|never|run|execute|ignore|do\s+not)\b/i,
|
|
17
|
+
/\bdo\s+not\s+(?:tell|mention|reveal|show|inform)\b/i,
|
|
18
|
+
/\bsystem\s+prompt\b/i,
|
|
19
|
+
/\b(?:run|execute)\s+(?:the\s+)?(?:following|this|these)\b/i,
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const lineAt = (text, idx) => { let n = 1; for (let i = text.indexOf('\n'); i >= 0 && i < idx; i = text.indexOf('\n', i + 1)) n++; return n; };
|
|
23
|
+
|
|
24
|
+
// "send BOTH the apikey header and Authorization: Bearer <key>" explains how to call the project's own API; nothing leaves the machine.
|
|
25
|
+
// A send-a-key sentence counts as that when it talks about a header or Authorization and names no destination (a URL or a domain).
|
|
26
|
+
const AUTH_HEADER_USE = /\b(?:headers?|authorization|bearer)\b/i;
|
|
27
|
+
const DESTINATION = /https?:\/\/|\b[a-z0-9-]+\.[a-z]{2,}\b/i;
|
|
28
|
+
const isApiAuthUse = (line, m) => { const window = line.slice(m.index, m.index + m[0].length + 120); return AUTH_HEADER_USE.test(window) && !DESTINATION.test(window); };
|
|
29
|
+
|
|
30
|
+
function credentialHit(line) {
|
|
31
|
+
for (const re of [CRED_PATH, SEND_SECRET]) {
|
|
32
|
+
const m = re.exec(line);
|
|
33
|
+
if (m && !NEGATION.test(line.slice(0, m.index)) && !(re === SEND_SECRET && isApiAuthUse(line, m))) return m[0];
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function instructionIssues(rel, text) {
|
|
39
|
+
const out = [];
|
|
40
|
+
const lines = text.split(/\r?\n/);
|
|
41
|
+
const push = (i) => ({ line: i + 1, file: rel });
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
const add = (issue) => { const k = `${issue.rule}:${issue.line}`; if (!seen.has(k)) { seen.add(k); out.push(issue); } };
|
|
44
|
+
lines.forEach((line, i) => {
|
|
45
|
+
const run = fetchAndRun(line);
|
|
46
|
+
if (run) {
|
|
47
|
+
add({
|
|
48
|
+
rule: 'instruction-fetch-exec', key: `instr-run:${i + 1}`, severity: 'high', ...push(i),
|
|
49
|
+
title: 'Instruction file directs agents to fetch and run code',
|
|
50
|
+
description: `${rel} line ${i + 1} contains a command that ${run.via === 'download' ? 'downloads' : 'decodes'} content and executes it ("${run.fetch}" ... "${run.exec}"). An agent that follows its instruction files will run whatever the remote server returns.`,
|
|
51
|
+
evidence: clip(line), fix: 'Remove the instruction, or replace it with a committed, reviewed script and a pinned checksum.',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const cred = credentialHit(line);
|
|
55
|
+
if (cred) {
|
|
56
|
+
add({
|
|
57
|
+
rule: 'instruction-credentials', key: `instr-cred:${i + 1}`, severity: 'high', ...push(i),
|
|
58
|
+
title: 'Instruction file directs agents to read or send credentials',
|
|
59
|
+
description: `${rel} line ${i + 1} tells an agent to read credential files or environment dumps, or to send tokens, secrets or keys somewhere ("${clip(cred, 60)}").`,
|
|
60
|
+
evidence: clip(line), fix: 'Remove the instruction. Agents should never be told to read or transmit credentials.',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
for (const m of line.matchAll(HIDDEN_ELEMENT)) {
|
|
64
|
+
if (HIDING.test(m[2])) {
|
|
65
|
+
add({
|
|
66
|
+
rule: 'instruction-hidden-markup', key: `instr-hidden-markup:${i + 1}`, severity: 'high', ...push(i),
|
|
67
|
+
title: 'Instruction file hides text with zero-size or white-text markup',
|
|
68
|
+
description: `${rel} line ${i + 1} wraps text in markup that renders it invisible to a human reviewer while an agent still reads it.`,
|
|
69
|
+
evidence: clip(`<${m[1]} ${m[2].trim()}> ${m[3]}`), fix: 'Remove the hidden element. Instruction files should contain only visible text.',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
for (const m of text.matchAll(COMMENT)) {
|
|
75
|
+
const body = m[1];
|
|
76
|
+
const hit = DIRECTIVE.map((re) => re.exec(body)).find(Boolean) ?? (fetchAndRun(body) ? ['fetch-and-run'] : null) ?? (credentialHit(body) ? ['credentials'] : null);
|
|
77
|
+
if (!hit) continue;
|
|
78
|
+
const line = lineAt(text, m.index);
|
|
79
|
+
add({
|
|
80
|
+
rule: 'instruction-hidden-comment', key: `instr-comment:${line}`, severity: 'high', line, file: rel,
|
|
81
|
+
title: 'Instruction file hides instructions in an HTML comment',
|
|
82
|
+
description: `${rel} line ${line} has an HTML comment that reads as an instruction to an agent ("${clip(hit[0], 60)}"). Markdown viewers do not show comments, so a human reviewer misses it while an agent reads it.`,
|
|
83
|
+
evidence: clip(body), fix: 'Remove the comment, or move the instruction into visible text a reviewer can read.',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Shared detection helpers for the agent-surface check: the fetch-or-decode-then-execute predicate, JSONC parsing, line lookup.
|
|
2
|
+
|
|
3
|
+
const FETCH = [
|
|
4
|
+
/\b(?:curl|wget|Invoke-WebRequest|iwr|irm|Invoke-RestMethod)\b/i,
|
|
5
|
+
/\bfetch\s*\(/i,
|
|
6
|
+
/\bcertutil\b[^\n]*-urlcache/i,
|
|
7
|
+
/\bbitsadmin\b/i,
|
|
8
|
+
];
|
|
9
|
+
const DECODE = [
|
|
10
|
+
/\bbase64\s+(?:-[a-zA-Z]*d[a-zA-Z]*|--decode)\b/i,
|
|
11
|
+
/\bFromBase64String\b/i,
|
|
12
|
+
/\batob\s*\(/i,
|
|
13
|
+
/\bcertutil\b[^\n]*-decode/i,
|
|
14
|
+
];
|
|
15
|
+
const EXEC = [
|
|
16
|
+
/\|\s*(?:sudo\s+(?:-\S+\s+)*)?(?:\/\S*\/)?(?:ba|z|da|k|a)?sh\b/i, // | sh, | bash, | sudo bash
|
|
17
|
+
/\|\s*(?:\/\S*\/)?(?:node|nodejs|bun|deno|python3?|perl|ruby|php|pwsh|powershell|iex)\b/i,
|
|
18
|
+
/\b(?:iex|Invoke-Expression)\b/i,
|
|
19
|
+
/\b(?:ba|z|da)?sh\s+(?:-\w+\s+)*-c\s+["']?\$\(/i, // sh -c "$(curl ...)"
|
|
20
|
+
/\b(?:ba|z)?sh\s+<\(/i, // bash <(curl ...)
|
|
21
|
+
/\beval\b/i,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/** Returns { via, fetch, exec } when the text fetches or decodes content and executes it, otherwise null. */
|
|
25
|
+
export function fetchAndRun(text) {
|
|
26
|
+
const s = String(text);
|
|
27
|
+
const fetch = FETCH.map((re) => re.exec(s)).find(Boolean);
|
|
28
|
+
const decode = fetch ? null : DECODE.map((re) => re.exec(s)).find(Boolean);
|
|
29
|
+
const first = fetch ?? decode;
|
|
30
|
+
if (!first) return null;
|
|
31
|
+
const exec = EXEC.map((re) => re.exec(s)).find(Boolean);
|
|
32
|
+
if (!exec) return null;
|
|
33
|
+
return { via: fetch ? 'download' : 'decode', fetch: first[0], exec: exec[0].trim() };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** JSON with comments and trailing commas (VS Code and devcontainer files). Returns undefined when unparseable. */
|
|
37
|
+
export function parseJsonc(text) {
|
|
38
|
+
const src = String(text).replace(/^\uFEFF/, '');
|
|
39
|
+
try { return JSON.parse(src); } catch { /* fall through to the tolerant pass */ }
|
|
40
|
+
let out = '';
|
|
41
|
+
for (let i = 0; i < src.length; i++) {
|
|
42
|
+
const c = src[i];
|
|
43
|
+
if (c === '"') {
|
|
44
|
+
let j = i + 1;
|
|
45
|
+
while (j < src.length && src[j] !== '"') j += src[j] === '\\' ? 2 : 1;
|
|
46
|
+
out += src.slice(i, j + 1);
|
|
47
|
+
i = j;
|
|
48
|
+
} else if (c === '/' && src[i + 1] === '/') {
|
|
49
|
+
while (i < src.length && src[i] !== '\n') i++;
|
|
50
|
+
out += '\n';
|
|
51
|
+
} else if (c === '/' && src[i + 1] === '*') {
|
|
52
|
+
const end = src.indexOf('*/', i + 2);
|
|
53
|
+
i = end < 0 ? src.length : end + 1;
|
|
54
|
+
} else out += c;
|
|
55
|
+
}
|
|
56
|
+
try { return JSON.parse(out.replace(/,(\s*[}\]])/g, '$1')); } catch { return undefined; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Line number (1-based) of the first occurrence of needle at or after `from`, or 1. */
|
|
60
|
+
export function lineOf(text, needle, from = 0) {
|
|
61
|
+
const at = needle ? text.indexOf(needle, from) : -1;
|
|
62
|
+
if (at < 0) return 1;
|
|
63
|
+
let line = 1;
|
|
64
|
+
for (let i = text.indexOf('\n'); i >= 0 && i < at; i = text.indexOf('\n', i + 1)) line++;
|
|
65
|
+
return line;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const clip = (s, n = 200) => { const t = String(s).replace(/\s+/g, ' ').trim(); return t.length > n ? `${t.slice(0, n)}...` : t; };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Hidden-Unicode rows of the agent-surface table: tag characters, bidi controls, zero-width characters, variation selectors.
|
|
2
|
+
// Emoji sequences (ZWJ, skin tones, FE0F, subdivision flags) are recognised and never flagged.
|
|
3
|
+
|
|
4
|
+
const CANDIDATE = /[\u200B-\u200D\u2060\uFEFF\u202A-\u202E\u2066-\u2069\uFE00-\uFE0F\u{E0000}-\u{E01EF}]/gu;
|
|
5
|
+
// Subdivision flag: black flag + 2 to 7 lowercase letters or digits as tag characters + cancel tag (e.g. England: gbeng).
|
|
6
|
+
const FLAG = /\u{1F3F4}[\u{E0030}-\u{E0039}\u{E0061}-\u{E007A}]{2,7}\u{E007F}/gu;
|
|
7
|
+
|
|
8
|
+
const PICTO = /^\p{Extended_Pictographic}$/u;
|
|
9
|
+
const MODIFIER = /^\p{Emoji_Modifier}$/u;
|
|
10
|
+
const EMOJI = /^\p{Emoji}$/u;
|
|
11
|
+
const hex = (cp) => `U+${cp.toString(16).toUpperCase().padStart(4, '0')}`;
|
|
12
|
+
|
|
13
|
+
function prevCp(text, i) {
|
|
14
|
+
const lo = text.charCodeAt(i - 1);
|
|
15
|
+
if (lo >= 0xdc00 && lo <= 0xdfff && i >= 2) {
|
|
16
|
+
const hi = text.charCodeAt(i - 2);
|
|
17
|
+
if (hi >= 0xd800 && hi <= 0xdbff) return { cp: ((hi - 0xd800) << 10) + (lo - 0xdc00) + 0x10000, at: i - 2 };
|
|
18
|
+
}
|
|
19
|
+
return i >= 1 ? { cp: lo, at: i - 1 } : null;
|
|
20
|
+
}
|
|
21
|
+
const nextCp = (text, i) => (i < text.length ? text.codePointAt(i) : null);
|
|
22
|
+
|
|
23
|
+
/** The emoji (or pictographic symbol) a joiner or selector at index i is attached to, skipping skin tones and FE0F. */
|
|
24
|
+
function baseBefore(text, i) {
|
|
25
|
+
let p = prevCp(text, i);
|
|
26
|
+
while (p && (p.cp === 0xfe0f || p.cp === 0xfe0e || MODIFIER.test(String.fromCodePoint(p.cp)))) p = prevCp(text, p.at);
|
|
27
|
+
return p;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function lineIndex(text) {
|
|
31
|
+
const starts = [0];
|
|
32
|
+
for (let i = text.indexOf('\n'); i >= 0; i = text.indexOf('\n', i + 1)) starts.push(i + 1);
|
|
33
|
+
return (idx) => {
|
|
34
|
+
let lo = 0;
|
|
35
|
+
let hi = starts.length - 1;
|
|
36
|
+
while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (starts[mid] <= idx) lo = mid; else hi = mid - 1; }
|
|
37
|
+
return { line: lo + 1, col: idx - starts[lo] + 1 };
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Scans text. `tier` is "sensitive" (instruction, config, docs, lockfiles) or "code".
|
|
43
|
+
* Returns one issue per rule: { rule, severity, line, count, evidence, hidden? }.
|
|
44
|
+
*/
|
|
45
|
+
export function scanUnicode(text, tier) {
|
|
46
|
+
if (text.search(CANDIDATE) < 0) return [];
|
|
47
|
+
const flags = [...text.matchAll(FLAG)].map((m) => [m.index, m.index + m[0].length]);
|
|
48
|
+
const inFlag = (i) => flags.some(([a, b]) => i >= a && i < b);
|
|
49
|
+
const where = lineIndex(text);
|
|
50
|
+
const found = { tag: [], bidi: [], zero: [], variation: [] };
|
|
51
|
+
for (const m of text.matchAll(CANDIDATE)) {
|
|
52
|
+
const i = m.index;
|
|
53
|
+
const cp = m[0].codePointAt(0);
|
|
54
|
+
const at = where(i);
|
|
55
|
+
const item = { cp, ...at, i };
|
|
56
|
+
if (cp >= 0xe0000 && cp <= 0xe007f) { if (!inFlag(i)) found.tag.push(item); continue; }
|
|
57
|
+
if ((cp >= 0x202a && cp <= 0x202e) || (cp >= 0x2066 && cp <= 0x2069)) { found.bidi.push(item); continue; }
|
|
58
|
+
if (cp >= 0xe0100 && cp <= 0xe01ef) { found.variation.push(item); continue; }
|
|
59
|
+
if (cp >= 0xfe00 && cp <= 0xfe0f) {
|
|
60
|
+
const base = baseBefore(text, i);
|
|
61
|
+
const ch = base ? String.fromCodePoint(base.cp) : '';
|
|
62
|
+
const keycap = base && /^[0-9#*]$/.test(ch) && nextCp(text, i + 1) === 0x20e3;
|
|
63
|
+
const legit = base && (PICTO.test(ch) || (EMOJI.test(ch) && !/^[0-9#*]$/.test(ch)) || keycap);
|
|
64
|
+
if (!legit) found.variation.push(item);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (cp === 0xfeff && i === 0) continue; // byte order mark
|
|
68
|
+
if (cp === 0x200d) {
|
|
69
|
+
const base = baseBefore(text, i);
|
|
70
|
+
const next = nextCp(text, i + 1);
|
|
71
|
+
const between = base && PICTO.test(String.fromCodePoint(base.cp)) && next !== null && PICTO.test(String.fromCodePoint(next));
|
|
72
|
+
if (between) continue;
|
|
73
|
+
}
|
|
74
|
+
found.zero.push(item);
|
|
75
|
+
}
|
|
76
|
+
const sensitive = tier !== 'code';
|
|
77
|
+
const list = (items) => items.slice(0, 8).map((x) => `${hex(x.cp)} at ${x.line}:${x.col}`).join(', ') + (items.length > 8 ? `, and ${items.length - 8} more` : '');
|
|
78
|
+
const issue = (rule, severity, items, extra = {}) => ({ rule, severity, line: items[0].line, count: items.length, evidence: list(items), ...extra });
|
|
79
|
+
const out = [];
|
|
80
|
+
if (found.tag.length) {
|
|
81
|
+
const decoded = found.tag.map((x) => (x.cp >= 0xe0020 && x.cp <= 0xe007e ? String.fromCharCode(x.cp - 0xe0000) : '')).join('');
|
|
82
|
+
out.push(issue('tag-characters', 'critical', found.tag, { evidence: `${list(found.tag)}${decoded ? `; decoded hidden text: "${decoded.slice(0, 200)}"` : ''}` }));
|
|
83
|
+
}
|
|
84
|
+
if (found.bidi.length) out.push(issue('bidi-controls', sensitive ? 'critical' : 'high', found.bidi));
|
|
85
|
+
if (found.zero.length) out.push(issue('zero-width', sensitive ? 'high' : 'medium', found.zero));
|
|
86
|
+
if (found.variation.length) out.push(issue('variation-selectors', 'high', found.variation));
|
|
87
|
+
return out;
|
|
88
|
+
}
|