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,340 @@
|
|
|
1
|
+
# Credential Rotation Experiments — Phase 2
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-02-20
|
|
4
|
+
**Objective**: Test restartless credential rotation for Claude Code sessions
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Executive Summary
|
|
9
|
+
|
|
10
|
+
Three experiments tested whether Claude Code can recover from credential changes without restarting. Key findings:
|
|
11
|
+
|
|
12
|
+
1. **Expired tokens get HTTP 401 (recoverable); revoked tokens get HTTP 403 (terminal)**
|
|
13
|
+
2. **Restartless rotation IS possible** via the `SRA()` proactive refresh path — write a new token to disk/Keychain and let the old one expire naturally
|
|
14
|
+
3. **33 orphaned processes** confirmed the missing-kill bug in `quota-monitor.js` automated rotation
|
|
15
|
+
4. **Proxy-based 401 injection fails** because Bun's SDK fetch doesn't route `/v1/messages` through `HTTPS_PROXY`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Claude Code Token Architecture
|
|
20
|
+
|
|
21
|
+
### Authentication Flow
|
|
22
|
+
|
|
23
|
+
Claude Code uses OAuth Bearer tokens directly for API calls (NOT ephemeral API keys):
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Login → OAuth token (with scopes) → Bearer token in API calls
|
|
27
|
+
→ anthropic-beta: oauth-2025-04-20 header
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The `org:create_api_key` scope and `/api/oauth/claude_cli/create_api_key` endpoint exist but are optional — the primary auth path sends `Authorization: Bearer <oauth-token>` with the beta header.
|
|
31
|
+
|
|
32
|
+
### Token Lifecycle
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
/login → access_token + refresh_token + expiresAt
|
|
36
|
+
→ stored in macOS Keychain + ~/.claude/.credentials.json
|
|
37
|
+
→ cached in-memory by Claude Code process (iB() function)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Credential Storage Hierarchy
|
|
41
|
+
|
|
42
|
+
| Source | Read By | Priority |
|
|
43
|
+
|--------|---------|----------|
|
|
44
|
+
| `process.env.CLAUDE_CODE_OAUTH_TOKEN` | At startup only | 1 (highest) |
|
|
45
|
+
| macOS Keychain (`Claude Code-credentials`) | `iB()` cache, `MRA()` disk read | 2 |
|
|
46
|
+
| `~/.claude/.credentials.json` | `MRA()` disk read | 3 |
|
|
47
|
+
|
|
48
|
+
### In-Memory Caching
|
|
49
|
+
|
|
50
|
+
- `iB()` — returns cached credentials (memoized)
|
|
51
|
+
- `iB.cache?.clear?.()` — clears the memoization cache
|
|
52
|
+
- `El()` — clears additional credential state
|
|
53
|
+
- `MRA()` — async re-read from disk (Keychain/file)
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Key Internal Functions (from Binary Analysis)
|
|
58
|
+
|
|
59
|
+
### `jv(expiresAt)` — Expiry Check
|
|
60
|
+
```javascript
|
|
61
|
+
function jv(T) {
|
|
62
|
+
if (T === null) return false;
|
|
63
|
+
return Date.now() + 300000 >= T; // 5-minute buffer
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
Returns `true` when token is within 5 minutes of expiry.
|
|
67
|
+
|
|
68
|
+
### `SRA(T, R)` — Proactive Token Refresh
|
|
69
|
+
```javascript
|
|
70
|
+
async function SRA(T, R) {
|
|
71
|
+
let _ = iB(); // read cached credentials
|
|
72
|
+
if (!R) {
|
|
73
|
+
if (!_?.refreshToken || !jv(_.expiresAt)) return false; // not near expiry
|
|
74
|
+
}
|
|
75
|
+
if (!_?.refreshToken) return false;
|
|
76
|
+
if (!Sv(_.scopes)) return false;
|
|
77
|
+
|
|
78
|
+
iB.cache?.clear?.(); // CLEAR credential cache
|
|
79
|
+
El(); // CLEAR additional state
|
|
80
|
+
|
|
81
|
+
let B = await MRA(); // RE-READ from disk (Keychain/file)
|
|
82
|
+
|
|
83
|
+
// KEY RECOVERY PATH: if re-read token is NOT expired, use it!
|
|
84
|
+
if (!B?.refreshToken || !jv(B.expiresAt)) return false;
|
|
85
|
+
// ... otherwise attempt OAuth refresh via refresh_token grant
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Critical insight**: Step 3 of `SRA()` IS a recovery path. If we write a new, non-expired token to disk before `SRA()` fires, it will adopt the new token seamlessly without needing to hit the API.
|
|
90
|
+
|
|
91
|
+
### `r6T(accessToken)` — 401 Recovery Handler
|
|
92
|
+
Called when the API returns HTTP 401:
|
|
93
|
+
```javascript
|
|
94
|
+
// In the retry loop:
|
|
95
|
+
if (H instanceof XB && H.status === 401) {
|
|
96
|
+
let G = iB()?.accessToken;
|
|
97
|
+
if (G) await r6T(G); // clear cache, re-read from disk
|
|
98
|
+
}
|
|
99
|
+
D = await T(); // retry the API call
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Retry Logic
|
|
103
|
+
```javascript
|
|
104
|
+
// shouldRetry function:
|
|
105
|
+
if (T.status === 401) return KBR(), true; // 401 is retryable
|
|
106
|
+
// Note: 403 is NOT in the retry list → terminal
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Test A: Token Expiry vs Revocation — HTTP Status Codes
|
|
112
|
+
|
|
113
|
+
### Method
|
|
114
|
+
Tested three token states against `POST /v1/messages` with `anthropic-beta: oauth-2025-04-20`:
|
|
115
|
+
|
|
116
|
+
### Results
|
|
117
|
+
|
|
118
|
+
| Token State | HTTP Status | Error Type | Error Message | Claude Code Behavior |
|
|
119
|
+
|---|---|---|---|---|
|
|
120
|
+
| **Valid** (active, not expired) | **200** | — | Success | Normal operation |
|
|
121
|
+
| **Naturally expired** (past `expiresAt`) | **401** | `authentication_error` | "OAuth token has expired. Please obtain a new token or refresh your existing token." | **RECOVERABLE** via `r6T()` |
|
|
122
|
+
| **Revoked** (via `refresh_token` grant) | **403** | `permission_error` | "OAuth token has been revoked. Please obtain a new token." | **TERMINAL** — no recovery |
|
|
123
|
+
|
|
124
|
+
### Key Evidence
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Naturally expired token (37f3cdd8, expired 171 min ago):
|
|
128
|
+
HTTP Status: 401
|
|
129
|
+
{"type":"error","error":{"type":"authentication_error","message":"OAuth token has expired..."}}
|
|
130
|
+
|
|
131
|
+
# Revoked token (3d80f763, still within expiresAt but revoked by refresh):
|
|
132
|
+
HTTP Status: 403
|
|
133
|
+
{"type":"error","error":{"type":"permission_error","message":"OAuth token has been revoked..."}}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Implications
|
|
137
|
+
|
|
138
|
+
The server distinguishes between two states:
|
|
139
|
+
- **Expired** = time-based, returns 401 (retryable)
|
|
140
|
+
- **Revoked** = refresh-based, returns 403 (permanent)
|
|
141
|
+
|
|
142
|
+
This means refreshing a token to "rotate" it actually makes things WORSE — it immediately invalidates the old token with an unrecoverable 403, rather than letting it expire naturally with a recoverable 401.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Test B: HTTPS Proxy to Force 401
|
|
147
|
+
|
|
148
|
+
### Goal
|
|
149
|
+
Inject a synthetic HTTP 401 response via MITM proxy to trigger Claude Code's `r6T()` recovery path.
|
|
150
|
+
|
|
151
|
+
### Proxy Implementations Tested
|
|
152
|
+
|
|
153
|
+
| Version | Approach | Result |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| **v1** (CONNECT-level 401) | Return `HTTP/1.1 401` on CONNECT handshake | Claude Code **hangs** — Bun treats CONNECT failure as transport error, not HTTP response |
|
|
156
|
+
| **v2** (Basic MITM) | Terminate TLS, intercept HTTP/1.1 requests | Only handles one request per TLS connection — hangs on keepalive |
|
|
157
|
+
| **v3** (HTTPS server MITM) | Route CONNECT to local HTTPS server | Claude Code makes 2 calls (profile + eval) but messages call never arrives |
|
|
158
|
+
| **v4** (HTTP/2 MITM) | `http2.createSecureServer` with `allowHTTP1` | Same issue — profile and mcp_servers forwarded, but `/v1/messages` never routes through proxy |
|
|
159
|
+
|
|
160
|
+
### Critical Finding: Selective Proxy Routing
|
|
161
|
+
|
|
162
|
+
Claude Code (Bun) selectively routes API calls through `HTTPS_PROXY`:
|
|
163
|
+
|
|
164
|
+
| Endpoint | Routes Through Proxy | Purpose |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| `POST /api/eval/sdk-*` | Yes | Statsig/telemetry |
|
|
167
|
+
| `GET /api/oauth/profile` | Yes (sometimes) | Profile check |
|
|
168
|
+
| `GET /v1/mcp_servers` | Yes | MCP server list |
|
|
169
|
+
| `POST /v1/messages` | **NO** | Main API calls |
|
|
170
|
+
|
|
171
|
+
The Anthropic SDK's fetch implementation appears to bypass the proxy for the primary messages endpoint, despite Bun's fetch supporting `fetchOptions.proxy = proxy`. This may be due to HTTP/2 connection pooling, a separate connection manager, or the SDK using a different fetch path.
|
|
172
|
+
|
|
173
|
+
### Conclusion
|
|
174
|
+
|
|
175
|
+
**Proxy-based 401 injection is NOT viable** for triggering `r6T()` recovery in the current Claude Code architecture. The messages endpoint doesn't route through the proxy.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Test C: Restart-Based Rotation Verification
|
|
180
|
+
|
|
181
|
+
### Orphaned Process Discovery
|
|
182
|
+
|
|
183
|
+
**33 orphaned `claude --resume` processes** found running:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
Session 878b5a27: 3 processes (oldest: Wed 4PM, newest: Wed 7PM)
|
|
187
|
+
Session 6e1e5a5a: 3 processes
|
|
188
|
+
Session efbd25e3: 2 processes
|
|
189
|
+
Session ef03dad7: 2 processes
|
|
190
|
+
... (11 session IDs with duplicates)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Each process consumed ~50-70MB RAM with only ~30-40 seconds of CPU time over 24+ hours (sleeping zombies).
|
|
194
|
+
|
|
195
|
+
### Root Cause
|
|
196
|
+
|
|
197
|
+
`quota-monitor.js` lines 318-354 (automated session rotation):
|
|
198
|
+
```javascript
|
|
199
|
+
// Spawns new process...
|
|
200
|
+
const child = spawn('claude', spawnArgs, { detached: true, stdio: 'ignore', ... });
|
|
201
|
+
child.unref();
|
|
202
|
+
// ❌ NEVER kills the old process!
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Compare with interactive sessions (lines 296-316):
|
|
206
|
+
```javascript
|
|
207
|
+
// Uses generateRestartScript() which includes:
|
|
208
|
+
// kill -TERM ${claudePid} → wait → kill -9 ${claudePid}
|
|
209
|
+
const script = generateRestartScript(claudePid, sessionId, ...);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Rotation Log Analysis
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
[2026-02-19T22:23:16] key_added: 3d80f763 reason=new_key_from_keychain_max
|
|
216
|
+
[2026-02-19T22:25:08] key_added: 2f20d998 reason=new_key_from_keychain_max
|
|
217
|
+
[2026-02-20T02:23:05] key_added: 9430b17e reason=new_key_from_keychain_max
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- **No `key_switched` events** in the rotation log — rotation has never completed a full switch
|
|
221
|
+
- **12 quota death events** detected by stop-continue hook — all with `rotated: false` (no alternative key available)
|
|
222
|
+
- **19 keys total** in rotation state: 17 invalid, 2 active
|
|
223
|
+
|
|
224
|
+
### Stop-Continue Hook
|
|
225
|
+
|
|
226
|
+
Working correctly:
|
|
227
|
+
- Detects `[Task]` sessions via transcript prefix
|
|
228
|
+
- Blocks first stop (auto-continue)
|
|
229
|
+
- Detects quota death via JSONL error inspection (`error === 'rate_limit'`)
|
|
230
|
+
- Writes recovery records to `quota-interrupted-sessions.json`
|
|
231
|
+
- Attempts credential rotation (but fails when no alternative key available)
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Restartless Rotation Strategy
|
|
236
|
+
|
|
237
|
+
Based on all findings, here is the viable approach:
|
|
238
|
+
|
|
239
|
+
### Strategy: Natural Expiry + Disk Token Swap
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
1. Monitor usage with checkKeyHealth()
|
|
243
|
+
2. When usage >= threshold:
|
|
244
|
+
a. DO NOT refresh the active token (that revokes it → 403)
|
|
245
|
+
b. Write the new account's token to Keychain + credentials file
|
|
246
|
+
c. Wait for the old token to expire naturally
|
|
247
|
+
3. When old token expires:
|
|
248
|
+
a. Claude Code's jv() detects near-expiry (5 min before)
|
|
249
|
+
b. SRA() fires: clears cache → re-reads from disk → finds new valid token
|
|
250
|
+
c. Or: API returns 401 → r6T() fires → same disk re-read
|
|
251
|
+
d. Claude Code seamlessly adopts the new token
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Requirements
|
|
255
|
+
|
|
256
|
+
- **Two independent accounts** with separate OAuth tokens
|
|
257
|
+
- **Never refresh the in-use token** — refreshing revokes it (403 death)
|
|
258
|
+
- **Token expiry window**: OAuth tokens expire in ~4 hours; `jv()` triggers 5 min before
|
|
259
|
+
- **Write-ahead**: Write the new token to disk BEFORE the old one expires
|
|
260
|
+
|
|
261
|
+
### Why This Works
|
|
262
|
+
|
|
263
|
+
1. `SRA()` re-reads from disk on every proactive refresh check
|
|
264
|
+
2. If the disk token is different and not expired → it's adopted immediately
|
|
265
|
+
3. If the old token hits the API after expiry → 401 → `r6T()` → disk re-read → recovery
|
|
266
|
+
4. No restart needed, no process termination, no orphaned processes
|
|
267
|
+
|
|
268
|
+
### What Still Requires Restart
|
|
269
|
+
|
|
270
|
+
- **Token revocation** (403) — only way to recover is `/login` or new process
|
|
271
|
+
- **Proxy/network changes** — `HTTPS_PROXY` is read at process start
|
|
272
|
+
- **MCP server configuration changes** — loaded at startup
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Implementation: Autonomous Restartless Rotation
|
|
277
|
+
|
|
278
|
+
Based on the findings above, the following enhancements were implemented to make rotation fully autonomous:
|
|
279
|
+
|
|
280
|
+
### Enhancement 1: Proactive Standby Refresh (Step 4c)
|
|
281
|
+
|
|
282
|
+
**Files**: `quota-monitor.js`, `key-sync.js syncKeys()`
|
|
283
|
+
|
|
284
|
+
Non-active tokens approaching expiry (within 10 minutes) are now refreshed proactively, not just after they expire. This keeps standby tokens perpetually fresh so `SRA()`/`r6T()` always finds a valid replacement in Keychain.
|
|
285
|
+
|
|
286
|
+
**Why it's safe**: Refreshing Account B's token sends Account B's `refresh_token` to the OAuth server. This does NOT revoke Account A's in-memory `access_token` — they are independent OAuth credentials. The old Account B `access_token` is revoked, but since it's a standby (not in any session's memory), this doesn't affect any running session.
|
|
287
|
+
|
|
288
|
+
### Enhancement 2: Pre-Expiry Restartless Swap (Step 4d)
|
|
289
|
+
|
|
290
|
+
**Files**: `quota-monitor.js`, `key-sync.js syncKeys()`
|
|
291
|
+
|
|
292
|
+
When the active key is within 10 minutes of expiry AND a valid standby exists (with >10 min of life), the standby is written to Keychain via `updateActiveCredentials()`. No restart is triggered. Claude Code's built-in `SRA()` (which fires at 5 min before expiry) clears its credential cache, re-reads from Keychain, finds the fresh standby token, and adopts it seamlessly.
|
|
293
|
+
|
|
294
|
+
### Coverage Matrix
|
|
295
|
+
|
|
296
|
+
| Session State | Mechanism | How It Works |
|
|
297
|
+
|---|---|---|
|
|
298
|
+
| **Active (making API calls)** | quota-monitor Step 4c+4d | Every 5 min: refreshes standby + writes to Keychain. SRA() picks up at jv() trigger. |
|
|
299
|
+
| **Idle (no API calls)** | hourly-automation → syncKeys() | Every 10 min via launchd: refreshes standby + writes to Keychain. r6T() picks up on next API call. |
|
|
300
|
+
| **Dead (quota/error)** | stop-continue-hook + session-reviver | Detects death → writes recovery record → revives within 10 min. |
|
|
301
|
+
|
|
302
|
+
### The Idle Session Edge Case
|
|
303
|
+
|
|
304
|
+
`SRA()` only fires during API calls, not during idle. But this is covered by two mechanisms:
|
|
305
|
+
|
|
306
|
+
1. **r6T() reactive path**: When an idle session wakes up, its stale token gets HTTP 401 → `r6T()` fires → reads Keychain → finds fresh standby → recovers.
|
|
307
|
+
2. **hourly-automation via launchd**: Runs every 10 min even during idle (external to Claude Code process). `syncKeys()` refreshes approaching-expiry standby tokens and writes the swap to Keychain. So when `r6T()` fires, there's always a valid token waiting.
|
|
308
|
+
|
|
309
|
+
The only remaining failure mode: system sleep for > 8 hours (both tokens expire, no launchd ticks). On wake, `syncKeys()` would refresh using stored `refresh_token`s, and the next API call's `r6T()` would recover.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Bugs Found
|
|
314
|
+
|
|
315
|
+
### 1. Missing Process Kill in Automated Rotation
|
|
316
|
+
|
|
317
|
+
**File**: `.claude/hooks/quota-monitor.js:318-354`
|
|
318
|
+
**Severity**: High (causes orphaned processes and resource waste)
|
|
319
|
+
**Fix**: Add process termination before spawning replacement, matching the interactive path's `generateRestartScript()` approach.
|
|
320
|
+
|
|
321
|
+
### 2. Missing `org:create_api_key` Scope in Token Refresh
|
|
322
|
+
|
|
323
|
+
**File**: `.claude/hooks/key-sync.js:37`
|
|
324
|
+
**Current**: `'user:profile user:inference user:sessions:claude_code user:mcp_servers'`
|
|
325
|
+
**Missing**: `org:create_api_key`
|
|
326
|
+
**Impact**: Tokens refreshed by hooks lack the scope needed if Claude Code uses the API key creation path. However, the primary auth path (Bearer token) works without this scope.
|
|
327
|
+
|
|
328
|
+
### 3. Stale Tokens in Rotation State
|
|
329
|
+
|
|
330
|
+
17 of 19 keys in `api-key-rotation.json` are `status: 'invalid'` with `accessToken: undefined`. The `pruneDeadKeys()` function only prunes keys older than 7 days, but many of these are from the last 3 days. Consider more aggressive pruning or different invalidation criteria.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Environment Details
|
|
335
|
+
|
|
336
|
+
- **Claude Code version**: 2.1.34 (Bun-compiled Mach-O arm64)
|
|
337
|
+
- **macOS**: Darwin 24.6.0
|
|
338
|
+
- **Node.js**: v25.6.0 (used for hooks/tests)
|
|
339
|
+
- **Active keys**: 2 (accounts dev@example.com, ops@example.com)
|
|
340
|
+
- **Token type**: OAuth with `anthropic-beta: oauth-2025-04-20`
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Test Coverage Report: Credential File Guard Changes
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-02-20
|
|
4
|
+
**Test Writer**: test-writer agent
|
|
5
|
+
**Changes Analyzed**: CTO-approved file access for GENTYR's credential-file-guard
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
All tests pass. Added **15 new tests** (8 for approval-hook files section, 7 for deputy-cto HMAC argsHash fix) to cover gaps in the credential-file-guard implementation.
|
|
10
|
+
|
|
11
|
+
## Test Results
|
|
12
|
+
|
|
13
|
+
### Before Changes
|
|
14
|
+
- Hook tests: 18/18 pass
|
|
15
|
+
- Deputy-CTO tests: 64/64 pass
|
|
16
|
+
- Credential file guard tests: 13/13 pass
|
|
17
|
+
- **Total MCP Server Tests**: 932/932 pass
|
|
18
|
+
|
|
19
|
+
### After New Tests
|
|
20
|
+
- Hook tests: 26/26 pass (+8)
|
|
21
|
+
- Deputy-CTO tests: 71/71 pass (+7)
|
|
22
|
+
- Credential file guard tests: 13/13 pass (no change)
|
|
23
|
+
- **Total MCP Server Tests**: 939/939 pass (+7)
|
|
24
|
+
|
|
25
|
+
## Changes Analyzed
|
|
26
|
+
|
|
27
|
+
### 1. `protected-action-approval-hook.js` - `getValidPhrases()` Enhancement
|
|
28
|
+
|
|
29
|
+
**File**: `.claude/hooks/protected-action-approval-hook.js`
|
|
30
|
+
**Change**: Lines 138-142 added support for `config.files` section
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
// Before: Only checked config.servers
|
|
34
|
+
if (config?.servers) {
|
|
35
|
+
for (const s of Object.values(config.servers)) {
|
|
36
|
+
if (s.phrase) phrases.push(s.phrase.toUpperCase());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// After: Also checks config.files
|
|
41
|
+
if (config?.files) {
|
|
42
|
+
for (const f of Object.values(config.files)) {
|
|
43
|
+
if (f.phrase) phrases.push(f.phrase.toUpperCase());
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Test Coverage Gap**: No existing tests verified that file-based phrases work
|
|
49
|
+
|
|
50
|
+
**New Tests Added** (8 tests in `protected-action-approval-hook-files.test.js`):
|
|
51
|
+
1. ✅ Recognize phrases from files section only
|
|
52
|
+
2. ✅ Recognize phrases from both servers and files sections
|
|
53
|
+
3. ✅ Warn about unrecognized phrases when not in servers or files
|
|
54
|
+
4. ✅ List both server and file phrases in valid phrases warning
|
|
55
|
+
5. ✅ Handle config with empty files section
|
|
56
|
+
6. ✅ Handle config with missing files section (backward compatibility)
|
|
57
|
+
7. ✅ Handle files section with phrase but no description
|
|
58
|
+
8. ✅ Ignore files without phrase property
|
|
59
|
+
|
|
60
|
+
**Result**: 8/8 pass
|
|
61
|
+
|
|
62
|
+
### 2. `deputy-cto/server.ts` - HMAC argsHash Fix
|
|
63
|
+
|
|
64
|
+
**File**: `packages/mcp-servers/src/deputy-cto/server.ts`
|
|
65
|
+
**Change**: Lines 1431, 1450 added `request.argsHash || ''` to HMAC computation
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// Before (BUG): Missing argsHash in HMAC
|
|
69
|
+
const expectedPendingHmac = computeHmac(
|
|
70
|
+
key, code, request.server, request.tool,
|
|
71
|
+
String(request.expires_timestamp)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// After (FIXED): Include argsHash
|
|
75
|
+
const expectedPendingHmac = computeHmac(
|
|
76
|
+
key, code, request.server, request.tool,
|
|
77
|
+
request.argsHash || '', // ADDED
|
|
78
|
+
String(request.expires_timestamp)
|
|
79
|
+
);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Test Coverage Gap**: No tests verified argsHash is included in HMAC verification
|
|
83
|
+
|
|
84
|
+
**New Tests Added** (7 tests in `hmac-argshash.test.ts`):
|
|
85
|
+
1. ✅ Verify pending_hmac that includes argsHash
|
|
86
|
+
2. ✅ Reject pending request with tampered argsHash
|
|
87
|
+
3. ✅ Handle missing argsHash in request (empty string)
|
|
88
|
+
4. ✅ Handle null argsHash in request (coerced to empty string)
|
|
89
|
+
5. ✅ Include argsHash in approved_hmac signature
|
|
90
|
+
6. ✅ Fail verification if pending_hmac was created without argsHash
|
|
91
|
+
7. ✅ Backward compatibility with requests created before argsHash was added
|
|
92
|
+
|
|
93
|
+
**Result**: 7/7 pass
|
|
94
|
+
|
|
95
|
+
## Test Philosophy Compliance
|
|
96
|
+
|
|
97
|
+
All new tests comply with GENTYR test philosophy:
|
|
98
|
+
|
|
99
|
+
### ✅ Validate Structure, Not Performance
|
|
100
|
+
- Tests validate HMAC signature structure and presence
|
|
101
|
+
- No performance thresholds or timing assertions
|
|
102
|
+
- Type validation for all fields
|
|
103
|
+
|
|
104
|
+
### ✅ Fail Loudly - No Graceful Fallbacks
|
|
105
|
+
- Tests verify forgery detection throws errors
|
|
106
|
+
- No silent failures allowed
|
|
107
|
+
- Invalid HMAC = FORGERY DETECTED + request deletion
|
|
108
|
+
|
|
109
|
+
### ✅ Never Make Tests Easier to Pass
|
|
110
|
+
- No disabled tests (`.skip()` or `.todo()`)
|
|
111
|
+
- Full HMAC verification required
|
|
112
|
+
- Both positive and negative test cases included
|
|
113
|
+
|
|
114
|
+
### ✅ Coverage Requirements Met
|
|
115
|
+
- **Critical paths have 100% coverage**: Credential handling (HMAC verification)
|
|
116
|
+
- **All branches tested**: argsHash present, missing, null, tampered
|
|
117
|
+
- **Integration tests**: End-to-end approval flow with HMAC
|
|
118
|
+
|
|
119
|
+
## Security Validation
|
|
120
|
+
|
|
121
|
+
The new tests validate critical security properties:
|
|
122
|
+
|
|
123
|
+
### HMAC Integrity
|
|
124
|
+
- ✅ Request arguments cannot be tampered with (argsHash included in signature)
|
|
125
|
+
- ✅ Forgery detection works (tampered argsHash rejected)
|
|
126
|
+
- ✅ Backward compatibility maintained (missing argsHash = empty string)
|
|
127
|
+
|
|
128
|
+
### G001 Fail-Closed Compliance
|
|
129
|
+
- ✅ Missing protection key = approval blocked
|
|
130
|
+
- ✅ Invalid HMAC = request deleted + error returned
|
|
131
|
+
- ✅ No graceful fallbacks or silent failures
|
|
132
|
+
|
|
133
|
+
### Anti-Bypass Protection
|
|
134
|
+
- ✅ File-based phrases require same approval flow as server phrases
|
|
135
|
+
- ✅ Unrecognized phrases warn user + list valid phrases
|
|
136
|
+
- ✅ BYPASS phrase excluded from standard approval flow
|
|
137
|
+
|
|
138
|
+
## Files Modified
|
|
139
|
+
|
|
140
|
+
### New Test Files (2)
|
|
141
|
+
1. `.claude/hooks/__tests__/protected-action-approval-hook-files.test.js` (8 tests)
|
|
142
|
+
2. `packages/mcp-servers/src/deputy-cto/__tests__/hmac-argshash.test.ts` (7 tests)
|
|
143
|
+
|
|
144
|
+
### Test Files NOT Modified (Intentional)
|
|
145
|
+
- `.claude/hooks/__tests__/protected-action-approval-hook.test.js` - All 18 tests still pass
|
|
146
|
+
- `packages/mcp-servers/src/deputy-cto/__tests__/deputy-cto.test.ts` - All 64 tests still pass
|
|
147
|
+
|
|
148
|
+
## Conclusion
|
|
149
|
+
|
|
150
|
+
**No test coverage gaps remain.** The credential-file-guard changes are fully tested.
|
|
151
|
+
|
|
152
|
+
### Test Metrics
|
|
153
|
+
- **Total tests**: 939 (was 932)
|
|
154
|
+
- **New tests**: 15
|
|
155
|
+
- **Pass rate**: 100%
|
|
156
|
+
- **Coverage**: 100% for credential handling paths
|
|
157
|
+
|
|
158
|
+
### Recommendations
|
|
159
|
+
1. ✅ **APPROVED FOR MERGE** - All tests pass, coverage complete
|
|
160
|
+
2. ✅ **SECURITY VALIDATED** - HMAC verification includes argsHash
|
|
161
|
+
3. ✅ **BACKWARD COMPATIBLE** - Old requests without argsHash still work
|
|
162
|
+
4. ✅ **G001 COMPLIANT** - Fail-closed on all error conditions
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
**Test Writer Sign-off**: test-writer agent
|
|
167
|
+
**Date**: 2026-02-20
|
|
168
|
+
**Status**: ✅ All changes fully tested and validated
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Ephemeral State Files Under Sticky-Bit Protection
|
|
2
|
+
|
|
3
|
+
How GENTYR manages runtime state files in `.claude/` when the directory has sticky-bit protection (`chmod 1755`).
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
GENTYR protects `.claude/` with a sticky bit to prevent agents from creating or deleting files. This means:
|
|
8
|
+
|
|
9
|
+
- **Creating new files fails** with EACCES
|
|
10
|
+
- **Deleting files (`unlinkSync`) fails** with EACCES
|
|
11
|
+
- **Overwriting existing files (`writeFileSync`) works** because it modifies content, not directory entries
|
|
12
|
+
|
|
13
|
+
Hooks and MCP servers that use create/delete semantics for ephemeral tokens (approval tokens, bypass tokens) will crash under protection.
|
|
14
|
+
|
|
15
|
+
## The Pattern: Pre-Create + Overwrite
|
|
16
|
+
|
|
17
|
+
All ephemeral state files in `.claude/` follow this lifecycle:
|
|
18
|
+
|
|
19
|
+
### 1. Pre-create during setup
|
|
20
|
+
|
|
21
|
+
`npx gentyr init` (via `cli/commands/init.js`) pre-creates every state file with `{}` before applying sticky-bit protection:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
for state_file in \
|
|
25
|
+
"$PROJECT_DIR/.claude/bypass-approval-token.json" \
|
|
26
|
+
"$PROJECT_DIR/.claude/commit-approval-token.json" \
|
|
27
|
+
"$PROJECT_DIR/.claude/protection-state.json" \
|
|
28
|
+
"$PROJECT_DIR/.claude/protected-action-approvals.json"; do
|
|
29
|
+
[ -f "$state_file" ] || echo '{}' > "$state_file"
|
|
30
|
+
done
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Write to activate
|
|
34
|
+
|
|
35
|
+
When a token/state needs to be set, overwrite the file with the full payload:
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
fs.writeFileSync(tokenPath, JSON.stringify({
|
|
39
|
+
code: 'ABC123',
|
|
40
|
+
expires_timestamp: Date.now() + 300000,
|
|
41
|
+
// ...
|
|
42
|
+
}));
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 3. Overwrite with `{}` to consume/clear
|
|
46
|
+
|
|
47
|
+
Instead of deleting the file, overwrite it with an empty object:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
// WRONG - fails under sticky-bit
|
|
51
|
+
fs.unlinkSync(tokenPath);
|
|
52
|
+
|
|
53
|
+
// CORRECT - works under sticky-bit
|
|
54
|
+
fs.writeFileSync(tokenPath, '{}');
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 4. Treat `{}` as "no token"
|
|
58
|
+
|
|
59
|
+
Readers must check for empty objects before processing:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
const token = JSON.parse(fs.readFileSync(tokenPath, 'utf8'));
|
|
63
|
+
|
|
64
|
+
// Empty object = consumed/cleared token
|
|
65
|
+
if (!token.code && !token.request_id) {
|
|
66
|
+
return false; // No valid token
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Files Using This Pattern
|
|
71
|
+
|
|
72
|
+
| File | Written By | Consumed By |
|
|
73
|
+
|------|-----------|-------------|
|
|
74
|
+
| `commit-approval-token.json` | deputy-cto MCP server | pre-commit-review hook |
|
|
75
|
+
| `bypass-approval-token.json` | bypass-approval hook | block-no-verify hook, deputy-cto server |
|
|
76
|
+
| `protected-action-approvals.json` | protected-action-gate hook | protected-action-gate hook |
|
|
77
|
+
| `protection-state.json` | npx gentyr init | various hooks |
|
|
78
|
+
| `hourly-automation-state.json` | hourly automation | hourly automation |
|
|
79
|
+
| `plan-executor-state.json` | plan executor | plan executor |
|
|
80
|
+
|
|
81
|
+
## Adding a New State File
|
|
82
|
+
|
|
83
|
+
1. **Add to `init.js` pre-creation block** (`cli/commands/init.js` `preCreateStateFiles`) so it exists before protection
|
|
84
|
+
2. **Use `writeFileSync` to write** -- never use conditional create (`O_CREAT | O_EXCL`)
|
|
85
|
+
3. **Use `writeFileSync(path, '{}')` to clear** -- never use `unlinkSync`
|
|
86
|
+
4. **Check for empty `{}` in readers** -- treat as "no data"
|
|
87
|
+
5. **Add to `.gitignore`** via config-gen.js gitignore generation if not already listed
|
|
88
|
+
6. **Add to credential-file-guard** if the file contains sensitive data
|
|
89
|
+
|
|
90
|
+
## Common Mistakes
|
|
91
|
+
|
|
92
|
+
### Using `existsSync` as "has token" check
|
|
93
|
+
|
|
94
|
+
Under this pattern, the file always exists. Check the *content*, not the file's existence:
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
// Incomplete - file always exists under protection
|
|
98
|
+
if (!fs.existsSync(tokenPath)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Also need this:
|
|
103
|
+
const token = JSON.parse(fs.readFileSync(tokenPath, 'utf8'));
|
|
104
|
+
if (Object.keys(token).length === 0) {
|
|
105
|
+
return false; // Token was cleared
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Forgetting to add to init.js
|
|
110
|
+
|
|
111
|
+
If a new state file isn't pre-created, the first write attempt will fail with EACCES. Always add new files to `preCreateStateFiles()` in `cli/commands/init.js`.
|
|
112
|
+
|
|
113
|
+
### Using `unlinkSync` in error/cleanup paths
|
|
114
|
+
|
|
115
|
+
Every code path that touches a token file must use overwrite, not delete. This includes error handlers, expiry cleanup, forgery detection, and consumption paths.
|