fix(worker): planning launcher — avoid cmd shell to prevent prompt injection
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
// Allowed-tools: --allowedTools (camelCase), comma-separated tokens
|
// Allowed-tools: --allowedTools (camelCase), comma-separated tokens
|
||||||
// System prompt: --append-system-prompt-file <path> (file form)
|
// System prompt: --append-system-prompt-file <path> (file form)
|
||||||
// Session ID: no pre-assign flag; resume with --resume <id>
|
// Session ID: no pre-assign flag; resume with --resume <id>
|
||||||
|
// Launch model: wt.exe directly spawns claude.exe via argv (UseShellExecute=false).
|
||||||
|
// No cmd /k shim — arbitrary initial-prompt content would be re-parsed by cmd.exe otherwise.
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
@@ -38,29 +40,31 @@ public sealed class WindowsTerminalPlanningLauncher : IPlanningTerminalLauncher
|
|||||||
if (resolvedClaude is null)
|
if (resolvedClaude is null)
|
||||||
throw new PlanningLaunchException($"claude executable not found: {_claudePath}");
|
throw new PlanningLaunchException($"claude executable not found: {_claudePath}");
|
||||||
|
|
||||||
var initialPrompt = File.Exists(ctx.Files.InitialPromptPath)
|
|
||||||
? File.ReadAllText(ctx.Files.InitialPromptPath)
|
|
||||||
: string.Empty;
|
|
||||||
|
|
||||||
// Build cmd line: set MAX_THINKING_TOKENS=20000 && claude <args> "prompt"
|
|
||||||
// UseShellExecute=true means we cannot set psi.Environment, so we inject via cmd /k.
|
|
||||||
var claudeArgs = BuildStartArgs(ctx, initialPrompt, resolvedClaude);
|
|
||||||
var cmdLine = $"set MAX_THINKING_TOKENS=20000 && {claudeArgs}";
|
|
||||||
|
|
||||||
var psi = new ProcessStartInfo
|
var psi = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = resolvedWt,
|
FileName = resolvedWt,
|
||||||
UseShellExecute = true,
|
UseShellExecute = false,
|
||||||
WorkingDirectory = ctx.WorkingDir,
|
CreateNoWindow = false,
|
||||||
};
|
};
|
||||||
// wt.exe -d <workingDir> cmd /k "<cmdLine>"
|
|
||||||
|
psi.Environment["MAX_THINKING_TOKENS"] = "20000";
|
||||||
|
|
||||||
psi.ArgumentList.Add("-d");
|
psi.ArgumentList.Add("-d");
|
||||||
psi.ArgumentList.Add(ctx.WorkingDir);
|
psi.ArgumentList.Add(ctx.WorkingDir);
|
||||||
psi.ArgumentList.Add("cmd");
|
psi.ArgumentList.Add(resolvedClaude);
|
||||||
psi.ArgumentList.Add("/k");
|
psi.ArgumentList.Add("--model");
|
||||||
psi.ArgumentList.Add(cmdLine);
|
psi.ArgumentList.Add(Model);
|
||||||
|
psi.ArgumentList.Add("--mcp-config");
|
||||||
|
psi.ArgumentList.Add(ctx.Files.McpConfigPath);
|
||||||
|
psi.ArgumentList.Add("--append-system-prompt-file");
|
||||||
|
psi.ArgumentList.Add(ctx.Files.SystemPromptPath);
|
||||||
|
psi.ArgumentList.Add("--allowedTools");
|
||||||
|
psi.ArgumentList.Add(AllowedTools);
|
||||||
|
psi.ArgumentList.Add(File.ReadAllText(ctx.Files.InitialPromptPath));
|
||||||
|
|
||||||
|
var proc = Process.Start(psi)
|
||||||
|
?? throw new PlanningLaunchException("Failed to start Windows Terminal process.");
|
||||||
|
|
||||||
Process.Start(psi);
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,9 +73,6 @@ public sealed class WindowsTerminalPlanningLauncher : IPlanningTerminalLauncher
|
|||||||
if (!Directory.Exists(ctx.WorkingDir))
|
if (!Directory.Exists(ctx.WorkingDir))
|
||||||
throw new PlanningLaunchException($"Working directory does not exist: {ctx.WorkingDir}");
|
throw new PlanningLaunchException($"Working directory does not exist: {ctx.WorkingDir}");
|
||||||
|
|
||||||
if (!File.Exists(ctx.McpConfigPath))
|
|
||||||
throw new PlanningLaunchException($"MCP config file not found: {ctx.McpConfigPath}");
|
|
||||||
|
|
||||||
var resolvedWt = Resolve(_wtPath);
|
var resolvedWt = Resolve(_wtPath);
|
||||||
if (resolvedWt is null)
|
if (resolvedWt is null)
|
||||||
throw new PlanningLaunchException($"Windows Terminal not found: {_wtPath}");
|
throw new PlanningLaunchException($"Windows Terminal not found: {_wtPath}");
|
||||||
@@ -83,32 +84,24 @@ public sealed class WindowsTerminalPlanningLauncher : IPlanningTerminalLauncher
|
|||||||
var psi = new ProcessStartInfo
|
var psi = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = resolvedWt,
|
FileName = resolvedWt,
|
||||||
UseShellExecute = true,
|
UseShellExecute = false,
|
||||||
WorkingDirectory = ctx.WorkingDir,
|
CreateNoWindow = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
psi.ArgumentList.Add("-d");
|
psi.ArgumentList.Add("-d");
|
||||||
psi.ArgumentList.Add(ctx.WorkingDir);
|
psi.ArgumentList.Add(ctx.WorkingDir);
|
||||||
psi.ArgumentList.Add("cmd");
|
psi.ArgumentList.Add(resolvedClaude);
|
||||||
psi.ArgumentList.Add("/k");
|
psi.ArgumentList.Add("--resume");
|
||||||
psi.ArgumentList.Add(BuildResumeArgs(ctx, resolvedClaude));
|
psi.ArgumentList.Add(ctx.ClaudeSessionId);
|
||||||
|
psi.ArgumentList.Add("--mcp-config");
|
||||||
|
psi.ArgumentList.Add(ctx.McpConfigPath);
|
||||||
|
|
||||||
|
var proc = Process.Start(psi)
|
||||||
|
?? throw new PlanningLaunchException("Failed to start Windows Terminal process.");
|
||||||
|
|
||||||
Process.Start(psi);
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string BuildStartArgs(PlanningSessionStartContext ctx, string initialPrompt, string claudePath)
|
|
||||||
{
|
|
||||||
// Build as a flat string for cmd /k; quote paths that may contain spaces.
|
|
||||||
return $"\"{claudePath}\" --mcp-config \"{ctx.Files.McpConfigPath}\" --append-system-prompt-file \"{ctx.Files.SystemPromptPath}\" --allowedTools \"{AllowedTools}\" --model {Model} \"{EscapeArg(initialPrompt)}\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildResumeArgs(PlanningSessionResumeContext ctx, string claudePath)
|
|
||||||
{
|
|
||||||
return $"\"{claudePath}\" --resume {ctx.ClaudeSessionId} --mcp-config \"{ctx.McpConfigPath}\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string EscapeArg(string value) => value.Replace("\"", "\\\"");
|
|
||||||
|
|
||||||
private static string? Resolve(string pathOrName)
|
private static string? Resolve(string pathOrName)
|
||||||
{
|
{
|
||||||
if (File.Exists(pathOrName))
|
if (File.Exists(pathOrName))
|
||||||
|
|||||||
Reference in New Issue
Block a user