@qduc/term2 0.6.0 → 0.6.1
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/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +11 -9
- package/dist/agent.js.map +1 -1
- package/dist/agent.lite-mode.test.d.ts +2 -0
- package/dist/agent.lite-mode.test.d.ts.map +1 -0
- package/dist/agent.lite-mode.test.js +39 -0
- package/dist/agent.lite-mode.test.js.map +1 -0
- package/dist/agent.test.js +17 -4
- package/dist/agent.test.js.map +1 -1
- package/dist/app.d.ts +4 -2
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +24 -1
- package/dist/app.js.map +1 -1
- package/dist/cli.js +53 -24
- package/dist/cli.js.map +1 -1
- package/dist/components/ApprovalPrompt.test.d.ts +2 -0
- package/dist/components/ApprovalPrompt.test.d.ts.map +1 -0
- package/dist/components/ApprovalPrompt.test.js +23 -0
- package/dist/components/ApprovalPrompt.test.js.map +1 -0
- package/dist/components/LiveResponse.test.d.ts +2 -0
- package/dist/components/LiveResponse.test.d.ts.map +1 -0
- package/dist/components/LiveResponse.test.js +13 -0
- package/dist/components/LiveResponse.test.js.map +1 -0
- package/dist/components/ModelSelectionMenu.error-tabs.test.d.ts +2 -0
- package/dist/components/ModelSelectionMenu.error-tabs.test.d.ts.map +1 -0
- package/dist/components/ModelSelectionMenu.error-tabs.test.js +18 -0
- package/dist/components/ModelSelectionMenu.error-tabs.test.js.map +1 -0
- package/dist/components/TextInput.d.ts +42 -0
- package/dist/components/TextInput.d.ts.map +1 -0
- package/dist/components/TextInput.js +398 -0
- package/dist/components/TextInput.js.map +1 -0
- package/dist/components/TextInput.test.d.ts +2 -0
- package/dist/components/TextInput.test.d.ts.map +1 -0
- package/dist/components/TextInput.test.js +75 -0
- package/dist/components/TextInput.test.js.map +1 -0
- package/dist/debug_ask_mentor.d.ts +2 -0
- package/dist/debug_ask_mentor.d.ts.map +1 -0
- package/dist/debug_ask_mentor.js +73 -0
- package/dist/debug_ask_mentor.js.map +1 -0
- package/dist/hooks/use-conversation.clear.test.d.ts +2 -0
- package/dist/hooks/use-conversation.clear.test.d.ts.map +1 -0
- package/dist/hooks/use-conversation.clear.test.js +49 -0
- package/dist/hooks/use-conversation.clear.test.js.map +1 -0
- package/dist/hooks/use-conversation.d.ts +5 -2
- package/dist/hooks/use-conversation.d.ts.map +1 -1
- package/dist/hooks/use-conversation.js +10 -4
- package/dist/hooks/use-conversation.js.map +1 -1
- package/dist/hooks/use-conversation.max-turns-undo.test.d.ts +2 -0
- package/dist/hooks/use-conversation.max-turns-undo.test.d.ts.map +1 -0
- package/dist/hooks/use-conversation.max-turns-undo.test.js +118 -0
- package/dist/hooks/use-conversation.max-turns-undo.test.js.map +1 -0
- package/dist/lib/openai-agent-client.chat.test.js +40 -0
- package/dist/lib/openai-agent-client.chat.test.js.map +1 -1
- package/dist/lib/openai-agent-client.d.ts +8 -1
- package/dist/lib/openai-agent-client.d.ts.map +1 -1
- package/dist/lib/openai-agent-client.js +43 -1
- package/dist/lib/openai-agent-client.js.map +1 -1
- package/dist/lib/retry-executor.js +45 -7
- package/dist/lib/retry-executor.js.map +1 -1
- package/dist/lib/retry-executor.test.js +63 -0
- package/dist/lib/retry-executor.test.js.map +1 -1
- package/dist/lib/shell.d.ts +7 -0
- package/dist/lib/shell.d.ts.map +1 -0
- package/dist/lib/shell.js +56 -0
- package/dist/lib/shell.js.map +1 -0
- package/dist/modes/companion/command-index.d.ts +26 -0
- package/dist/modes/companion/command-index.d.ts.map +1 -0
- package/dist/modes/companion/command-index.js +50 -0
- package/dist/modes/companion/command-index.js.map +1 -0
- package/dist/modes/companion/command-index.test.d.ts +2 -0
- package/dist/modes/companion/command-index.test.d.ts.map +1 -0
- package/dist/modes/companion/command-index.test.js +86 -0
- package/dist/modes/companion/command-index.test.js.map +1 -0
- package/dist/modes/companion/companion-app.d.ts +12 -0
- package/dist/modes/companion/companion-app.d.ts.map +1 -0
- package/dist/modes/companion/companion-app.js +297 -0
- package/dist/modes/companion/companion-app.js.map +1 -0
- package/dist/modes/companion/companion-session.d.ts +63 -0
- package/dist/modes/companion/companion-session.d.ts.map +1 -0
- package/dist/modes/companion/companion-session.js +146 -0
- package/dist/modes/companion/companion-session.js.map +1 -0
- package/dist/modes/companion/companion-session.test.d.ts +2 -0
- package/dist/modes/companion/companion-session.test.d.ts.map +1 -0
- package/dist/modes/companion/companion-session.test.js +28 -0
- package/dist/modes/companion/companion-session.test.js.map +1 -0
- package/dist/modes/companion/components/status-bar.d.ts +13 -0
- package/dist/modes/companion/components/status-bar.d.ts.map +1 -0
- package/dist/modes/companion/components/status-bar.js +26 -0
- package/dist/modes/companion/components/status-bar.js.map +1 -0
- package/dist/modes/companion/context-buffer.d.ts +65 -0
- package/dist/modes/companion/context-buffer.d.ts.map +1 -0
- package/dist/modes/companion/context-buffer.js +156 -0
- package/dist/modes/companion/context-buffer.js.map +1 -0
- package/dist/modes/companion/context-buffer.test.d.ts +2 -0
- package/dist/modes/companion/context-buffer.test.d.ts.map +1 -0
- package/dist/modes/companion/context-buffer.test.js +154 -0
- package/dist/modes/companion/context-buffer.test.js.map +1 -0
- package/dist/modes/companion/event-detector.d.ts +46 -0
- package/dist/modes/companion/event-detector.d.ts.map +1 -0
- package/dist/modes/companion/event-detector.js +169 -0
- package/dist/modes/companion/event-detector.js.map +1 -0
- package/dist/modes/companion/event-detector.test.d.ts +2 -0
- package/dist/modes/companion/event-detector.test.d.ts.map +1 -0
- package/dist/modes/companion/event-detector.test.js +121 -0
- package/dist/modes/companion/event-detector.test.js.map +1 -0
- package/dist/modes/companion/index.d.ts +33 -0
- package/dist/modes/companion/index.d.ts.map +1 -0
- package/dist/modes/companion/index.js +21 -0
- package/dist/modes/companion/index.js.map +1 -0
- package/dist/modes/companion/input-key-mapper.d.ts +3 -0
- package/dist/modes/companion/input-key-mapper.d.ts.map +1 -0
- package/dist/modes/companion/input-key-mapper.js +31 -0
- package/dist/modes/companion/input-key-mapper.js.map +1 -0
- package/dist/modes/companion/input-key-mapper.test.d.ts +2 -0
- package/dist/modes/companion/input-key-mapper.test.d.ts.map +1 -0
- package/dist/modes/companion/input-key-mapper.test.js +26 -0
- package/dist/modes/companion/input-key-mapper.test.js.map +1 -0
- package/dist/modes/companion/input-parser.d.ts +53 -0
- package/dist/modes/companion/input-parser.d.ts.map +1 -0
- package/dist/modes/companion/input-parser.js +114 -0
- package/dist/modes/companion/input-parser.js.map +1 -0
- package/dist/modes/companion/input-parser.test.d.ts +2 -0
- package/dist/modes/companion/input-parser.test.d.ts.map +1 -0
- package/dist/modes/companion/input-parser.test.js +123 -0
- package/dist/modes/companion/input-parser.test.js.map +1 -0
- package/dist/modes/companion/mode-manager.d.ts +41 -0
- package/dist/modes/companion/mode-manager.d.ts.map +1 -0
- package/dist/modes/companion/mode-manager.js +56 -0
- package/dist/modes/companion/mode-manager.js.map +1 -0
- package/dist/modes/companion/mode-manager.test.d.ts +2 -0
- package/dist/modes/companion/mode-manager.test.d.ts.map +1 -0
- package/dist/modes/companion/mode-manager.test.js +65 -0
- package/dist/modes/companion/mode-manager.test.js.map +1 -0
- package/dist/modes/companion/output-classifier.d.ts +15 -0
- package/dist/modes/companion/output-classifier.d.ts.map +1 -0
- package/dist/modes/companion/output-classifier.js +77 -0
- package/dist/modes/companion/output-classifier.js.map +1 -0
- package/dist/modes/companion/output-classifier.test.d.ts +2 -0
- package/dist/modes/companion/output-classifier.test.d.ts.map +1 -0
- package/dist/modes/companion/output-classifier.test.js +133 -0
- package/dist/modes/companion/output-classifier.test.js.map +1 -0
- package/dist/modes/companion/pty-wrapper.d.ts +46 -0
- package/dist/modes/companion/pty-wrapper.d.ts.map +1 -0
- package/dist/modes/companion/pty-wrapper.js +143 -0
- package/dist/modes/companion/pty-wrapper.js.map +1 -0
- package/dist/modes/companion/safety-classifier.d.ts +31 -0
- package/dist/modes/companion/safety-classifier.d.ts.map +1 -0
- package/dist/modes/companion/safety-classifier.js +140 -0
- package/dist/modes/companion/safety-classifier.js.map +1 -0
- package/dist/modes/companion/safety-classifier.test.d.ts +2 -0
- package/dist/modes/companion/safety-classifier.test.d.ts.map +1 -0
- package/dist/modes/companion/safety-classifier.test.js +151 -0
- package/dist/modes/companion/safety-classifier.test.js.map +1 -0
- package/dist/modes/companion/summarizer.d.ts +24 -0
- package/dist/modes/companion/summarizer.d.ts.map +1 -0
- package/dist/modes/companion/summarizer.js +132 -0
- package/dist/modes/companion/summarizer.js.map +1 -0
- package/dist/modes/companion/terminal-history.d.ts +27 -0
- package/dist/modes/companion/terminal-history.d.ts.map +1 -0
- package/dist/modes/companion/terminal-history.js +142 -0
- package/dist/modes/companion/terminal-history.js.map +1 -0
- package/dist/non-interactive.d.ts +1 -0
- package/dist/non-interactive.d.ts.map +1 -1
- package/dist/non-interactive.js +3 -1
- package/dist/non-interactive.js.map +1 -1
- package/dist/non-interactive.test.js +8 -1
- package/dist/non-interactive.test.js.map +1 -1
- package/dist/prompts/gpt-5.md +4 -7
- package/dist/prompts/simple-mentor.md +215 -0
- package/dist/providers/ai-sdk-logging-fetch.d.ts +1 -1
- package/dist/providers/ai-sdk-logging-fetch.d.ts.map +1 -1
- package/dist/providers/ai-sdk-logging-fetch.js +67 -44
- package/dist/providers/ai-sdk-logging-fetch.js.map +1 -1
- package/dist/providers/ai-sdk-logging-fetch.test.js +59 -5
- package/dist/providers/ai-sdk-logging-fetch.test.js.map +1 -1
- package/dist/providers/ai-sdk-openai-compatible.provider.d.ts +2 -0
- package/dist/providers/ai-sdk-openai-compatible.provider.d.ts.map +1 -1
- package/dist/providers/ai-sdk-openai-compatible.provider.js +8 -4
- package/dist/providers/ai-sdk-openai-compatible.provider.js.map +1 -1
- package/dist/providers/ai-sdk-openai-compatible.provider.test.js +119 -1
- package/dist/providers/ai-sdk-openai-compatible.provider.test.js.map +1 -1
- package/dist/providers/ai-sdk-openai.provider.d.ts +14 -0
- package/dist/providers/ai-sdk-openai.provider.d.ts.map +1 -0
- package/dist/providers/ai-sdk-openai.provider.js +18 -0
- package/dist/providers/ai-sdk-openai.provider.js.map +1 -0
- package/dist/providers/github-copilot/converters.d.ts +45 -0
- package/dist/providers/github-copilot/converters.d.ts.map +1 -0
- package/dist/providers/github-copilot/converters.js +118 -0
- package/dist/providers/github-copilot/converters.js.map +1 -0
- package/dist/providers/github-copilot/converters.test.d.ts +2 -0
- package/dist/providers/github-copilot/converters.test.d.ts.map +1 -0
- package/dist/providers/github-copilot/converters.test.js +162 -0
- package/dist/providers/github-copilot/converters.test.js.map +1 -0
- package/dist/providers/github-copilot/github-copilot.provider.d.ts +2 -0
- package/dist/providers/github-copilot/github-copilot.provider.d.ts.map +1 -0
- package/dist/providers/github-copilot/github-copilot.provider.js +75 -0
- package/dist/providers/github-copilot/github-copilot.provider.js.map +1 -0
- package/dist/providers/github-copilot/github-copilot.provider.test.d.ts +2 -0
- package/dist/providers/github-copilot/github-copilot.provider.test.d.ts.map +1 -0
- package/dist/providers/github-copilot/github-copilot.provider.test.js +26 -0
- package/dist/providers/github-copilot/github-copilot.provider.test.js.map +1 -0
- package/dist/providers/github-copilot/index.d.ts +4 -0
- package/dist/providers/github-copilot/index.d.ts.map +1 -0
- package/dist/providers/github-copilot/index.js +4 -0
- package/dist/providers/github-copilot/index.js.map +1 -0
- package/dist/providers/github-copilot/model.d.ts +24 -0
- package/dist/providers/github-copilot/model.d.ts.map +1 -0
- package/dist/providers/github-copilot/model.delta.test.d.ts +2 -0
- package/dist/providers/github-copilot/model.delta.test.d.ts.map +1 -0
- package/dist/providers/github-copilot/model.delta.test.js +15 -0
- package/dist/providers/github-copilot/model.delta.test.js.map +1 -0
- package/dist/providers/github-copilot/model.js +498 -0
- package/dist/providers/github-copilot/model.js.map +1 -0
- package/dist/providers/github-copilot/model.test.d.ts +2 -0
- package/dist/providers/github-copilot/model.test.d.ts.map +1 -0
- package/dist/providers/github-copilot/model.test.js +63 -0
- package/dist/providers/github-copilot/model.test.js.map +1 -0
- package/dist/providers/github-copilot/provider.d.ts +15 -0
- package/dist/providers/github-copilot/provider.d.ts.map +1 -0
- package/dist/providers/github-copilot/provider.js +25 -0
- package/dist/providers/github-copilot/provider.js.map +1 -0
- package/dist/providers/github-copilot/provider.test.d.ts +2 -0
- package/dist/providers/github-copilot/provider.test.d.ts.map +1 -0
- package/dist/providers/github-copilot/provider.test.js +52 -0
- package/dist/providers/github-copilot/provider.test.js.map +1 -0
- package/dist/providers/github-copilot/utils.d.ts +20 -0
- package/dist/providers/github-copilot/utils.d.ts.map +1 -0
- package/dist/providers/github-copilot/utils.js +142 -0
- package/dist/providers/github-copilot/utils.js.map +1 -0
- package/dist/providers/github-copilot/utils.test.d.ts +2 -0
- package/dist/providers/github-copilot/utils.test.d.ts.map +1 -0
- package/dist/providers/github-copilot/utils.test.js +21 -0
- package/dist/providers/github-copilot/utils.test.js.map +1 -0
- package/dist/providers/openai-compatible/api.d.ts +17 -0
- package/dist/providers/openai-compatible/api.d.ts.map +1 -0
- package/dist/providers/openai-compatible/api.js +58 -0
- package/dist/providers/openai-compatible/api.js.map +1 -0
- package/dist/providers/openai-compatible/model.d.ts +17 -0
- package/dist/providers/openai-compatible/model.d.ts.map +1 -0
- package/dist/providers/openai-compatible/model.js +473 -0
- package/dist/providers/openai-compatible/model.js.map +1 -0
- package/dist/providers/openai-compatible/provider.d.ts +22 -0
- package/dist/providers/openai-compatible/provider.d.ts.map +1 -0
- package/dist/providers/openai-compatible/provider.js +43 -0
- package/dist/providers/openai-compatible/provider.js.map +1 -0
- package/dist/providers/openai-compatible/reasoning-content.test.d.ts +2 -0
- package/dist/providers/openai-compatible/reasoning-content.test.d.ts.map +1 -0
- package/dist/providers/openai-compatible/reasoning-content.test.js +258 -0
- package/dist/providers/openai-compatible/reasoning-content.test.js.map +1 -0
- package/dist/providers/openai-compatible/utils.d.ts +3 -0
- package/dist/providers/openai-compatible/utils.d.ts.map +1 -0
- package/dist/providers/openai-compatible/utils.js +11 -0
- package/dist/providers/openai-compatible/utils.js.map +1 -0
- package/dist/providers/openai-compatible-behavior-gate.test.js +42 -1
- package/dist/providers/openai-compatible-behavior-gate.test.js.map +1 -1
- package/dist/providers/openai.provider.js +21 -1
- package/dist/providers/openai.provider.js.map +1 -1
- package/dist/providers/openrouter/api.d.ts +39 -0
- package/dist/providers/openrouter/api.d.ts.map +1 -0
- package/dist/providers/openrouter/api.js +164 -0
- package/dist/providers/openrouter/api.js.map +1 -0
- package/dist/providers/openrouter/converters.d.ts +8 -0
- package/dist/providers/openrouter/converters.d.ts.map +1 -0
- package/dist/providers/openrouter/converters.js +391 -0
- package/dist/providers/openrouter/converters.js.map +1 -0
- package/dist/providers/openrouter/converters.test.d.ts +2 -0
- package/dist/providers/openrouter/converters.test.d.ts.map +1 -0
- package/dist/providers/openrouter/converters.test.js +179 -0
- package/dist/providers/openrouter/converters.test.js.map +1 -0
- package/dist/providers/openrouter/index.d.ts +4 -0
- package/dist/providers/openrouter/index.d.ts.map +1 -0
- package/dist/providers/openrouter/index.js +4 -0
- package/dist/providers/openrouter/index.js.map +1 -0
- package/dist/providers/openrouter/model.d.ts +14 -0
- package/dist/providers/openrouter/model.d.ts.map +1 -0
- package/dist/providers/openrouter/model.js +457 -0
- package/dist/providers/openrouter/model.js.map +1 -0
- package/dist/providers/openrouter/provider.d.ts +15 -0
- package/dist/providers/openrouter/provider.d.ts.map +1 -0
- package/dist/providers/openrouter/provider.js +21 -0
- package/dist/providers/openrouter/provider.js.map +1 -0
- package/dist/providers/openrouter/reasoning-content.test.d.ts +2 -0
- package/dist/providers/openrouter/reasoning-content.test.d.ts.map +1 -0
- package/dist/providers/openrouter/reasoning-content.test.js +122 -0
- package/dist/providers/openrouter/reasoning-content.test.js.map +1 -0
- package/dist/providers/openrouter/utils.d.ts +11 -0
- package/dist/providers/openrouter/utils.d.ts.map +1 -0
- package/dist/providers/openrouter/utils.js +29 -0
- package/dist/providers/openrouter/utils.js.map +1 -0
- package/dist/providers/openrouter.api.retry.test.d.ts +2 -0
- package/dist/providers/openrouter.api.retry.test.d.ts.map +1 -0
- package/dist/providers/openrouter.api.retry.test.js +148 -0
- package/dist/providers/openrouter.api.retry.test.js.map +1 -0
- package/dist/providers/openrouter.provider.d.ts +2 -0
- package/dist/providers/openrouter.provider.d.ts.map +1 -1
- package/dist/providers/openrouter.provider.js +20 -2
- package/dist/providers/openrouter.provider.js.map +1 -1
- package/dist/providers/openrouter.provider.request-preprocessing.test.d.ts +2 -0
- package/dist/providers/openrouter.provider.request-preprocessing.test.d.ts.map +1 -0
- package/dist/providers/openrouter.provider.request-preprocessing.test.js +48 -0
- package/dist/providers/openrouter.provider.request-preprocessing.test.js.map +1 -0
- package/dist/providers/openrouter.test.d.ts +2 -0
- package/dist/providers/openrouter.test.d.ts.map +1 -0
- package/dist/providers/openrouter.test.js +1449 -0
- package/dist/providers/openrouter.test.js.map +1 -0
- package/dist/providers/registry.test.js +27 -2
- package/dist/providers/registry.test.js.map +1 -1
- package/dist/safety-checker.js +57 -0
- package/dist/scripts/eval-auto-approval/dataset.d.ts +1 -1
- package/dist/services/check_mock.d.ts +2 -0
- package/dist/services/check_mock.d.ts.map +1 -0
- package/dist/services/check_mock.js +22 -0
- package/dist/services/check_mock.js.map +1 -0
- package/dist/services/conversation-clear-save.test.d.ts +2 -0
- package/dist/services/conversation-clear-save.test.d.ts.map +1 -0
- package/dist/services/conversation-clear-save.test.js +85 -0
- package/dist/services/conversation-clear-save.test.js.map +1 -0
- package/dist/services/conversation-history-repair.d.ts +25 -0
- package/dist/services/conversation-history-repair.d.ts.map +1 -0
- package/dist/services/conversation-history-repair.js +227 -0
- package/dist/services/conversation-history-repair.js.map +1 -0
- package/dist/services/conversation-history-repair.test.d.ts +2 -0
- package/dist/services/conversation-history-repair.test.d.ts.map +1 -0
- package/dist/services/conversation-history-repair.test.js +76 -0
- package/dist/services/conversation-history-repair.test.js.map +1 -0
- package/dist/services/conversation-persistence.d.ts +7 -4
- package/dist/services/conversation-persistence.d.ts.map +1 -1
- package/dist/services/conversation-persistence.js +68 -15
- package/dist/services/conversation-persistence.js.map +1 -1
- package/dist/services/conversation-persistence.test.js +297 -2
- package/dist/services/conversation-persistence.test.js.map +1 -1
- package/dist/services/conversation-retry-policy.d.ts +7 -0
- package/dist/services/conversation-retry-policy.d.ts.map +1 -1
- package/dist/services/conversation-retry-policy.js +34 -0
- package/dist/services/conversation-retry-policy.js.map +1 -1
- package/dist/services/conversation-retry-policy.test.js +44 -2
- package/dist/services/conversation-retry-policy.test.js.map +1 -1
- package/dist/services/conversation-service.d.ts +3 -2
- package/dist/services/conversation-service.d.ts.map +1 -1
- package/dist/services/conversation-service.js +11 -3
- package/dist/services/conversation-service.js.map +1 -1
- package/dist/services/conversation-service.test.d.ts +2 -0
- package/dist/services/conversation-service.test.d.ts.map +1 -0
- package/dist/services/conversation-service.test.js +156 -0
- package/dist/services/conversation-service.test.js.map +1 -0
- package/dist/services/conversation-session.auto-approval.test.js +2 -1
- package/dist/services/conversation-session.auto-approval.test.js.map +1 -1
- package/dist/services/conversation-session.d.ts +3 -1
- package/dist/services/conversation-session.d.ts.map +1 -1
- package/dist/services/conversation-session.js +160 -89
- package/dist/services/conversation-session.js.map +1 -1
- package/dist/services/conversation-store.d.ts.map +1 -1
- package/dist/services/conversation-store.js +6 -5
- package/dist/services/conversation-store.js.map +1 -1
- package/dist/services/conversation-store.test.js +34 -0
- package/dist/services/conversation-store.test.js.map +1 -1
- package/dist/services/logging-contract.d.ts +1 -1
- package/dist/services/logging-service.d.ts +4 -0
- package/dist/services/logging-service.d.ts.map +1 -1
- package/dist/services/logging-service.js +91 -33
- package/dist/services/logging-service.js.map +1 -1
- package/dist/services/model-service.d.ts.map +1 -1
- package/dist/services/model-service.js +0 -1
- package/dist/services/model-service.js.map +1 -1
- package/dist/services/provider-traffic.d.ts +87 -0
- package/dist/services/provider-traffic.d.ts.map +1 -0
- package/dist/services/provider-traffic.js +545 -0
- package/dist/services/provider-traffic.js.map +1 -0
- package/dist/services/provider-traffic.test.d.ts +2 -0
- package/dist/services/provider-traffic.test.d.ts.map +1 -0
- package/dist/services/provider-traffic.test.js +383 -0
- package/dist/services/provider-traffic.test.js.map +1 -0
- package/dist/services/service-interfaces.d.ts +16 -0
- package/dist/services/service-interfaces.d.ts.map +1 -1
- package/dist/services/settings-service.js +6 -6
- package/dist/services/settings-service.js.map +1 -1
- package/dist/services/shell-auto-approval-evaluator.d.ts +1 -1
- package/dist/services/shell-auto-approval-evaluator.d.ts.map +1 -1
- package/dist/services/shell-auto-approval-evaluator.js +284 -81
- package/dist/services/shell-auto-approval-evaluator.js.map +1 -1
- package/dist/services/shell-auto-approval-evaluator.test.js +215 -15
- package/dist/services/shell-auto-approval-evaluator.test.js.map +1 -1
- package/dist/services/subagents/reasoning-inheritance.test.d.ts +2 -0
- package/dist/services/subagents/reasoning-inheritance.test.d.ts.map +1 -0
- package/dist/services/subagents/reasoning-inheritance.test.js +73 -0
- package/dist/services/subagents/reasoning-inheritance.test.js.map +1 -0
- package/dist/services/subagents/subagent-manager.d.ts +2 -0
- package/dist/services/subagents/subagent-manager.d.ts.map +1 -1
- package/dist/services/subagents/subagent-manager.js +36 -5
- package/dist/services/subagents/subagent-manager.js.map +1 -1
- package/dist/services/subagents/subagent-manager.test.js +99 -0
- package/dist/services/subagents/subagent-manager.test.js.map +1 -1
- package/dist/tools/bash.d.ts +10 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +55 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/utils/command-safety/path-analysis.d.ts.map +1 -1
- package/dist/utils/command-safety/path-analysis.js +143 -35
- package/dist/utils/command-safety/path-analysis.js.map +1 -1
- package/dist/utils/command-safety.d.ts +21 -0
- package/dist/utils/command-safety.d.ts.map +1 -0
- package/dist/utils/command-safety.devnull.test.d.ts +2 -0
- package/dist/utils/command-safety.devnull.test.d.ts.map +1 -0
- package/dist/utils/command-safety.devnull.test.js +13 -0
- package/dist/utils/command-safety.devnull.test.js.map +1 -0
- package/dist/utils/command-safety.js +348 -0
- package/dist/utils/command-safety.js.map +1 -0
- package/dist/utils/conversation-event-handler.d.ts.map +1 -1
- package/dist/utils/conversation-event-handler.js +19 -3
- package/dist/utils/conversation-event-handler.js.map +1 -1
- package/dist/utils/conversation-event-handler.test.js +72 -0
- package/dist/utils/conversation-event-handler.test.js.map +1 -1
- package/dist/utils/extract-command-messages.test.d.ts +2 -0
- package/dist/utils/extract-command-messages.test.d.ts.map +1 -0
- package/dist/utils/provider-traffic-extractor.repro.test.d.ts +2 -0
- package/dist/utils/provider-traffic-extractor.repro.test.d.ts.map +1 -0
- package/dist/utils/provider-traffic-extractor.repro.test.js.map +1 -0
- package/package.json +1 -1
- package/dist/hooks/use-path-completion.test.d.ts +0 -2
- package/dist/hooks/use-path-completion.test.d.ts.map +0 -1
- package/dist/hooks/use-path-completion.test.js +0 -54
- package/dist/hooks/use-path-completion.test.js.map +0 -1
- package/dist/providers/openai.provider.test.d.ts +0 -2
- package/dist/providers/openai.provider.test.d.ts.map +0 -1
- package/dist/providers/openai.provider.test.js +0 -22
- package/dist/providers/openai.provider.test.js.map +0 -1
- package/dist/utils/project-tree.d.ts +0 -14
- package/dist/utils/project-tree.d.ts.map +0 -1
- package/dist/utils/project-tree.js +0 -111
- package/dist/utils/project-tree.js.map +0 -1
- package/dist/utils/project-tree.test.d.ts +0 -2
- package/dist/utils/project-tree.test.d.ts.map +0 -1
- package/dist/utils/project-tree.test.js +0 -149
- package/dist/utils/project-tree.test.js.map +0 -1
- package/dist/utils/terminal-clear.d.ts +0 -7
- package/dist/utils/terminal-clear.d.ts.map +0 -1
- package/dist/utils/terminal-clear.js +0 -5
- package/dist/utils/terminal-clear.js.map +0 -1
- package/dist/utils/terminal-clear.test.d.ts +0 -2
- package/dist/utils/terminal-clear.test.d.ts.map +0 -1
- package/dist/utils/terminal-clear.test.js +0 -13
- package/dist/utils/terminal-clear.test.js.map +0 -1
|
@@ -10,10 +10,98 @@ const nullLoggingService = {
|
|
|
10
10
|
getCorrelationId: () => undefined,
|
|
11
11
|
clearCorrelationId: () => { },
|
|
12
12
|
};
|
|
13
|
-
|
|
13
|
+
// Dot-directories that almost always contain credentials, private keys, or secrets.
|
|
14
|
+
// Access to these should be blocked (RED) regardless of the specific file inside.
|
|
15
|
+
const HIGHLY_SENSITIVE_HOME_DIRS = new Set([
|
|
16
|
+
'.ssh',
|
|
17
|
+
'.gnupg',
|
|
18
|
+
'.gpg',
|
|
19
|
+
'.aws',
|
|
20
|
+
'.azure',
|
|
21
|
+
'.gcloud',
|
|
22
|
+
'.config/gcloud',
|
|
23
|
+
'.kube',
|
|
24
|
+
'.docker',
|
|
25
|
+
'.netrc',
|
|
26
|
+
'.pgpass',
|
|
27
|
+
'.password-store',
|
|
28
|
+
'.cert',
|
|
29
|
+
'.certs',
|
|
30
|
+
'.keys',
|
|
31
|
+
'.secrets',
|
|
32
|
+
]);
|
|
33
|
+
// Sensitive dotfiles at the root of the home directory (RED).
|
|
34
|
+
const HIGHLY_SENSITIVE_HOME_FILES = new Set([
|
|
35
|
+
'.netrc',
|
|
36
|
+
'.pgpass',
|
|
37
|
+
'.npmrc', // can contain auth tokens
|
|
38
|
+
'.pypirc', // can contain auth tokens
|
|
39
|
+
'.gitconfig', // may contain credentials / signing keys
|
|
40
|
+
'.git-credentials',
|
|
41
|
+
'.bash_history',
|
|
42
|
+
'.zsh_history',
|
|
43
|
+
'.python_history',
|
|
44
|
+
'.node_repl_history',
|
|
45
|
+
'.mysql_history',
|
|
46
|
+
'.psql_history',
|
|
47
|
+
'.lesshst',
|
|
48
|
+
]);
|
|
49
|
+
// Dot-directories that are commonly used as working/cache/data directories.
|
|
50
|
+
// These should NOT be RED — they're YELLOW at worst, often GREEN-ish.
|
|
51
|
+
// Users frequently work in these (installing tools, inspecting caches, etc.).
|
|
52
|
+
const COMMON_WORKING_DOT_DIRS = new Set([
|
|
53
|
+
'.local',
|
|
54
|
+
'.cache',
|
|
55
|
+
'.config', // root .config is broad; specific sensitive subdirs handled above
|
|
56
|
+
'.npm',
|
|
57
|
+
'.yarn',
|
|
58
|
+
'.pnpm',
|
|
59
|
+
'.nvm',
|
|
60
|
+
'.node_modules',
|
|
61
|
+
'.vscode',
|
|
62
|
+
'.idea',
|
|
63
|
+
'.cursor',
|
|
64
|
+
'.vim',
|
|
65
|
+
'.emacs.d',
|
|
66
|
+
'.mozilla',
|
|
67
|
+
'.themes',
|
|
68
|
+
'.icons',
|
|
69
|
+
'.fonts',
|
|
70
|
+
'.gradle',
|
|
71
|
+
'.m2',
|
|
72
|
+
'.cargo',
|
|
73
|
+
'.rustup',
|
|
74
|
+
'.pyenv',
|
|
75
|
+
'.rbenv',
|
|
76
|
+
'.virtualenvs',
|
|
77
|
+
'.docker-desktop',
|
|
78
|
+
]);
|
|
79
|
+
const SENSITIVE_LOCAL_PATH_SEGMENTS = new Set(['.ssh', '.gnupg', '.aws', '.kube', '.git', '.env']);
|
|
14
80
|
function getLogger(loggingService) {
|
|
15
81
|
return loggingService ?? nullLoggingService;
|
|
16
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Given a sliced home-relative path (e.g. "/.ssh/id_rsa" or "/.local/bin/foo"),
|
|
85
|
+
* return the first dot-segment (e.g. ".ssh", ".local") or undefined.
|
|
86
|
+
*/
|
|
87
|
+
function firstDotSegment(sliced) {
|
|
88
|
+
const segments = sliced.split('/').filter(Boolean);
|
|
89
|
+
const first = segments[0];
|
|
90
|
+
return first && first.startsWith('.') ? first : undefined;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check whether a sliced home path falls within a highly sensitive directory.
|
|
94
|
+
* Handles nested keys like ".config/gcloud".
|
|
95
|
+
*/
|
|
96
|
+
function isInHighlySensitiveDir(sliced) {
|
|
97
|
+
const trimmed = sliced.replace(/^\/+/, '');
|
|
98
|
+
for (const dir of HIGHLY_SENSITIVE_HOME_DIRS) {
|
|
99
|
+
if (trimmed === dir || trimmed.startsWith(dir + '/')) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
17
105
|
export function analyzePathRisk(inputPath, loggingService) {
|
|
18
106
|
const logger = getLogger(loggingService);
|
|
19
107
|
const candidate = inputPath?.trim();
|
|
@@ -46,48 +134,74 @@ export function analyzePathRisk(inputPath, loggingService) {
|
|
|
46
134
|
// Plain home directory access without any suffix is broad and privacy-sensitive,
|
|
47
135
|
// but not inherently destructive. Let the model judge the task context.
|
|
48
136
|
if (sliced === '' || sliced === '/') {
|
|
49
|
-
logger.security('Path risk: home directory access', {
|
|
50
|
-
path: candidate,
|
|
51
|
-
});
|
|
137
|
+
logger.security('Path risk: home directory access', { path: candidate });
|
|
52
138
|
return SafetyStatus.YELLOW;
|
|
53
139
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
140
|
+
const filename = path.basename(candidate);
|
|
141
|
+
const topDot = firstDotSegment(sliced);
|
|
142
|
+
// RED: highly sensitive credential directories (anywhere inside)
|
|
143
|
+
if (isInHighlySensitiveDir(sliced)) {
|
|
144
|
+
logger.security('Path risk: sensitive credential directory', { path: candidate });
|
|
59
145
|
return SafetyStatus.RED;
|
|
60
146
|
}
|
|
61
|
-
//
|
|
62
|
-
|
|
147
|
+
// RED: highly sensitive dotfiles at home root
|
|
148
|
+
// (only when the file sits directly under home, not deep inside another dir)
|
|
149
|
+
const slicedSegments = sliced.split('/').filter(Boolean);
|
|
150
|
+
if (slicedSegments.length === 1 && HIGHLY_SENSITIVE_HOME_FILES.has(slicedSegments[0])) {
|
|
151
|
+
logger.security('Path risk: sensitive home dotfile', { path: candidate });
|
|
152
|
+
return SafetyStatus.RED;
|
|
153
|
+
}
|
|
154
|
+
// Legacy explicit sensitive-path substring match (kept for back-compat with constants.ts)
|
|
155
|
+
if (SENSITIVE_PATHS.some((sensitive) => sliced.includes(sensitive))) {
|
|
156
|
+
logger.security('Path risk: known sensitive path', { path: candidate });
|
|
157
|
+
return SafetyStatus.RED;
|
|
158
|
+
}
|
|
159
|
+
// Suspicious filename patterns (credentials.json, *_token.json, etc.) anywhere in home -> RED
|
|
63
160
|
if (/\.json$/i.test(filename) && SUSPICIOUS_JSON_PATTERNS.some((pattern) => pattern.test(filename))) {
|
|
64
161
|
logger.security('Path risk: suspicious JSON file in home directory', { path: candidate });
|
|
65
162
|
return SafetyStatus.RED;
|
|
66
163
|
}
|
|
67
|
-
//
|
|
164
|
+
// Sensitive extensions (private keys, pem, etc.) anywhere in home -> RED
|
|
68
165
|
if (SENSITIVE_EXTENSIONS.some((ext) => filename.endsWith(ext))) {
|
|
69
|
-
logger.security('Path risk: sensitive file in home directory', {
|
|
70
|
-
path: candidate,
|
|
71
|
-
});
|
|
166
|
+
logger.security('Path risk: sensitive file in home directory', { path: candidate });
|
|
72
167
|
return SafetyStatus.RED;
|
|
73
168
|
}
|
|
169
|
+
// YELLOW: common working dot-directories (~/.local, ~/.cache, ~/.config/..., etc.)
|
|
170
|
+
// Users legitimately operate here often — flag as cautious, not blocked.
|
|
171
|
+
if (topDot && COMMON_WORKING_DOT_DIRS.has(topDot)) {
|
|
172
|
+
logger.security('Path risk: home working dot-directory', { path: candidate, segment: topDot });
|
|
173
|
+
return SafetyStatus.YELLOW;
|
|
174
|
+
}
|
|
175
|
+
// YELLOW: any other unknown dotfile/dotdir directly under home — be cautious
|
|
176
|
+
// but don't block, since many tools create custom dot-directories.
|
|
177
|
+
if (topDot) {
|
|
178
|
+
logger.security('Path risk: unknown home dot-entry', { path: candidate, segment: topDot });
|
|
179
|
+
return SafetyStatus.YELLOW;
|
|
180
|
+
}
|
|
181
|
+
// Non-dot files/folders inside home -> YELLOW (privacy-sensitive but not destructive)
|
|
182
|
+
logger.security('Path risk: home directory file', { path: candidate });
|
|
183
|
+
return SafetyStatus.YELLOW;
|
|
74
184
|
}
|
|
75
185
|
// YELLOW: Absolute System Paths
|
|
76
186
|
if (path.isAbsolute(candidate)) {
|
|
77
187
|
if (SYSTEM_PATHS.some((sys) => candidate.startsWith(sys))) {
|
|
78
|
-
logger.security('Path risk: absolute system path', {
|
|
79
|
-
path: candidate,
|
|
80
|
-
});
|
|
188
|
+
logger.security('Path risk: absolute system path', { path: candidate });
|
|
81
189
|
return SafetyStatus.YELLOW;
|
|
82
190
|
}
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
candidate.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
191
|
+
// Absolute paths into highly sensitive home dirs are still RED
|
|
192
|
+
const absHomeMatch = candidate.match(/^\/(?:home|Users)\/[^/]+(\/.*)?$/) ||
|
|
193
|
+
(candidate.startsWith('/root') ? [candidate, candidate.slice(5)] : null);
|
|
194
|
+
if (absHomeMatch) {
|
|
195
|
+
const tail = absHomeMatch[1] ?? '';
|
|
196
|
+
if (isInHighlySensitiveDir(tail)) {
|
|
197
|
+
logger.security('Path risk: absolute sensitive home dir', { path: candidate });
|
|
198
|
+
return SafetyStatus.RED;
|
|
199
|
+
}
|
|
200
|
+
const tailSegments = tail.split('/').filter(Boolean);
|
|
201
|
+
if (tailSegments.length === 1 && HIGHLY_SENSITIVE_HOME_FILES.has(tailSegments[0])) {
|
|
202
|
+
logger.security('Path risk: absolute sensitive home file', { path: candidate });
|
|
203
|
+
return SafetyStatus.RED;
|
|
204
|
+
}
|
|
91
205
|
}
|
|
92
206
|
// Check if absolute path is within current project directory or in a safe temporary directory
|
|
93
207
|
const isTempDir = normalizedCandidate === '/tmp' ||
|
|
@@ -96,9 +210,7 @@ export function analyzePathRisk(inputPath, loggingService) {
|
|
|
96
210
|
normalizedCandidate.startsWith('/private/tmp' + path.sep);
|
|
97
211
|
if (!isWithinProject && !isTempDir) {
|
|
98
212
|
// Absolute paths outside project are suspicious -> audit
|
|
99
|
-
logger.security('Path risk: absolute non-system path', {
|
|
100
|
-
path: candidate,
|
|
101
|
-
});
|
|
213
|
+
logger.security('Path risk: absolute non-system path', { path: candidate });
|
|
102
214
|
return SafetyStatus.YELLOW;
|
|
103
215
|
}
|
|
104
216
|
// If within project, continue with normal flow (treat like relative path)
|
|
@@ -106,16 +218,12 @@ export function analyzePathRisk(inputPath, loggingService) {
|
|
|
106
218
|
}
|
|
107
219
|
// YELLOW: Directory Traversal
|
|
108
220
|
if (candidate.includes('..')) {
|
|
109
|
-
logger.security('Path risk: directory traversal detected', {
|
|
110
|
-
path: candidate,
|
|
111
|
-
});
|
|
221
|
+
logger.security('Path risk: directory traversal detected', { path: candidate });
|
|
112
222
|
return SafetyStatus.YELLOW;
|
|
113
223
|
}
|
|
114
224
|
const pathSegments = normalizedCandidate.split(/[\\/]+/).filter(Boolean);
|
|
115
225
|
if (pathSegments.some((segment) => SENSITIVE_LOCAL_PATH_SEGMENTS.has(segment))) {
|
|
116
|
-
logger.security('Path risk: sensitive local dot-directory', {
|
|
117
|
-
path: candidate,
|
|
118
|
-
});
|
|
226
|
+
logger.security('Path risk: sensitive local dot-directory', { path: candidate });
|
|
119
227
|
return SafetyStatus.YELLOW;
|
|
120
228
|
}
|
|
121
229
|
const filename = path.basename(candidate);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-analysis.js","sourceRoot":"","sources":["../../../source/utils/command-safety/path-analysis.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,eAAe,EACf,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AAExB,MAAM,kBAAkB,GAAoB;IAC1C,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;IACf,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;IACf,QAAQ,EAAE,GAAG,EAAE,GAAE,CAAC;IAClB,gBAAgB,EAAE,GAAG,EAAE,GAAE,CAAC;IAC1B,gBAAgB,EAAE,GAAG,EAAE,CAAC,SAAS;IACjC,kBAAkB,EAAE,GAAG,EAAE,GAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,
|
|
1
|
+
{"version":3,"file":"path-analysis.js","sourceRoot":"","sources":["../../../source/utils/command-safety/path-analysis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate command safety using an AST parser.
|
|
3
|
+
* Returns true when a command requires user approval.
|
|
4
|
+
* Throws for invalid/empty inputs.
|
|
5
|
+
*/
|
|
6
|
+
export declare enum SafetyStatus {
|
|
7
|
+
GREEN = "GREEN",
|
|
8
|
+
YELLOW = "YELLOW",
|
|
9
|
+
RED = "RED"
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Classify command into a SafetyStatus (GREEN/YELLOW/RED)
|
|
13
|
+
*/
|
|
14
|
+
export declare function classifyCommand(commandString: string): SafetyStatus;
|
|
15
|
+
/**
|
|
16
|
+
* Validate command safety using an AST parser.
|
|
17
|
+
* Returns true when a command requires user approval.
|
|
18
|
+
* Throws for invalid/empty inputs OR hard-blocked RED classifications.
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateCommandSafety(command: string): boolean;
|
|
21
|
+
//# sourceMappingURL=command-safety.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-safety.d.ts","sourceRoot":"","sources":["../../source/utils/command-safety.ts"],"names":[],"mappings":"AAuDA;;;;GAIG;AACH,oBAAY,YAAY;IACpB,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,GAAG,QAAQ;CACd;AAyGD;;GAEG;AACH,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,YAAY,CAyMnE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAyB9D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-safety.devnull.test.d.ts","sourceRoot":"","sources":["../../source/utils/command-safety.devnull.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import { analyzePathRisk } from './command-safety/path-analysis.js';
|
|
3
|
+
import { SafetyStatus, classifyCommand } from './command-safety/index.js';
|
|
4
|
+
test('analyzePathRisk - /dev/null should be GREEN', t => {
|
|
5
|
+
const result = analyzePathRisk('/dev/null');
|
|
6
|
+
t.is(result, SafetyStatus.GREEN, '/dev/null should be considered safe');
|
|
7
|
+
});
|
|
8
|
+
test('classifyCommand - cat prod.sh 2>/dev/null | head -200 should be GREEN', t => {
|
|
9
|
+
const command = 'cat prod.sh 2>/dev/null | head -200';
|
|
10
|
+
const status = classifyCommand(command);
|
|
11
|
+
t.is(status, SafetyStatus.GREEN, 'command with /dev/null redirection should be GREEN');
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=command-safety.devnull.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-safety.devnull.test.js","sourceRoot":"","sources":["../../source/utils/command-safety.devnull.test.ts"],"names":[],"mappings":"AACA,OAAO,IAAI,MAAM,KAAK,CAAC;AACvB,OAAO,EAAC,eAAe,EAAC,MAAM,mCAAmC,CAAC;AAClE,OAAO,EAAC,YAAY,EAAE,eAAe,EAAC,MAAM,2BAA2B,CAAC;AAExE,IAAI,CAAC,6CAA6C,EAAE,CAAC,CAAC,EAAE;IACpD,MAAM,MAAM,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAC5C,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,KAAK,EAAE,qCAAqC,CAAC,CAAC;AAC5E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uEAAuE,EAAE,CAAC,CAAC,EAAE;IAC9E,MAAM,OAAO,GAAG,qCAAqC,CAAC;IACtD,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,KAAK,EAAE,oDAAoD,CAAC,CAAC;AAC3F,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import parse from 'bash-parser';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { loggingService } from '../services/logging-service.js';
|
|
4
|
+
// 1. CONSTANTS
|
|
5
|
+
// Note: 'sed' is useful for read-only transformations. We allow it by default
|
|
6
|
+
// but add guards below to prevent in-place edits (-i) and unapproved redirections.
|
|
7
|
+
const ALLOWED_COMMANDS = new Set([
|
|
8
|
+
'ls',
|
|
9
|
+
'pwd',
|
|
10
|
+
'grep',
|
|
11
|
+
'cat',
|
|
12
|
+
'echo',
|
|
13
|
+
'head',
|
|
14
|
+
'tail',
|
|
15
|
+
'sed',
|
|
16
|
+
]);
|
|
17
|
+
const BLOCKED_COMMANDS = new Set([
|
|
18
|
+
// Filesystem
|
|
19
|
+
'rm',
|
|
20
|
+
'rmdir',
|
|
21
|
+
'mkfs',
|
|
22
|
+
'dd',
|
|
23
|
+
'mv',
|
|
24
|
+
'cp',
|
|
25
|
+
// System
|
|
26
|
+
'sudo',
|
|
27
|
+
'su',
|
|
28
|
+
'chmod',
|
|
29
|
+
'chown',
|
|
30
|
+
'shutdown',
|
|
31
|
+
'reboot',
|
|
32
|
+
// Network/Web
|
|
33
|
+
'curl',
|
|
34
|
+
'wget',
|
|
35
|
+
'ssh',
|
|
36
|
+
'scp',
|
|
37
|
+
'netstat',
|
|
38
|
+
// Package Managers / installers
|
|
39
|
+
'apt',
|
|
40
|
+
'yum',
|
|
41
|
+
'npm',
|
|
42
|
+
'yarn',
|
|
43
|
+
'pnpm',
|
|
44
|
+
'pip',
|
|
45
|
+
'gem',
|
|
46
|
+
// Dangerous wrappers / misc
|
|
47
|
+
'eval',
|
|
48
|
+
'exec',
|
|
49
|
+
'kill',
|
|
50
|
+
'killall',
|
|
51
|
+
]);
|
|
52
|
+
/* legacy containsDangerousCommand removed — replaced by classifyCommand + path analysis */
|
|
53
|
+
/**
|
|
54
|
+
* Validate command safety using an AST parser.
|
|
55
|
+
* Returns true when a command requires user approval.
|
|
56
|
+
* Throws for invalid/empty inputs.
|
|
57
|
+
*/
|
|
58
|
+
export var SafetyStatus;
|
|
59
|
+
(function (SafetyStatus) {
|
|
60
|
+
SafetyStatus["GREEN"] = "GREEN";
|
|
61
|
+
SafetyStatus["YELLOW"] = "YELLOW";
|
|
62
|
+
SafetyStatus["RED"] = "RED";
|
|
63
|
+
})(SafetyStatus || (SafetyStatus = {}));
|
|
64
|
+
// Extract a best-effort string for a word/arg node, including expansions.
|
|
65
|
+
function extractWordText(word) {
|
|
66
|
+
if (!word)
|
|
67
|
+
return undefined;
|
|
68
|
+
if (typeof word === 'string')
|
|
69
|
+
return word;
|
|
70
|
+
if (typeof word.text === 'string')
|
|
71
|
+
return word.text;
|
|
72
|
+
if (typeof word.value === 'string')
|
|
73
|
+
return word.value;
|
|
74
|
+
if (typeof word.content === 'string')
|
|
75
|
+
return word.content;
|
|
76
|
+
if (word.parameter)
|
|
77
|
+
return `$${word.parameter}`;
|
|
78
|
+
if (Array.isArray(word.parts)) {
|
|
79
|
+
return word.parts
|
|
80
|
+
.map((part) => extractWordText(part) ?? '')
|
|
81
|
+
.join('');
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
// 2. PATH ANALYSIS HELPER
|
|
86
|
+
function analyzePathRisk(inputPath) {
|
|
87
|
+
const candidate = inputPath?.trim();
|
|
88
|
+
if (!candidate)
|
|
89
|
+
return SafetyStatus.GREEN;
|
|
90
|
+
// RED: Explicit home-dotfiles (.ssh, .env, etc.) even before expansion
|
|
91
|
+
if (candidate.startsWith('~') ||
|
|
92
|
+
candidate.startsWith('$HOME') ||
|
|
93
|
+
candidate.startsWith('${HOME}')) {
|
|
94
|
+
const sliced = candidate.replace(/^~/, '').replace(/^\$\{?HOME\}?/, '');
|
|
95
|
+
// Check for dotfiles after home prefix
|
|
96
|
+
if (/^\/\.\w+/.test(sliced) ||
|
|
97
|
+
sliced.includes('/.ssh') ||
|
|
98
|
+
sliced.includes('/.env')) {
|
|
99
|
+
loggingService.security('Path risk: home dotfile', {
|
|
100
|
+
path: candidate,
|
|
101
|
+
});
|
|
102
|
+
return SafetyStatus.RED;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// RED: Absolute System Paths
|
|
106
|
+
if (path.isAbsolute(candidate)) {
|
|
107
|
+
const SYSTEM_PATHS = [
|
|
108
|
+
'/etc',
|
|
109
|
+
'/dev',
|
|
110
|
+
'/proc',
|
|
111
|
+
'/var',
|
|
112
|
+
'/usr',
|
|
113
|
+
'/boot',
|
|
114
|
+
'/bin',
|
|
115
|
+
];
|
|
116
|
+
if (SYSTEM_PATHS.some(sys => candidate.startsWith(sys))) {
|
|
117
|
+
loggingService.security('Path risk: absolute system path', {
|
|
118
|
+
path: candidate,
|
|
119
|
+
});
|
|
120
|
+
return SafetyStatus.RED;
|
|
121
|
+
}
|
|
122
|
+
// Home dotfiles when absolute
|
|
123
|
+
if (/^\/(home|Users)\/[^/]+\/\.\w+/.test(candidate) ||
|
|
124
|
+
candidate.includes('/.ssh') ||
|
|
125
|
+
candidate.includes('/.gitconfig')) {
|
|
126
|
+
loggingService.security('Path risk: absolute home dotfile', {
|
|
127
|
+
path: candidate,
|
|
128
|
+
});
|
|
129
|
+
return SafetyStatus.RED;
|
|
130
|
+
}
|
|
131
|
+
// Other absolute paths are suspicious -> audit
|
|
132
|
+
loggingService.security('Path risk: absolute non-system path', {
|
|
133
|
+
path: candidate,
|
|
134
|
+
});
|
|
135
|
+
return SafetyStatus.YELLOW;
|
|
136
|
+
}
|
|
137
|
+
// RED: Directory Traversal
|
|
138
|
+
if (candidate.includes('..')) {
|
|
139
|
+
loggingService.security('Path risk: directory traversal detected', {
|
|
140
|
+
path: candidate,
|
|
141
|
+
});
|
|
142
|
+
return SafetyStatus.RED;
|
|
143
|
+
}
|
|
144
|
+
// Hidden files -> YELLOW
|
|
145
|
+
const filename = path.basename(candidate);
|
|
146
|
+
if (filename.startsWith('.')) {
|
|
147
|
+
loggingService.security('Path risk: hidden file', { path: candidate });
|
|
148
|
+
return SafetyStatus.YELLOW;
|
|
149
|
+
}
|
|
150
|
+
// Sensitive extensions
|
|
151
|
+
const SENSITIVE_EXTENSIONS = ['.env', '.pem', '.key', '.json'];
|
|
152
|
+
if (SENSITIVE_EXTENSIONS.some(ext => filename.endsWith(ext))) {
|
|
153
|
+
loggingService.security('Path risk: sensitive extension', {
|
|
154
|
+
path: candidate,
|
|
155
|
+
});
|
|
156
|
+
return SafetyStatus.YELLOW;
|
|
157
|
+
}
|
|
158
|
+
return SafetyStatus.GREEN;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Classify command into a SafetyStatus (GREEN/YELLOW/RED)
|
|
162
|
+
*/
|
|
163
|
+
export function classifyCommand(commandString) {
|
|
164
|
+
try {
|
|
165
|
+
const reasons = [];
|
|
166
|
+
const truncatedCommand = commandString.substring(0, 200);
|
|
167
|
+
loggingService.security('Classifying command safety', {
|
|
168
|
+
command: truncatedCommand,
|
|
169
|
+
});
|
|
170
|
+
const ast = parse(commandString, { mode: 'bash' });
|
|
171
|
+
let worstStatus = SafetyStatus.GREEN;
|
|
172
|
+
function upgradeStatus(s, reason) {
|
|
173
|
+
if (worstStatus === SafetyStatus.RED)
|
|
174
|
+
return;
|
|
175
|
+
if (s === SafetyStatus.RED)
|
|
176
|
+
worstStatus = SafetyStatus.RED;
|
|
177
|
+
else if (s === SafetyStatus.YELLOW &&
|
|
178
|
+
worstStatus === SafetyStatus.GREEN)
|
|
179
|
+
worstStatus = SafetyStatus.YELLOW;
|
|
180
|
+
if (reason)
|
|
181
|
+
reasons.push(`${s}: ${reason}`);
|
|
182
|
+
}
|
|
183
|
+
function traverse(node) {
|
|
184
|
+
if (!node)
|
|
185
|
+
return;
|
|
186
|
+
if (Array.isArray(node))
|
|
187
|
+
return node.forEach(traverse);
|
|
188
|
+
if (node.type === 'Command') {
|
|
189
|
+
const name = node.name?.text ||
|
|
190
|
+
(node.name &&
|
|
191
|
+
node.name.parts &&
|
|
192
|
+
node.name.parts.map((p) => p.text).join(''));
|
|
193
|
+
if (typeof name === 'string') {
|
|
194
|
+
if (BLOCKED_COMMANDS.has(name)) {
|
|
195
|
+
upgradeStatus(SafetyStatus.RED, `blocked command: ${name}`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (!ALLOWED_COMMANDS.has(name)) {
|
|
199
|
+
upgradeStatus(SafetyStatus.YELLOW, `unknown or unlisted command: ${name}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const cmdName = typeof name === 'string' ? name : undefined;
|
|
203
|
+
if (node.suffix) {
|
|
204
|
+
let hasOutputRedirect = false;
|
|
205
|
+
let hasInPlaceEdit = false;
|
|
206
|
+
// First pass: detect dangerous sed patterns
|
|
207
|
+
for (const arg of node.suffix) {
|
|
208
|
+
if (arg?.type === 'Redirect') {
|
|
209
|
+
// Check if it's an output redirect (>, >>)
|
|
210
|
+
const op = arg.op?.text || arg.op;
|
|
211
|
+
if (op === '>' || op === '>>') {
|
|
212
|
+
hasOutputRedirect = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const argText = extractWordText(arg);
|
|
216
|
+
if (argText && argText.startsWith('-')) {
|
|
217
|
+
if (cmdName === 'sed' && argText.startsWith('-i')) {
|
|
218
|
+
hasInPlaceEdit = true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Second pass: classify arguments
|
|
223
|
+
for (const arg of node.suffix) {
|
|
224
|
+
// Redirects: analyze path risk. For `sed`, only mark output redirects as YELLOW
|
|
225
|
+
if (arg?.type === 'Redirect') {
|
|
226
|
+
const fileText = extractWordText(arg.file ?? arg);
|
|
227
|
+
const op = arg.op?.text || arg.op;
|
|
228
|
+
if (cmdName === 'sed' &&
|
|
229
|
+
(op === '>' || op === '>>')) {
|
|
230
|
+
upgradeStatus(SafetyStatus.YELLOW, `sed with output redirection to ${fileText ?? '<unknown>'}`);
|
|
231
|
+
}
|
|
232
|
+
const pathStatus = analyzePathRisk(fileText);
|
|
233
|
+
upgradeStatus(pathStatus, `redirect to ${fileText ?? '<unknown>'}`);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const argText = extractWordText(arg);
|
|
237
|
+
// Flags are normally ignored, but for `sed` the -i flag is dangerous
|
|
238
|
+
// because it performs in-place edits. Detect -i and variants (e.g. -i, -i.bak, -i'')
|
|
239
|
+
if (argText && argText.startsWith('-')) {
|
|
240
|
+
if (cmdName === 'sed' && argText.startsWith('-i')) {
|
|
241
|
+
upgradeStatus(SafetyStatus.RED, `sed in-place edit detected: ${argText}`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
continue; // other flags ignored
|
|
245
|
+
}
|
|
246
|
+
const pathStatus = analyzePathRisk(argText);
|
|
247
|
+
// For `sed`, file arguments are only risky if combined with dangerous operations
|
|
248
|
+
if (cmdName === 'sed' && argText) {
|
|
249
|
+
// If there's an in-place edit or output redirect, path risk matters
|
|
250
|
+
// Otherwise, reading files with sed is safe (GREEN)
|
|
251
|
+
if (hasInPlaceEdit || hasOutputRedirect) {
|
|
252
|
+
if (pathStatus === SafetyStatus.RED)
|
|
253
|
+
upgradeStatus(pathStatus, `sed file argument ${argText}`);
|
|
254
|
+
else
|
|
255
|
+
upgradeStatus(SafetyStatus.YELLOW, `sed file argument ${argText}`);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// Read-only sed: only escalate if path itself is risky
|
|
259
|
+
if (pathStatus !== SafetyStatus.GREEN) {
|
|
260
|
+
upgradeStatus(pathStatus, `sed file argument ${argText}`);
|
|
261
|
+
}
|
|
262
|
+
// Otherwise GREEN - read-only sed is safe
|
|
263
|
+
}
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
// Unknown/opaque args fall back to YELLOW
|
|
267
|
+
if (!argText)
|
|
268
|
+
upgradeStatus(SafetyStatus.YELLOW, 'opaque or unparseable argument');
|
|
269
|
+
else
|
|
270
|
+
upgradeStatus(pathStatus, `argument ${argText}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// recurse common shapes
|
|
275
|
+
if (node.type === 'LogicalExpression') {
|
|
276
|
+
traverse(node.left);
|
|
277
|
+
traverse(node.right);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (node.type === 'Pipeline') {
|
|
281
|
+
(node.commands || []).forEach(traverse);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (node.type === 'Subshell') {
|
|
285
|
+
traverse(node.list);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (node.type === 'CommandSubstitution') {
|
|
289
|
+
(node.commands || []).forEach(traverse);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (node.type === 'Script' || node.type === 'Program') {
|
|
293
|
+
(node.commands || []).forEach(traverse);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
for (const k of Object.keys(node)) {
|
|
297
|
+
const v = node[k];
|
|
298
|
+
if (v && typeof v === 'object')
|
|
299
|
+
traverse(v);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (ast && ast.commands) {
|
|
303
|
+
ast.commands.forEach(traverse);
|
|
304
|
+
}
|
|
305
|
+
loggingService.security('Command classification result', {
|
|
306
|
+
command: truncatedCommand,
|
|
307
|
+
status: worstStatus,
|
|
308
|
+
reasons,
|
|
309
|
+
});
|
|
310
|
+
return worstStatus;
|
|
311
|
+
}
|
|
312
|
+
catch (e) {
|
|
313
|
+
// Fail-safe: unparsable -> audit
|
|
314
|
+
loggingService.warn('Failed to parse command, classifying as YELLOW', {
|
|
315
|
+
command: commandString.substring(0, 200),
|
|
316
|
+
error: e instanceof Error ? e.message : String(e),
|
|
317
|
+
});
|
|
318
|
+
return SafetyStatus.YELLOW;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Validate command safety using an AST parser.
|
|
323
|
+
* Returns true when a command requires user approval.
|
|
324
|
+
* Throws for invalid/empty inputs OR hard-blocked RED classifications.
|
|
325
|
+
*/
|
|
326
|
+
export function validateCommandSafety(command) {
|
|
327
|
+
if (!command ||
|
|
328
|
+
typeof command !== 'string' ||
|
|
329
|
+
command.trim().length === 0) {
|
|
330
|
+
throw new Error('Command cannot be empty');
|
|
331
|
+
}
|
|
332
|
+
loggingService.security('Validating command safety', {
|
|
333
|
+
command: command.substring(0, 200),
|
|
334
|
+
});
|
|
335
|
+
const status = classifyCommand(command);
|
|
336
|
+
if (status === SafetyStatus.RED) {
|
|
337
|
+
loggingService.security('Command validation failed: RED (forbidden)', {
|
|
338
|
+
command: command.substring(0, 200),
|
|
339
|
+
});
|
|
340
|
+
throw new Error('Command classified as RED (forbidden)');
|
|
341
|
+
}
|
|
342
|
+
loggingService.security('Validation result', {
|
|
343
|
+
command: command.substring(0, 200),
|
|
344
|
+
status,
|
|
345
|
+
});
|
|
346
|
+
return status === SafetyStatus.YELLOW;
|
|
347
|
+
}
|
|
348
|
+
//# sourceMappingURL=command-safety.js.map
|