fix(cli/mcp): prevent command injection via $EDITOR/$VISUAL#5288
Open
YoungDan-hero wants to merge 1 commit into
Open
fix(cli/mcp): prevent command injection via $EDITOR/$VISUAL#5288YoungDan-hero wants to merge 1 commit into
YoungDan-hero wants to merge 1 commit into
Conversation
mcpEditConfigLaunchCommand concatenated the raw $VISUAL/$EDITOR value into a `sh -lc "<editor> <path>"` command string. Only the path was shell-quoted, so an editor value like `vim; rm -rf ~` (or any environment pollution) would execute arbitrary shell commands. Replace the sh -lc construction with editorLaunchCmd, which expands $VAR references and leading ~/~ prefixes without a shell, splits the result into argv via strings.Fields, and invokes the binary directly with exec.Command. This matches the safe pattern already used by the terminal-editor fallback (exec.Command(bin, path)) in the same function. Multi-token values such as `code --wait` are still honored. Add tests covering: direct invocation (no shell), argument splitting, $VAR expansion, tilde expansion, rejection of shell metacharacters, and proof that tilde expansion cannot be abused by an injection payload.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
mcpEditConfigLaunchCommandconcatenated the raw$VISUAL/$EDITORvalueinto a
sh -lc "<editor> <path>"command string. Only the path wasshell-quoted, so an editor value like
vim; rm -rf ~(or any environmentpollution) would execute arbitrary shell commands.
Severity
P1 (command injection). Exploitation requires the environment variable to be
set to a malicious value — not remote input, but a real attack surface for
shared accounts, compromised tooling, or untrusted config sources.
Fix
Replace the
sh -lcconstruction witheditorLaunchCmd:$VARreferences viaos.ExpandEnv(no shell)strings.Fields(supportscode --wait)~/~/viaos.UserHomeDir(no shell)exec.Command(args[0], append(args[1:], path)...)— binary resolved by OSShell metacharacters become literal argv tokens and cannot be executed. This
matches the safe pattern already used by the terminal-editor fallback
(
exec.Command(bin, path)) in the same function.The now-unused
shellQuotehelper is removed.Behavior preservation
vimcode --wait$HOME/bin/x~/bin/x~vim; rm -rf ~rmNo behavior change for legitimate values. Shell quoting inside the editor
value (e.g.
'my editor') is not supported, but was already broken underthe prior
sh -lcpath for non-single-quoted values, and no commonEDITOR/VISUAL value requires it.
Test (7 cases)
TestMCPEditConfigLaunchUsesVisualBeforeEditor— direct invocationTestMCPEditConfigLaunchEditorWithArgs—code --waitarg splittingTestMCPEditConfigLaunchEditorRejectsShellMetachars— injection defenseTestMCPEditConfigLaunchEditorExpandsEnvVar—$VARexpansionTestMCPEditConfigLaunchEditorExpandsTilde—~/~/expansionTestMCPEditConfigLaunchEditorTildeNotInPayload— tilde not abusedTestMCPEditConfigLaunchFallsBackToTerminalEditor— fallback unchangedVerification
go build ./...— passgo test ./internal/cli/— all passgo vet ./internal/cli/...— cleangofmt -l— cleanCache-impact: none