Fix hook commands that pass file URIs to spawn#4666
Fix hook commands that pass file URIs to spawn#4666darrenoh wants to merge 1 commit intomicrosoft:mainfrom
Conversation
|
@microsoft-github-policy-service agree |
feb9806 to
4800403
Compare
There was a problem hiding this comment.
Pull request overview
This PR fixes hook command execution when commands include file:// URIs by rewriting those URI tokens into filesystem paths before running the command via spawn, and adds unit tests to prevent regressions in URI handling.
Changes:
- Sanitize
file://...tokens insideChatHookCommand.commandbefore invokingchild_process.spawn. - Decode file URIs to filesystem paths and (re-)quote paths when needed (e.g., spaces).
- Add unit tests covering file URI sanitization, quoting preservation, and non-file URI passthrough.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/platform/chat/node/hookExecutor.ts |
Sanitizes hook command strings by detecting file:// URI tokens, converting them to fsPath, and quoting when needed before spawn. |
src/platform/chat/test/node/hookExecutor.spec.ts |
Adds regression tests asserting the sanitized command string passed to spawn for file URIs and non-file URIs. |
| return arg; | ||
| } | ||
|
|
||
| return `"${arg.replace(/["\\]/g, '\\$&')}"`; |
There was a problem hiding this comment.
quoteArgForShell wraps args in double quotes but does not escape $ or backticks. In /bin/sh -c ... (used when shell: true), $... and `...` are still expanded/executed inside double quotes, so a decoded file path containing these characters could change command behavior or allow command substitution. Consider switching to a POSIX-safe quoting strategy (e.g., single-quote with proper escaping) or explicitly escaping $ and backticks for the shell you invoke.
| return `"${arg.replace(/["\\]/g, '\\$&')}"`; | |
| if (!isWindows) { | |
| // POSIX-safe single-quote escaping: 'foo' -> 'foo', "foo'bar" -> 'foo'"'"'bar' | |
| let result = "'"; | |
| for (const ch of arg) { | |
| if (ch === "'") { | |
| result += `'"'"'`; | |
| } else { | |
| result += ch; | |
| } | |
| } | |
| result += "'"; | |
| return result; | |
| } | |
| // PowerShell-safe single-quote escaping: 'foo' -> 'foo', "foo'bar" -> 'foo''bar' | |
| return `'${arg.replace(/'/g, "''")}'`; |
| private _spawn(hook: ChatHookCommand, input: unknown, token: CancellationToken): Promise<IHookCommandResult> { | ||
| const cwd = hook.cwd ? uriToFsPath(hook.cwd) : homedir(); | ||
| const command = sanitizeHookCommand(hook.command); | ||
|
|
||
| const child = spawn(hook.command, [], { | ||
| const child = spawn(command, [], { | ||
| stdio: 'pipe', |
There was a problem hiding this comment.
executeCommand logs/errors refer to hookCommand.command, but _spawn now executes a sanitized command string. This can make diagnostics misleading (e.g., failures or timeouts will print the original file:// URI even though the executed command had a filesystem path/quoting). Consider logging/including the sanitized command (or both original + sanitized) anywhere the executed command is surfaced.
| test('quotes sanitized file URIs with spaces in hook commands', async () => { | ||
| const promise = executor.executeCommand( | ||
| cmd('cat file:///tmp/my%20file.txt'), | ||
| undefined, | ||
| CancellationToken.None |
There was a problem hiding this comment.
The new URI-sanitization tests cover spaces and quoting, but don't cover decoded shell metacharacters (e.g., %24 -> $ or %60 -> backtick). Since the sanitized string is executed via spawn(..., { shell: ... }), add a regression test that asserts these characters are escaped/quoted so they cannot trigger shell expansion/command substitution.
| } | ||
|
|
||
| const sanitizedPath = quote ? fsPath : quoteArgForShell(fsPath); | ||
| return `${prefix}${quote}${sanitizedPath}${quote}`; | ||
| }); |
There was a problem hiding this comment.
If the original URI token is already quoted (quote is non-empty), sanitizeHookCommand injects the decoded fsPath without escaping for that quote style. On POSIX, paths can contain " or ', which would break the surrounding quotes (and could change shell parsing). Consider escaping the decoded path for the specific quote character, or always re-quoting with a known-safe quoting routine.
|
@copilot open a new pull request to apply changes based on the comments in this thread |
Fixes microsoft/vscode#304555
Summary
file://tokens in hook command strings before invokingspawnTest plan
NodeHookExecutorunit testsnpm run typecheck