gentyr 1.3.0
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/.claude/agents/antipattern-hunter.md +176 -0
- package/.claude/agents/code-reviewer.md +205 -0
- package/.claude/agents/code-writer.md +154 -0
- package/.claude/agents/deputy-cto.md +309 -0
- package/.claude/agents/feedback-agent.md +101 -0
- package/.claude/agents/investigator.md +136 -0
- package/.claude/agents/product-manager.md +97 -0
- package/.claude/agents/project-manager.md +116 -0
- package/.claude/agents/repo-hygiene-expert.md +626 -0
- package/.claude/agents/secret-manager.md +324 -0
- package/.claude/agents/test-writer.md +354 -0
- package/.claude/commands/configure-personas.md +144 -0
- package/.claude/commands/cto-report.md +36 -0
- package/.claude/commands/demo.md +89 -0
- package/.claude/commands/deputy-cto.md +345 -0
- package/.claude/commands/hotfix.md +31 -0
- package/.claude/commands/overdrive-gentyr.md +167 -0
- package/.claude/commands/product-manager.md +32 -0
- package/.claude/commands/push-migrations.md +86 -0
- package/.claude/commands/push-secrets.md +97 -0
- package/.claude/commands/services.json.example +30 -0
- package/.claude/commands/setup-gentyr.md +396 -0
- package/.claude/commands/show.md +42 -0
- package/.claude/commands/spawn-tasks.md +79 -0
- package/.claude/commands/toggle-automation-gentyr.md +75 -0
- package/.claude/commands/toggle-product-manager.md +19 -0
- package/.claude/commands/triage.md +69 -0
- package/.claude/hooks/README.md +686 -0
- package/.claude/hooks/__tests__/README.md +129 -0
- package/.claude/hooks/agent-tracker.js +434 -0
- package/.claude/hooks/antipattern-hunter-hook.js +401 -0
- package/.claude/hooks/api-key-watcher.js +289 -0
- package/.claude/hooks/block-no-verify.js +301 -0
- package/.claude/hooks/bypass-approval-hook.js +313 -0
- package/.claude/hooks/compliance-checker.js +1309 -0
- package/.claude/hooks/config-reader.js +143 -0
- package/.claude/hooks/credential-file-guard.js +1139 -0
- package/.claude/hooks/credential-health-check.js +168 -0
- package/.claude/hooks/credential-sync-hook.js +79 -0
- package/.claude/hooks/cto-notification-hook.js +656 -0
- package/.claude/hooks/feedback-launcher.js +424 -0
- package/.claude/hooks/feedback-orchestrator.js +367 -0
- package/.claude/hooks/gentyr-splash.js +47 -0
- package/.claude/hooks/gentyr-sync.js +389 -0
- package/.claude/hooks/hourly-automation.js +3340 -0
- package/.claude/hooks/key-sync.js +899 -0
- package/.claude/hooks/lib/approval-utils.js +731 -0
- package/.claude/hooks/lib/feature-branch-helper.js +102 -0
- package/.claude/hooks/lib/worktree-manager.js +330 -0
- package/.claude/hooks/mapping-validator.js +285 -0
- package/.claude/hooks/plan-executor.js +398 -0
- package/.claude/hooks/playwright-cli-guard.js +104 -0
- package/.claude/hooks/playwright-health-check.js +71 -0
- package/.claude/hooks/pre-commit-review.js +725 -0
- package/.claude/hooks/prompts/local-spec-enforcement.md +310 -0
- package/.claude/hooks/prompts/mapping-fix.md +92 -0
- package/.claude/hooks/prompts/mapping-review.md +140 -0
- package/.claude/hooks/prompts/schema-mapper.md +185 -0
- package/.claude/hooks/prompts/spec-enforcement.md +233 -0
- package/.claude/hooks/protected-action-approval-hook.js +336 -0
- package/.claude/hooks/protected-action-gate.js +562 -0
- package/.claude/hooks/protected-actions.json +208 -0
- package/.claude/hooks/protected-actions.json.template +122 -0
- package/.claude/hooks/quota-monitor.js +490 -0
- package/.claude/hooks/reporters/jest-failure-reporter.js +401 -0
- package/.claude/hooks/reporters/playwright-failure-reporter.js +446 -0
- package/.claude/hooks/reporters/vitest-failure-reporter.js +443 -0
- package/.claude/hooks/schema-mapper-hook.js +544 -0
- package/.claude/hooks/secret-leak-detector.js +216 -0
- package/.claude/hooks/session-reviver.js +514 -0
- package/.claude/hooks/slash-command-prefetch.js +1145 -0
- package/.claude/hooks/stale-work-detector.js +205 -0
- package/.claude/hooks/stop-continue-hook.js +414 -0
- package/.claude/hooks/todo-maintenance.js +522 -0
- package/.claude/hooks/todo-processing-prompt.md +75 -0
- package/.claude/hooks/usage-optimizer.js +791 -0
- package/.claude/mcp/README.md +246 -0
- package/.claude/settings.json.template +168 -0
- package/.mcp.json.template +207 -0
- package/CLAUDE.md +340 -0
- package/CLAUDE.md.gentyr-section +89 -0
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/cli/commands/init.js +471 -0
- package/cli/commands/migrate.js +132 -0
- package/cli/commands/protect.js +271 -0
- package/cli/commands/scaffold.js +48 -0
- package/cli/commands/status.js +133 -0
- package/cli/commands/sync.js +101 -0
- package/cli/commands/uninstall.js +207 -0
- package/cli/index.js +111 -0
- package/cli/lib/config-gen.js +214 -0
- package/cli/lib/resolve-framework.js +97 -0
- package/cli/lib/state.js +140 -0
- package/cli/lib/symlinks.js +260 -0
- package/docs/AUTOMATION-SYSTEMS.md +484 -0
- package/docs/BINARY-PATCHING.md +212 -0
- package/docs/CHANGELOG.md +2830 -0
- package/docs/CREDENTIAL-DETECTION.md +151 -0
- package/docs/CTO-DASHBOARD.md +476 -0
- package/docs/DEPLOYMENT-FLOW.md +477 -0
- package/docs/DEVELOPER.md +116 -0
- package/docs/Executive.md +372 -0
- package/docs/SECRET-PATHS.md +77 -0
- package/docs/SETUP-GUIDE.md +419 -0
- package/docs/STACK.md +109 -0
- package/docs/TESTING.md +440 -0
- package/docs/assets/claude-logo.svg +3 -0
- package/docs/sessions/2026-01-24-spec-suite-implementation.md +190 -0
- package/docs/sessions/2026-02-15-feedback-e2e-audit.md +484 -0
- package/docs/sessions/2026-02-20-credential-rotation-experiments.md +340 -0
- package/docs/sessions/TEST-COVERAGE-REPORT-2026-02-20.md +168 -0
- package/docs/shared/EPHEMERAL-STATE-FILES.md +115 -0
- package/docs/shared/PROTECTION-SYSTEM.md +341 -0
- package/husky/post-commit +10 -0
- package/husky/pre-commit +40 -0
- package/husky/pre-push +94 -0
- package/package.json +43 -0
- package/packages/cto-dashboard/package-lock.json +3510 -0
- package/packages/cto-dashboard/package.json +41 -0
- package/packages/cto-dashboard/pnpm-lock.yaml +2168 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.d.ts +220 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.d.ts.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.js +376 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.js.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/index.d.ts +121 -0
- package/packages/mcp-servers/dist/__testUtils__/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/index.js +180 -0
- package/packages/mcp-servers/dist/__testUtils__/index.js.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.d.ts +84 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.d.ts.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.js +309 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.js.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/index.d.ts +7 -0
- package/packages/mcp-servers/dist/agent-reports/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/index.js +8 -0
- package/packages/mcp-servers/dist/agent-reports/index.js.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/server.d.ts +22 -0
- package/packages/mcp-servers/dist/agent-reports/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/server.js +535 -0
- package/packages/mcp-servers/dist/agent-reports/server.js.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/types.d.ts +258 -0
- package/packages/mcp-servers/dist/agent-reports/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/types.js +81 -0
- package/packages/mcp-servers/dist/agent-reports/types.js.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/index.d.ts +5 -0
- package/packages/mcp-servers/dist/agent-tracker/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/index.js +5 -0
- package/packages/mcp-servers/dist/agent-tracker/index.js.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/server.d.ts +12 -0
- package/packages/mcp-servers/dist/agent-tracker/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/server.js +919 -0
- package/packages/mcp-servers/dist/agent-tracker/server.js.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/types.d.ts +328 -0
- package/packages/mcp-servers/dist/agent-tracker/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/types.js +128 -0
- package/packages/mcp-servers/dist/agent-tracker/types.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.d.ts +27 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.js +167 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.d.ts +6 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.js +6 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.d.ts +13 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.js +959 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.d.ts +41 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.js +8 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.js.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/index.d.ts +8 -0
- package/packages/mcp-servers/dist/cloudflare/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/index.js +8 -0
- package/packages/mcp-servers/dist/cloudflare/index.js.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/server.d.ts +16 -0
- package/packages/mcp-servers/dist/cloudflare/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/server.js +253 -0
- package/packages/mcp-servers/dist/cloudflare/server.js.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/types.d.ts +141 -0
- package/packages/mcp-servers/dist/cloudflare/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/types.js +53 -0
- package/packages/mcp-servers/dist/cloudflare/types.js.map +1 -0
- package/packages/mcp-servers/dist/codecov/index.d.ts +7 -0
- package/packages/mcp-servers/dist/codecov/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/codecov/index.js +7 -0
- package/packages/mcp-servers/dist/codecov/index.js.map +1 -0
- package/packages/mcp-servers/dist/codecov/server.d.ts +21 -0
- package/packages/mcp-servers/dist/codecov/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/codecov/server.js +376 -0
- package/packages/mcp-servers/dist/codecov/server.js.map +1 -0
- package/packages/mcp-servers/dist/codecov/types.d.ts +269 -0
- package/packages/mcp-servers/dist/codecov/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/codecov/types.js +128 -0
- package/packages/mcp-servers/dist/codecov/types.js.map +1 -0
- package/packages/mcp-servers/dist/cto-report/index.d.ts +9 -0
- package/packages/mcp-servers/dist/cto-report/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-report/index.js +9 -0
- package/packages/mcp-servers/dist/cto-report/index.js.map +1 -0
- package/packages/mcp-servers/dist/cto-report/server.d.ts +14 -0
- package/packages/mcp-servers/dist/cto-report/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-report/server.js +859 -0
- package/packages/mcp-servers/dist/cto-report/server.js.map +1 -0
- package/packages/mcp-servers/dist/cto-report/types.d.ts +213 -0
- package/packages/mcp-servers/dist/cto-report/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-report/types.js +29 -0
- package/packages/mcp-servers/dist/cto-report/types.js.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/index.d.ts +7 -0
- package/packages/mcp-servers/dist/cto-reports/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/index.js +8 -0
- package/packages/mcp-servers/dist/cto-reports/index.js.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/server.d.ts +20 -0
- package/packages/mcp-servers/dist/cto-reports/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/server.js +538 -0
- package/packages/mcp-servers/dist/cto-reports/server.js.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/types.d.ts +236 -0
- package/packages/mcp-servers/dist/cto-reports/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/types.js +77 -0
- package/packages/mcp-servers/dist/cto-reports/types.js.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/index.d.ts +7 -0
- package/packages/mcp-servers/dist/deputy-cto/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/index.js +8 -0
- package/packages/mcp-servers/dist/deputy-cto/index.js.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/server.d.ts +23 -0
- package/packages/mcp-servers/dist/deputy-cto/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/server.js +1700 -0
- package/packages/mcp-servers/dist/deputy-cto/server.js.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/types.d.ts +439 -0
- package/packages/mcp-servers/dist/deputy-cto/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/types.js +102 -0
- package/packages/mcp-servers/dist/deputy-cto/types.js.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/index.d.ts +5 -0
- package/packages/mcp-servers/dist/elastic-logs/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/index.js +5 -0
- package/packages/mcp-servers/dist/elastic-logs/index.js.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/server.d.ts +18 -0
- package/packages/mcp-servers/dist/elastic-logs/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/server.js +259 -0
- package/packages/mcp-servers/dist/elastic-logs/server.js.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/types.d.ts +107 -0
- package/packages/mcp-servers/dist/elastic-logs/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/types.js +31 -0
- package/packages/mcp-servers/dist/elastic-logs/types.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.d.ts +2 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.js +2 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.d.ts +21 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.js +580 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.d.ts +331 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.js +40 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.d.ts +9 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.js +9 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.d.ts +36 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.js +392 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.d.ts +152 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.js +67 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.js.map +1 -0
- package/packages/mcp-servers/dist/github/index.d.ts +7 -0
- package/packages/mcp-servers/dist/github/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/github/index.js +7 -0
- package/packages/mcp-servers/dist/github/index.js.map +1 -0
- package/packages/mcp-servers/dist/github/server.d.ts +15 -0
- package/packages/mcp-servers/dist/github/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/github/server.js +686 -0
- package/packages/mcp-servers/dist/github/server.js.map +1 -0
- package/packages/mcp-servers/dist/github/types.d.ts +660 -0
- package/packages/mcp-servers/dist/github/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/github/types.js +209 -0
- package/packages/mcp-servers/dist/github/types.js.map +1 -0
- package/packages/mcp-servers/dist/index.d.ts +30 -0
- package/packages/mcp-servers/dist/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/index.js +32 -0
- package/packages/mcp-servers/dist/index.js.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.d.ts +5 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.js +5 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.js.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.d.ts +15 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.js +252 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.js.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.d.ts +74 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.js +20 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.js.map +1 -0
- package/packages/mcp-servers/dist/onepassword/index.d.ts +2 -0
- package/packages/mcp-servers/dist/onepassword/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/onepassword/index.js +2 -0
- package/packages/mcp-servers/dist/onepassword/index.js.map +1 -0
- package/packages/mcp-servers/dist/onepassword/server.d.ts +2 -0
- package/packages/mcp-servers/dist/onepassword/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/onepassword/server.js +159 -0
- package/packages/mcp-servers/dist/onepassword/server.js.map +1 -0
- package/packages/mcp-servers/dist/onepassword/types.d.ts +55 -0
- package/packages/mcp-servers/dist/onepassword/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/onepassword/types.js +22 -0
- package/packages/mcp-servers/dist/onepassword/types.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/helpers.d.ts +20 -0
- package/packages/mcp-servers/dist/playwright/helpers.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/helpers.js +31 -0
- package/packages/mcp-servers/dist/playwright/helpers.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/index.d.ts +5 -0
- package/packages/mcp-servers/dist/playwright/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/index.js +5 -0
- package/packages/mcp-servers/dist/playwright/index.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/server.d.ts +13 -0
- package/packages/mcp-servers/dist/playwright/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/server.js +1201 -0
- package/packages/mcp-servers/dist/playwright/server.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/types.d.ts +216 -0
- package/packages/mcp-servers/dist/playwright/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/types.js +172 -0
- package/packages/mcp-servers/dist/playwright/types.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.d.ts +39 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.js +71 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.d.ts +5 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.js +5 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.d.ts +34 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.js +538 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.d.ts +305 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.js +123 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.js.map +1 -0
- package/packages/mcp-servers/dist/product-manager/server.d.ts +17 -0
- package/packages/mcp-servers/dist/product-manager/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/product-manager/server.js +690 -0
- package/packages/mcp-servers/dist/product-manager/server.js.map +1 -0
- package/packages/mcp-servers/dist/product-manager/types.d.ts +286 -0
- package/packages/mcp-servers/dist/product-manager/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/product-manager/types.js +99 -0
- package/packages/mcp-servers/dist/product-manager/types.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.d.ts +7 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.js +7 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.d.ts +19 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.js +174 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.d.ts +35 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.js +465 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.d.ts +127 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.js +80 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.js.map +1 -0
- package/packages/mcp-servers/dist/render/index.d.ts +8 -0
- package/packages/mcp-servers/dist/render/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/render/index.js +8 -0
- package/packages/mcp-servers/dist/render/index.js.map +1 -0
- package/packages/mcp-servers/dist/render/server.d.ts +15 -0
- package/packages/mcp-servers/dist/render/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/render/server.js +428 -0
- package/packages/mcp-servers/dist/render/server.js.map +1 -0
- package/packages/mcp-servers/dist/render/types.d.ts +273 -0
- package/packages/mcp-servers/dist/render/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/render/types.js +102 -0
- package/packages/mcp-servers/dist/render/types.js.map +1 -0
- package/packages/mcp-servers/dist/resend/index.d.ts +7 -0
- package/packages/mcp-servers/dist/resend/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/resend/index.js +7 -0
- package/packages/mcp-servers/dist/resend/index.js.map +1 -0
- package/packages/mcp-servers/dist/resend/server.d.ts +15 -0
- package/packages/mcp-servers/dist/resend/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/resend/server.js +298 -0
- package/packages/mcp-servers/dist/resend/server.js.map +1 -0
- package/packages/mcp-servers/dist/resend/types.d.ts +222 -0
- package/packages/mcp-servers/dist/resend/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/resend/types.js +58 -0
- package/packages/mcp-servers/dist/resend/types.js.map +1 -0
- package/packages/mcp-servers/dist/review-queue/index.d.ts +6 -0
- package/packages/mcp-servers/dist/review-queue/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/review-queue/index.js +6 -0
- package/packages/mcp-servers/dist/review-queue/index.js.map +1 -0
- package/packages/mcp-servers/dist/review-queue/server.d.ts +17 -0
- package/packages/mcp-servers/dist/review-queue/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/review-queue/server.js +348 -0
- package/packages/mcp-servers/dist/review-queue/server.js.map +1 -0
- package/packages/mcp-servers/dist/review-queue/types.d.ts +162 -0
- package/packages/mcp-servers/dist/review-queue/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/review-queue/types.js +56 -0
- package/packages/mcp-servers/dist/review-queue/types.js.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/server.d.ts +19 -0
- package/packages/mcp-servers/dist/secret-sync/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/server.js +1139 -0
- package/packages/mcp-servers/dist/secret-sync/server.js.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/types.d.ts +442 -0
- package/packages/mcp-servers/dist/secret-sync/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/types.js +113 -0
- package/packages/mcp-servers/dist/secret-sync/types.js.map +1 -0
- package/packages/mcp-servers/dist/session-events/index.d.ts +5 -0
- package/packages/mcp-servers/dist/session-events/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-events/index.js +5 -0
- package/packages/mcp-servers/dist/session-events/index.js.map +1 -0
- package/packages/mcp-servers/dist/session-events/server.d.ts +11 -0
- package/packages/mcp-servers/dist/session-events/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-events/server.js +290 -0
- package/packages/mcp-servers/dist/session-events/server.js.map +1 -0
- package/packages/mcp-servers/dist/session-events/types.d.ts +213 -0
- package/packages/mcp-servers/dist/session-events/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-events/types.js +69 -0
- package/packages/mcp-servers/dist/session-events/types.js.map +1 -0
- package/packages/mcp-servers/dist/session-restart/index.d.ts +9 -0
- package/packages/mcp-servers/dist/session-restart/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-restart/index.js +9 -0
- package/packages/mcp-servers/dist/session-restart/index.js.map +1 -0
- package/packages/mcp-servers/dist/session-restart/server.d.ts +20 -0
- package/packages/mcp-servers/dist/session-restart/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-restart/server.js +411 -0
- package/packages/mcp-servers/dist/session-restart/server.js.map +1 -0
- package/packages/mcp-servers/dist/session-restart/types.d.ts +26 -0
- package/packages/mcp-servers/dist/session-restart/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-restart/types.js +16 -0
- package/packages/mcp-servers/dist/session-restart/types.js.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/index.d.ts +5 -0
- package/packages/mcp-servers/dist/setup-helper/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/index.js +5 -0
- package/packages/mcp-servers/dist/setup-helper/index.js.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/server.d.ts +14 -0
- package/packages/mcp-servers/dist/setup-helper/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/server.js +454 -0
- package/packages/mcp-servers/dist/setup-helper/server.js.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/types.d.ts +81 -0
- package/packages/mcp-servers/dist/setup-helper/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/types.js +41 -0
- package/packages/mcp-servers/dist/setup-helper/types.js.map +1 -0
- package/packages/mcp-servers/dist/shared/audited-server.d.ts +31 -0
- package/packages/mcp-servers/dist/shared/audited-server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/audited-server.js +126 -0
- package/packages/mcp-servers/dist/shared/audited-server.js.map +1 -0
- package/packages/mcp-servers/dist/shared/constants.d.ts +26 -0
- package/packages/mcp-servers/dist/shared/constants.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/constants.js +41 -0
- package/packages/mcp-servers/dist/shared/constants.js.map +1 -0
- package/packages/mcp-servers/dist/shared/index.d.ts +6 -0
- package/packages/mcp-servers/dist/shared/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/index.js +6 -0
- package/packages/mcp-servers/dist/shared/index.js.map +1 -0
- package/packages/mcp-servers/dist/shared/readonly-db.d.ts +11 -0
- package/packages/mcp-servers/dist/shared/readonly-db.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/readonly-db.js +47 -0
- package/packages/mcp-servers/dist/shared/readonly-db.js.map +1 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.d.ts +20 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.js +65 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.js.map +1 -0
- package/packages/mcp-servers/dist/shared/server.d.ts +86 -0
- package/packages/mcp-servers/dist/shared/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/server.js +291 -0
- package/packages/mcp-servers/dist/shared/server.js.map +1 -0
- package/packages/mcp-servers/dist/shared/types.d.ts +113 -0
- package/packages/mcp-servers/dist/shared/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/types.js +36 -0
- package/packages/mcp-servers/dist/shared/types.js.map +1 -0
- package/packages/mcp-servers/dist/show/server.d.ts +12 -0
- package/packages/mcp-servers/dist/show/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/show/server.js +97 -0
- package/packages/mcp-servers/dist/show/server.js.map +1 -0
- package/packages/mcp-servers/dist/show/types.d.ts +19 -0
- package/packages/mcp-servers/dist/show/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/show/types.js +32 -0
- package/packages/mcp-servers/dist/show/types.js.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/index.d.ts +5 -0
- package/packages/mcp-servers/dist/specs-browser/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/index.js +5 -0
- package/packages/mcp-servers/dist/specs-browser/index.js.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/server.d.ts +13 -0
- package/packages/mcp-servers/dist/specs-browser/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/server.js +692 -0
- package/packages/mcp-servers/dist/specs-browser/server.js.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/types.d.ts +337 -0
- package/packages/mcp-servers/dist/specs-browser/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/types.js +134 -0
- package/packages/mcp-servers/dist/specs-browser/types.js.map +1 -0
- package/packages/mcp-servers/dist/supabase/index.d.ts +10 -0
- package/packages/mcp-servers/dist/supabase/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/supabase/index.js +10 -0
- package/packages/mcp-servers/dist/supabase/index.js.map +1 -0
- package/packages/mcp-servers/dist/supabase/server.d.ts +20 -0
- package/packages/mcp-servers/dist/supabase/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/supabase/server.js +451 -0
- package/packages/mcp-servers/dist/supabase/server.js.map +1 -0
- package/packages/mcp-servers/dist/supabase/types.d.ts +196 -0
- package/packages/mcp-servers/dist/supabase/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/supabase/types.js +76 -0
- package/packages/mcp-servers/dist/supabase/types.js.map +1 -0
- package/packages/mcp-servers/dist/todo-db/index.d.ts +5 -0
- package/packages/mcp-servers/dist/todo-db/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/todo-db/index.js +5 -0
- package/packages/mcp-servers/dist/todo-db/index.js.map +1 -0
- package/packages/mcp-servers/dist/todo-db/server.d.ts +13 -0
- package/packages/mcp-servers/dist/todo-db/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/todo-db/server.js +649 -0
- package/packages/mcp-servers/dist/todo-db/server.js.map +1 -0
- package/packages/mcp-servers/dist/todo-db/types.d.ts +225 -0
- package/packages/mcp-servers/dist/todo-db/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/todo-db/types.js +69 -0
- package/packages/mcp-servers/dist/todo-db/types.js.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/index.d.ts +7 -0
- package/packages/mcp-servers/dist/user-feedback/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/index.js +8 -0
- package/packages/mcp-servers/dist/user-feedback/index.js.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/server.d.ts +25 -0
- package/packages/mcp-servers/dist/user-feedback/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/server.js +914 -0
- package/packages/mcp-servers/dist/user-feedback/server.js.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/types.d.ts +415 -0
- package/packages/mcp-servers/dist/user-feedback/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/types.js +132 -0
- package/packages/mcp-servers/dist/user-feedback/types.js.map +1 -0
- package/packages/mcp-servers/dist/vercel/index.d.ts +9 -0
- package/packages/mcp-servers/dist/vercel/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/vercel/index.js +9 -0
- package/packages/mcp-servers/dist/vercel/index.js.map +1 -0
- package/packages/mcp-servers/dist/vercel/server.d.ts +17 -0
- package/packages/mcp-servers/dist/vercel/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/vercel/server.js +265 -0
- package/packages/mcp-servers/dist/vercel/server.js.map +1 -0
- package/packages/mcp-servers/dist/vercel/types.d.ts +189 -0
- package/packages/mcp-servers/dist/vercel/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/vercel/types.js +65 -0
- package/packages/mcp-servers/dist/vercel/types.js.map +1 -0
- package/packages/mcp-servers/package-lock.json +3765 -0
- package/packages/mcp-servers/package.json +64 -0
- package/packages/mcp-servers/test/reporters/test-failure-reporter.ts +372 -0
- package/packages/mcp-servers/vitest.config.ts +27 -0
- package/scripts/__tests__/README.md +163 -0
- package/scripts/apply-credential-hardening.sh +271 -0
- package/scripts/credential-providers/manual.js +56 -0
- package/scripts/credential-providers/onepassword.js +85 -0
- package/scripts/credential-providers/provider-interface.js +104 -0
- package/scripts/encrypt-credential.js +337 -0
- package/scripts/feedback-launcher.js +338 -0
- package/scripts/feedback-orchestrator.js +373 -0
- package/scripts/fix-mcp-launcher-issues.sh +97 -0
- package/scripts/force-spawn-tasks.js +651 -0
- package/scripts/force-triage-reports.js +560 -0
- package/scripts/generate-protected-actions-spec.js +142 -0
- package/scripts/generate-proxy-certs.sh +158 -0
- package/scripts/grant-chrome-ext-permissions.sh +242 -0
- package/scripts/mcp-launcher.js +125 -0
- package/scripts/merge-settings.cjs +167 -0
- package/scripts/patch-clawd.py +844 -0
- package/scripts/patch-credential-cache.py +313 -0
- package/scripts/patches/credential-file-guard-patched.mjs +573 -0
- package/scripts/patches/credential-file-guard.js.patched +573 -0
- package/scripts/patches/verify-tokenizer.mjs +132 -0
- package/scripts/protect-framework.sh +478 -0
- package/scripts/readme-chrome.template +12 -0
- package/scripts/reap-completed-agents.js +439 -0
- package/scripts/reinstall.sh +86 -0
- package/scripts/resign-node.sh +185 -0
- package/scripts/rotation-proxy.js +656 -0
- package/scripts/rotation-stress-monitor.mjs +862 -0
- package/scripts/setup-automation-service.sh +648 -0
- package/scripts/setup-check.js +251 -0
- package/scripts/watch-claude-version.js +142 -0
- package/specs/framework/CORE-INVARIANTS.md +161 -0
- package/specs/patterns/AGENT-PATTERNS.md +223 -0
- package/specs/patterns/HOOK-PATTERNS.md +242 -0
- package/specs/patterns/MCP-SERVER-PATTERNS.md +144 -0
- package/templates/config/gitignore.template +14 -0
- package/templates/config/merge-chain-check.yml.template +51 -0
- package/templates/config/package.json.template +18 -0
- package/templates/config/pnpm-workspace.yaml +5 -0
- package/templates/config/services.json.template +18 -0
- package/templates/config/tsconfig.base.json +17 -0
- package/templates/scaffold/integrations/_template/.gitkeep +0 -0
- package/templates/scaffold/packages/logger/package.json +17 -0
- package/templates/scaffold/packages/logger/src/logger.ts +44 -0
- package/templates/scaffold/packages/shared/package.json +17 -0
- package/templates/scaffold/packages/shared/src/errors.ts +43 -0
- package/templates/scaffold/products/_product/apps/backend/package.json +21 -0
- package/templates/scaffold/products/_product/apps/backend/src/index.ts +17 -0
- package/templates/scaffold/products/_product/apps/extension/.gitkeep +0 -0
- package/templates/scaffold/products/_product/apps/web/.gitkeep +0 -0
- package/templates/scaffold/specs/global/.gitkeep +0 -0
- package/templates/scaffold/specs/local/.gitkeep +0 -0
- package/templates/scaffold/specs/reference/.gitkeep +0 -0
- package/version.json +15 -0
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Approval Utilities for Protected MCP Actions
|
|
4
|
+
*
|
|
5
|
+
* Provides encryption, code generation, and approval validation
|
|
6
|
+
* for the CTO-protected MCP action system.
|
|
7
|
+
*
|
|
8
|
+
* Security Model:
|
|
9
|
+
* - Credentials encrypted with AES-256-GCM
|
|
10
|
+
* - Decryption key stored in .claude/protection-key (root-owned)
|
|
11
|
+
* - Approval codes are 6-char alphanumeric, one-time use
|
|
12
|
+
* - Approvals expire after 5 minutes
|
|
13
|
+
*
|
|
14
|
+
* @version 1.0.0
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import crypto from 'crypto';
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Configuration
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
26
|
+
const PROTECTION_KEY_PATH = path.join(PROJECT_DIR, '.claude', 'protection-key');
|
|
27
|
+
const PROTECTED_ACTIONS_PATH = path.join(PROJECT_DIR, '.claude', 'hooks', 'protected-actions.json');
|
|
28
|
+
const APPROVALS_PATH = path.join(PROJECT_DIR, '.claude', 'protected-action-approvals.json');
|
|
29
|
+
const DEPUTY_CTO_DB = path.join(PROJECT_DIR, '.claude', 'deputy-cto.db');
|
|
30
|
+
|
|
31
|
+
// Token expires after 5 minutes
|
|
32
|
+
const TOKEN_EXPIRY_MS = 5 * 60 * 1000;
|
|
33
|
+
|
|
34
|
+
// Lock file for TOCTOU-safe approval consumption
|
|
35
|
+
const LOCK_PATH = APPROVALS_PATH + '.lock';
|
|
36
|
+
|
|
37
|
+
// Encryption constants
|
|
38
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
39
|
+
const KEY_LENGTH = 32; // 256 bits
|
|
40
|
+
const IV_LENGTH = 16;
|
|
41
|
+
const AUTH_TAG_LENGTH = 16;
|
|
42
|
+
const ENCRYPTED_PREFIX = '${GENTYR_ENCRYPTED:';
|
|
43
|
+
const ENCRYPTED_SUFFIX = '}';
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Code Generation
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate a 6-character alphanumeric approval code
|
|
51
|
+
* Excludes confusing characters: 0/O, 1/I/L
|
|
52
|
+
*/
|
|
53
|
+
export function generateCode() {
|
|
54
|
+
const chars = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
|
|
55
|
+
let code = '';
|
|
56
|
+
const randomBytes = crypto.randomBytes(6);
|
|
57
|
+
for (let i = 0; i < 6; i++) {
|
|
58
|
+
code += chars.charAt(randomBytes[i] % chars.length);
|
|
59
|
+
}
|
|
60
|
+
return code;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// HMAC Signing (shared across hooks)
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Compute HMAC-SHA256 over pipe-delimited fields.
|
|
69
|
+
* Shared function used by createRequest, checkApproval, and external hooks
|
|
70
|
+
* (protected-action-gate, protected-action-approval-hook, deputy-cto server).
|
|
71
|
+
*
|
|
72
|
+
* @param {string} keyBase64 - Base64-encoded protection key
|
|
73
|
+
* @param {...string} fields - Fields to include in HMAC
|
|
74
|
+
* @returns {string} Hex-encoded HMAC
|
|
75
|
+
*/
|
|
76
|
+
export function computeHmac(keyBase64, ...fields) {
|
|
77
|
+
const keyBuffer = Buffer.from(keyBase64, 'base64');
|
|
78
|
+
return crypto.createHmac('sha256', keyBuffer)
|
|
79
|
+
.update(fields.join('|'))
|
|
80
|
+
.digest('hex');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// File Locking (TOCTOU protection for approval consumption)
|
|
85
|
+
// ============================================================================
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Acquire an advisory lock on the approvals file.
|
|
89
|
+
* Uses exclusive file creation (O_CREAT | O_EXCL) as a cross-process mutex.
|
|
90
|
+
* Retries with backoff for up to 2 seconds.
|
|
91
|
+
* @returns {boolean} true if lock acquired
|
|
92
|
+
*/
|
|
93
|
+
function acquireLock() {
|
|
94
|
+
const maxAttempts = 10;
|
|
95
|
+
const baseDelay = 50; // ms
|
|
96
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
97
|
+
try {
|
|
98
|
+
const fd = fs.openSync(LOCK_PATH, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
|
|
99
|
+
fs.writeSync(fd, String(process.pid));
|
|
100
|
+
fs.closeSync(fd);
|
|
101
|
+
return true;
|
|
102
|
+
} catch (err) {
|
|
103
|
+
// Check for stale lock (older than 10 seconds)
|
|
104
|
+
try {
|
|
105
|
+
const stat = fs.statSync(LOCK_PATH);
|
|
106
|
+
if (Date.now() - stat.mtimeMs > 10000) {
|
|
107
|
+
fs.unlinkSync(LOCK_PATH);
|
|
108
|
+
continue; // Retry immediately after removing stale lock
|
|
109
|
+
}
|
|
110
|
+
} catch { /* lock file gone, retry */ }
|
|
111
|
+
|
|
112
|
+
// Exponential backoff
|
|
113
|
+
const delay = baseDelay * Math.pow(2, i);
|
|
114
|
+
const start = Date.now();
|
|
115
|
+
while (Date.now() - start < delay) { /* busy wait */ }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Release the advisory lock.
|
|
123
|
+
*/
|
|
124
|
+
function releaseLock() {
|
|
125
|
+
try {
|
|
126
|
+
fs.unlinkSync(LOCK_PATH);
|
|
127
|
+
} catch { /* already released */ }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// Encryption / Decryption
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Generate a new protection key
|
|
136
|
+
* @returns {string} Base64-encoded key
|
|
137
|
+
*/
|
|
138
|
+
export function generateProtectionKey() {
|
|
139
|
+
return crypto.randomBytes(KEY_LENGTH).toString('base64');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Read the protection key from disk
|
|
144
|
+
* @returns {Buffer|null} The key buffer or null if not found
|
|
145
|
+
*/
|
|
146
|
+
export function readProtectionKey() {
|
|
147
|
+
try {
|
|
148
|
+
if (!fs.existsSync(PROTECTION_KEY_PATH)) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const keyBase64 = fs.readFileSync(PROTECTION_KEY_PATH, 'utf8').trim();
|
|
152
|
+
return Buffer.from(keyBase64, 'base64');
|
|
153
|
+
} catch (err) {
|
|
154
|
+
console.error(`[approval-utils] Failed to read protection key: ${err.message}`);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Write the protection key to disk
|
|
161
|
+
* Note: Caller should ensure root ownership after writing
|
|
162
|
+
* @param {string} keyBase64 - Base64-encoded key
|
|
163
|
+
*/
|
|
164
|
+
export function writeProtectionKey(keyBase64) {
|
|
165
|
+
const dir = path.dirname(PROTECTION_KEY_PATH);
|
|
166
|
+
if (!fs.existsSync(dir)) {
|
|
167
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
168
|
+
}
|
|
169
|
+
fs.writeFileSync(PROTECTION_KEY_PATH, keyBase64 + '\n', { mode: 0o600 });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Encrypt a credential value
|
|
174
|
+
* @param {string} value - Plain text value to encrypt
|
|
175
|
+
* @param {Buffer} key - Encryption key
|
|
176
|
+
* @returns {string} Encrypted string in ${GENTYR_ENCRYPTED:...} format
|
|
177
|
+
*/
|
|
178
|
+
export function encryptCredential(value, key) {
|
|
179
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
180
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
181
|
+
|
|
182
|
+
let encrypted = cipher.update(value, 'utf8', 'base64');
|
|
183
|
+
encrypted += cipher.final('base64');
|
|
184
|
+
|
|
185
|
+
const authTag = cipher.getAuthTag();
|
|
186
|
+
|
|
187
|
+
// Format: iv:authTag:ciphertext (all base64)
|
|
188
|
+
const payload = `${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted}`;
|
|
189
|
+
return `${ENCRYPTED_PREFIX}${payload}${ENCRYPTED_SUFFIX}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Decrypt a credential value
|
|
194
|
+
* @param {string} encryptedValue - Value in ${GENTYR_ENCRYPTED:...} format
|
|
195
|
+
* @param {Buffer} key - Decryption key
|
|
196
|
+
* @returns {string|null} Decrypted value or null on failure
|
|
197
|
+
*/
|
|
198
|
+
export function decryptCredential(encryptedValue, key) {
|
|
199
|
+
try {
|
|
200
|
+
if (!encryptedValue.startsWith(ENCRYPTED_PREFIX) || !encryptedValue.endsWith(ENCRYPTED_SUFFIX)) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const payload = encryptedValue.slice(ENCRYPTED_PREFIX.length, -ENCRYPTED_SUFFIX.length);
|
|
205
|
+
const [ivBase64, authTagBase64, ciphertext] = payload.split(':');
|
|
206
|
+
|
|
207
|
+
if (!ivBase64 || !authTagBase64 || !ciphertext) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const iv = Buffer.from(ivBase64, 'base64');
|
|
212
|
+
const authTag = Buffer.from(authTagBase64, 'base64');
|
|
213
|
+
|
|
214
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
215
|
+
decipher.setAuthTag(authTag);
|
|
216
|
+
|
|
217
|
+
let decrypted = decipher.update(ciphertext, 'base64', 'utf8');
|
|
218
|
+
decrypted += decipher.final('utf8');
|
|
219
|
+
|
|
220
|
+
return decrypted;
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.error(`[approval-utils] Decryption failed: ${err.message}`);
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Check if a value is encrypted
|
|
229
|
+
* @param {string} value - Value to check
|
|
230
|
+
* @returns {boolean}
|
|
231
|
+
*/
|
|
232
|
+
export function isEncrypted(value) {
|
|
233
|
+
return typeof value === 'string' &&
|
|
234
|
+
value.startsWith(ENCRYPTED_PREFIX) &&
|
|
235
|
+
value.endsWith(ENCRYPTED_SUFFIX);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// Protected Actions Configuration
|
|
240
|
+
// ============================================================================
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Load protected actions configuration
|
|
244
|
+
* @returns {object|null} Configuration or null if not found
|
|
245
|
+
*/
|
|
246
|
+
export function loadProtectedActions() {
|
|
247
|
+
try {
|
|
248
|
+
if (!fs.existsSync(PROTECTED_ACTIONS_PATH)) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
return JSON.parse(fs.readFileSync(PROTECTED_ACTIONS_PATH, 'utf8'));
|
|
252
|
+
} catch (err) {
|
|
253
|
+
console.error(`[approval-utils] Failed to load protected actions: ${err.message}`);
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Save protected actions configuration
|
|
260
|
+
* @param {object} config - Configuration to save
|
|
261
|
+
*/
|
|
262
|
+
export function saveProtectedActions(config) {
|
|
263
|
+
const dir = path.dirname(PROTECTED_ACTIONS_PATH);
|
|
264
|
+
if (!fs.existsSync(dir)) {
|
|
265
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
266
|
+
}
|
|
267
|
+
fs.writeFileSync(PROTECTED_ACTIONS_PATH, JSON.stringify(config, null, 2));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Check if a server:tool is protected
|
|
272
|
+
* @param {string} server - MCP server name
|
|
273
|
+
* @param {string} tool - Tool name
|
|
274
|
+
* @param {object} config - Protected actions config (optional, loads if not provided)
|
|
275
|
+
* @returns {object|null} Protection config or null if not protected
|
|
276
|
+
*/
|
|
277
|
+
export function getProtection(server, tool, config = null) {
|
|
278
|
+
const cfg = config || loadProtectedActions();
|
|
279
|
+
if (!cfg || !cfg.servers || !cfg.servers[server]) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const serverConfig = cfg.servers[server];
|
|
284
|
+
|
|
285
|
+
// Check if this tool is protected
|
|
286
|
+
if (serverConfig.tools === '*') {
|
|
287
|
+
return serverConfig;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (Array.isArray(serverConfig.tools) && serverConfig.tools.includes(tool)) {
|
|
291
|
+
return serverConfig;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ============================================================================
|
|
298
|
+
// Approval Management
|
|
299
|
+
// ============================================================================
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Load current approvals
|
|
303
|
+
* @returns {object} Approvals object (may be empty)
|
|
304
|
+
*/
|
|
305
|
+
export function loadApprovals() {
|
|
306
|
+
try {
|
|
307
|
+
if (!fs.existsSync(APPROVALS_PATH)) {
|
|
308
|
+
return { approvals: {} };
|
|
309
|
+
}
|
|
310
|
+
const data = JSON.parse(fs.readFileSync(APPROVALS_PATH, 'utf8'));
|
|
311
|
+
if (!data || typeof data !== 'object' || !data.approvals || typeof data.approvals !== 'object') {
|
|
312
|
+
return { approvals: {} };
|
|
313
|
+
}
|
|
314
|
+
return data;
|
|
315
|
+
} catch (err) {
|
|
316
|
+
return { approvals: {} };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Save approvals
|
|
322
|
+
* @param {object} approvals - Approvals object
|
|
323
|
+
*/
|
|
324
|
+
export function saveApprovals(approvals) {
|
|
325
|
+
const dir = path.dirname(APPROVALS_PATH);
|
|
326
|
+
if (!fs.existsSync(dir)) {
|
|
327
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
328
|
+
}
|
|
329
|
+
fs.writeFileSync(APPROVALS_PATH, JSON.stringify(approvals, null, 2));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Create a pending approval request
|
|
334
|
+
* @param {string} server - MCP server name (or '__file__' for file approvals)
|
|
335
|
+
* @param {string} tool - Tool name (or file config key for file approvals)
|
|
336
|
+
* @param {object} args - Tool arguments
|
|
337
|
+
* @param {string} phrase - Approval phrase (e.g., "APPROVE PROD")
|
|
338
|
+
* @param {object} [options] - Additional options
|
|
339
|
+
* @param {string} [options.approvalMode] - 'cto' (default) or 'deputy-cto'
|
|
340
|
+
* @returns {object} Request details including code
|
|
341
|
+
*/
|
|
342
|
+
export function createRequest(server, tool, args, phrase, options = {}) {
|
|
343
|
+
const code = generateCode();
|
|
344
|
+
const now = Date.now();
|
|
345
|
+
const expiresTimestamp = now + TOKEN_EXPIRY_MS;
|
|
346
|
+
|
|
347
|
+
// Hash the args to bind the approval to these specific arguments
|
|
348
|
+
const argsHash = crypto.createHash('sha256')
|
|
349
|
+
.update(JSON.stringify(args || {}))
|
|
350
|
+
.digest('hex');
|
|
351
|
+
|
|
352
|
+
// Compute HMAC for pending request (prevents agent forgery)
|
|
353
|
+
const key = readProtectionKey();
|
|
354
|
+
const keyBase64 = key ? key.toString('base64') : null;
|
|
355
|
+
let pendingHmac;
|
|
356
|
+
if (keyBase64) {
|
|
357
|
+
pendingHmac = computeHmac(keyBase64, code, server, tool, argsHash, String(expiresTimestamp));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const approvals = loadApprovals();
|
|
361
|
+
approvals.approvals[code] = {
|
|
362
|
+
server,
|
|
363
|
+
tool,
|
|
364
|
+
args,
|
|
365
|
+
argsHash,
|
|
366
|
+
phrase,
|
|
367
|
+
code,
|
|
368
|
+
status: 'pending',
|
|
369
|
+
approval_mode: options.approvalMode || 'cto',
|
|
370
|
+
created_at: new Date(now).toISOString(),
|
|
371
|
+
created_timestamp: now,
|
|
372
|
+
expires_at: new Date(expiresTimestamp).toISOString(),
|
|
373
|
+
expires_timestamp: expiresTimestamp,
|
|
374
|
+
...(pendingHmac && { pending_hmac: pendingHmac }),
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// Clean expired requests
|
|
378
|
+
const validApprovals = {};
|
|
379
|
+
for (const [k, val] of Object.entries(approvals.approvals)) {
|
|
380
|
+
if (val.expires_timestamp > now) {
|
|
381
|
+
validApprovals[k] = val;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
approvals.approvals = validApprovals;
|
|
385
|
+
|
|
386
|
+
saveApprovals(approvals);
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
code,
|
|
390
|
+
server,
|
|
391
|
+
tool,
|
|
392
|
+
phrase,
|
|
393
|
+
message: `CTO must type: ${phrase} ${code}`,
|
|
394
|
+
expires_in_minutes: Math.round(TOKEN_EXPIRY_MS / 60000),
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Validate an approval code and mark as approved
|
|
400
|
+
* @param {string} phrase - The approval phrase (e.g., "APPROVE PROD")
|
|
401
|
+
* @param {string} code - The 6-character code
|
|
402
|
+
* @returns {object} Validation result
|
|
403
|
+
*/
|
|
404
|
+
export function validateApproval(phrase, code) {
|
|
405
|
+
const approvals = loadApprovals();
|
|
406
|
+
const request = approvals.approvals[code.toUpperCase()];
|
|
407
|
+
|
|
408
|
+
if (!request) {
|
|
409
|
+
return { valid: false, reason: 'No pending request with this code' };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (request.status === 'approved') {
|
|
413
|
+
return { valid: false, reason: 'This code has already been used' };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (Date.now() > request.expires_timestamp) {
|
|
417
|
+
// Clean up expired request
|
|
418
|
+
delete approvals.approvals[code.toUpperCase()];
|
|
419
|
+
saveApprovals(approvals);
|
|
420
|
+
return { valid: false, reason: 'Approval code has expired' };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Verify phrase matches (case-insensitive)
|
|
424
|
+
if (request.phrase.toUpperCase() !== phrase.toUpperCase()) {
|
|
425
|
+
return {
|
|
426
|
+
valid: false,
|
|
427
|
+
reason: `Wrong approval phrase. Expected: ${request.phrase}`
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Mark as approved
|
|
432
|
+
request.status = 'approved';
|
|
433
|
+
request.approved_at = new Date().toISOString();
|
|
434
|
+
request.approved_timestamp = Date.now();
|
|
435
|
+
saveApprovals(approvals);
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
valid: true,
|
|
439
|
+
server: request.server,
|
|
440
|
+
tool: request.tool,
|
|
441
|
+
args: request.args,
|
|
442
|
+
request,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Check if there's a valid approval for a server:tool call.
|
|
448
|
+
* Verifies HMAC signatures to prevent agent forgery.
|
|
449
|
+
* Uses file locking to prevent TOCTOU race conditions on approval consumption.
|
|
450
|
+
*
|
|
451
|
+
* @param {string} server - MCP server name (or '__file__' for file approvals)
|
|
452
|
+
* @param {string} tool - Tool name (or file config key for file approvals)
|
|
453
|
+
* @param {object} [args] - Tool arguments (used to verify approval is scoped to these exact args)
|
|
454
|
+
* @returns {object|null} Approval if valid, null otherwise
|
|
455
|
+
*/
|
|
456
|
+
export function checkApproval(server, tool, args) {
|
|
457
|
+
// Acquire lock to prevent TOCTOU race: two concurrent checks consuming same approval
|
|
458
|
+
if (!acquireLock()) {
|
|
459
|
+
console.error('[approval-utils] G001 FAIL-CLOSED: Could not acquire approvals lock. Blocking action.');
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
const approvals = loadApprovals();
|
|
465
|
+
const now = Date.now();
|
|
466
|
+
const key = readProtectionKey();
|
|
467
|
+
const keyBase64 = key ? key.toString('base64') : null;
|
|
468
|
+
let dirty = false;
|
|
469
|
+
|
|
470
|
+
// Hash the current call's arguments to verify they match the approved args
|
|
471
|
+
const argsHash = crypto.createHash('sha256')
|
|
472
|
+
.update(JSON.stringify(args || {}))
|
|
473
|
+
.digest('hex');
|
|
474
|
+
|
|
475
|
+
for (const [code, request] of Object.entries(approvals.approvals)) {
|
|
476
|
+
if (request.status !== 'approved') continue;
|
|
477
|
+
if (request.expires_timestamp < now) continue;
|
|
478
|
+
if (request.server !== server) continue;
|
|
479
|
+
if (request.tool !== tool) continue;
|
|
480
|
+
|
|
481
|
+
// Verify args match what was approved (prevents bait-and-switch attack)
|
|
482
|
+
if (request.argsHash && request.argsHash !== argsHash) {
|
|
483
|
+
continue; // Args don't match the approved request
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// HMAC verification: Verify signatures to prevent agent forgery
|
|
487
|
+
if (keyBase64) {
|
|
488
|
+
// Verify pending_hmac (was this request created by the hook with these args?)
|
|
489
|
+
if (request.pending_hmac) {
|
|
490
|
+
const expectedPendingHmac = computeHmac(keyBase64, code, server, tool, request.argsHash || argsHash, String(request.expires_timestamp));
|
|
491
|
+
if (request.pending_hmac !== expectedPendingHmac) {
|
|
492
|
+
console.error(`[approval-utils] FORGERY DETECTED: Invalid pending_hmac for ${code}. Deleting.`);
|
|
493
|
+
delete approvals.approvals[code];
|
|
494
|
+
dirty = true;
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Verify approved_hmac (was this approval created by the approval hook?)
|
|
500
|
+
if (request.approved_hmac) {
|
|
501
|
+
const expectedApprovedHmac = computeHmac(keyBase64, code, server, tool, 'approved', request.argsHash || argsHash, String(request.expires_timestamp));
|
|
502
|
+
if (request.approved_hmac !== expectedApprovedHmac) {
|
|
503
|
+
console.error(`[approval-utils] FORGERY DETECTED: Invalid approved_hmac for ${code}. Deleting.`);
|
|
504
|
+
delete approvals.approvals[code];
|
|
505
|
+
dirty = true;
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
} else if (request.pending_hmac || request.approved_hmac) {
|
|
510
|
+
// G001 Fail-Closed: Request has HMAC fields but we can't verify them
|
|
511
|
+
// (protection key missing/unreadable). Reject rather than skip verification.
|
|
512
|
+
console.error(`[approval-utils] G001 FAIL-CLOSED: Cannot verify HMAC for ${code} (protection key missing). Skipping.`);
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Found a valid, HMAC-verified approval - consume it (one-time use)
|
|
517
|
+
delete approvals.approvals[code];
|
|
518
|
+
saveApprovals(approvals);
|
|
519
|
+
|
|
520
|
+
return request;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Save if we deleted forged entries
|
|
524
|
+
if (dirty) {
|
|
525
|
+
saveApprovals(approvals);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return null;
|
|
529
|
+
} finally {
|
|
530
|
+
releaseLock();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Get all pending requests (for display/debugging)
|
|
536
|
+
* @returns {object[]} List of pending requests
|
|
537
|
+
*/
|
|
538
|
+
export function getPendingRequests() {
|
|
539
|
+
const approvals = loadApprovals();
|
|
540
|
+
const now = Date.now();
|
|
541
|
+
|
|
542
|
+
return Object.values(approvals.approvals)
|
|
543
|
+
.filter(r => r.status === 'pending' && r.expires_timestamp > now)
|
|
544
|
+
.map(r => ({
|
|
545
|
+
code: r.code,
|
|
546
|
+
server: r.server,
|
|
547
|
+
tool: r.tool,
|
|
548
|
+
phrase: r.phrase,
|
|
549
|
+
created_at: r.created_at,
|
|
550
|
+
expires_at: r.expires_at,
|
|
551
|
+
}));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// ============================================================================
|
|
555
|
+
// Database Helpers (for integration with deputy-cto.db)
|
|
556
|
+
// ============================================================================
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Create a protected-action-request in deputy-cto.db
|
|
560
|
+
* This allows the request to show up in CTO notifications
|
|
561
|
+
* @param {string} server - MCP server name
|
|
562
|
+
* @param {string} tool - Tool name
|
|
563
|
+
* @param {object} args - Tool arguments
|
|
564
|
+
* @param {string} code - Approval code
|
|
565
|
+
* @param {string} phrase - Approval phrase
|
|
566
|
+
* @returns {string|null} Question ID or null on failure
|
|
567
|
+
*/
|
|
568
|
+
export async function createDbRequest(server, tool, args, code, phrase) {
|
|
569
|
+
try {
|
|
570
|
+
const Database = (await import('better-sqlite3')).default;
|
|
571
|
+
|
|
572
|
+
if (!fs.existsSync(DEPUTY_CTO_DB)) {
|
|
573
|
+
console.error('[approval-utils] deputy-cto.db not found');
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const db = new Database(DEPUTY_CTO_DB);
|
|
578
|
+
const id = crypto.randomUUID();
|
|
579
|
+
const now = new Date();
|
|
580
|
+
|
|
581
|
+
const description = `**Protected Action Request**
|
|
582
|
+
|
|
583
|
+
**Server:** ${server}
|
|
584
|
+
**Tool:** ${tool}
|
|
585
|
+
**Arguments:** \`\`\`json
|
|
586
|
+
${JSON.stringify(args, null, 2)}
|
|
587
|
+
\`\`\`
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
**CTO Action Required:**
|
|
592
|
+
To approve this action, type exactly: **${phrase} ${code}**
|
|
593
|
+
|
|
594
|
+
This approval will expire in 5 minutes.`;
|
|
595
|
+
|
|
596
|
+
const context = JSON.stringify({ code, server, tool, args, phrase });
|
|
597
|
+
|
|
598
|
+
db.prepare(`
|
|
599
|
+
INSERT INTO questions (id, type, status, title, description, context, created_at, created_timestamp)
|
|
600
|
+
VALUES (?, 'protected-action-request', 'pending', ?, ?, ?, ?, ?)
|
|
601
|
+
`).run(
|
|
602
|
+
id,
|
|
603
|
+
`Protected Action: ${server}:${tool}`,
|
|
604
|
+
description,
|
|
605
|
+
context,
|
|
606
|
+
now.toISOString(),
|
|
607
|
+
Math.floor(now.getTime() / 1000)
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
db.close();
|
|
611
|
+
return id;
|
|
612
|
+
} catch (err) {
|
|
613
|
+
console.error(`[approval-utils] Failed to create DB request: ${err.message}`);
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Validate an approval code against deputy-cto.db
|
|
620
|
+
* @param {string} code - The 6-character code
|
|
621
|
+
* @returns {object} Validation result with question details
|
|
622
|
+
*/
|
|
623
|
+
export async function validateDbApproval(code) {
|
|
624
|
+
try {
|
|
625
|
+
const Database = (await import('better-sqlite3')).default;
|
|
626
|
+
|
|
627
|
+
if (!fs.existsSync(DEPUTY_CTO_DB)) {
|
|
628
|
+
return { valid: false, reason: 'Database not found' };
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const db = new Database(DEPUTY_CTO_DB, { readonly: true });
|
|
632
|
+
|
|
633
|
+
// Look for pending protected-action-request with this code in context
|
|
634
|
+
const question = db.prepare(`
|
|
635
|
+
SELECT id, title, context, created_at FROM questions
|
|
636
|
+
WHERE type = 'protected-action-request'
|
|
637
|
+
AND status = 'pending'
|
|
638
|
+
AND context LIKE ?
|
|
639
|
+
`).get(`%"code":"${code}"%`);
|
|
640
|
+
|
|
641
|
+
db.close();
|
|
642
|
+
|
|
643
|
+
if (!question) {
|
|
644
|
+
return { valid: false, reason: 'No pending request with this code' };
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const context = JSON.parse(question.context);
|
|
648
|
+
|
|
649
|
+
return {
|
|
650
|
+
valid: true,
|
|
651
|
+
question_id: question.id,
|
|
652
|
+
server: context.server,
|
|
653
|
+
tool: context.tool,
|
|
654
|
+
args: context.args,
|
|
655
|
+
phrase: context.phrase,
|
|
656
|
+
created_at: question.created_at,
|
|
657
|
+
};
|
|
658
|
+
} catch (err) {
|
|
659
|
+
return { valid: false, reason: `Database error: ${err.message}` };
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Mark a protected-action-request as answered in deputy-cto.db
|
|
665
|
+
* @param {string} questionId - Question UUID
|
|
666
|
+
*/
|
|
667
|
+
export async function markDbRequestApproved(questionId) {
|
|
668
|
+
try {
|
|
669
|
+
const Database = (await import('better-sqlite3')).default;
|
|
670
|
+
|
|
671
|
+
if (!fs.existsSync(DEPUTY_CTO_DB)) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const db = new Database(DEPUTY_CTO_DB);
|
|
676
|
+
|
|
677
|
+
db.prepare(`
|
|
678
|
+
UPDATE questions
|
|
679
|
+
SET status = 'answered', answer = 'APPROVED', answered_at = ?
|
|
680
|
+
WHERE id = ?
|
|
681
|
+
`).run(new Date().toISOString(), questionId);
|
|
682
|
+
|
|
683
|
+
db.close();
|
|
684
|
+
} catch (err) {
|
|
685
|
+
console.error(`[approval-utils] Failed to mark request approved: ${err.message}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// ============================================================================
|
|
690
|
+
// Exports
|
|
691
|
+
// ============================================================================
|
|
692
|
+
|
|
693
|
+
export default {
|
|
694
|
+
// Code generation
|
|
695
|
+
generateCode,
|
|
696
|
+
|
|
697
|
+
// HMAC
|
|
698
|
+
computeHmac,
|
|
699
|
+
|
|
700
|
+
// Encryption
|
|
701
|
+
generateProtectionKey,
|
|
702
|
+
readProtectionKey,
|
|
703
|
+
writeProtectionKey,
|
|
704
|
+
encryptCredential,
|
|
705
|
+
decryptCredential,
|
|
706
|
+
isEncrypted,
|
|
707
|
+
|
|
708
|
+
// Configuration
|
|
709
|
+
loadProtectedActions,
|
|
710
|
+
saveProtectedActions,
|
|
711
|
+
getProtection,
|
|
712
|
+
|
|
713
|
+
// Approvals
|
|
714
|
+
loadApprovals,
|
|
715
|
+
saveApprovals,
|
|
716
|
+
createRequest,
|
|
717
|
+
validateApproval,
|
|
718
|
+
checkApproval,
|
|
719
|
+
getPendingRequests,
|
|
720
|
+
|
|
721
|
+
// Database integration
|
|
722
|
+
createDbRequest,
|
|
723
|
+
validateDbApproval,
|
|
724
|
+
markDbRequestApproved,
|
|
725
|
+
|
|
726
|
+
// Constants
|
|
727
|
+
PROTECTION_KEY_PATH,
|
|
728
|
+
PROTECTED_ACTIONS_PATH,
|
|
729
|
+
APPROVALS_PATH,
|
|
730
|
+
TOKEN_EXPIRY_MS,
|
|
731
|
+
};
|