Laufzeiten: die MCP-Bruecke ist auch ohne Starter in der Sandbox (#712)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a76983aa28
commit
7a8852fe6b
@@ -101,7 +101,7 @@ public sealed class ClaudeCodeRuntime(Func<ClaudeCodeSettings> settings) : IFore
|
|||||||
["CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"] = "1"
|
["CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"] = "1"
|
||||||
};
|
};
|
||||||
|
|
||||||
var readOnly = new List<string>(config.ReadOnlyPaths);
|
var readOnly = new List<string>(config.ReadOnlyPaths.Concat(context.HelperPaths()));
|
||||||
var directory = Path.GetDirectoryName(executable)!;
|
var directory = Path.GetDirectoryName(executable)!;
|
||||||
if (!directory.StartsWith("/usr/", StringComparison.Ordinal))
|
if (!directory.StartsWith("/usr/", StringComparison.Ordinal))
|
||||||
readOnly.Add(directory);
|
readOnly.Add(directory);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public sealed class CommandRuntime : IForeignRuntime
|
|||||||
File.WriteAllText(Path.Combine(context.PrivateDirectory, ForeignRunContext.BriefingFileName), context.Briefing);
|
File.WriteAllText(Path.Combine(context.PrivateDirectory, ForeignRunContext.BriefingFileName), context.Briefing);
|
||||||
|
|
||||||
var command = runtime.Command.Trim();
|
var command = runtime.Command.Trim();
|
||||||
var readOnly = new List<string>();
|
var readOnly = new List<string>(context.HelperPaths());
|
||||||
if (Path.IsPathRooted(command))
|
if (Path.IsPathRooted(command))
|
||||||
{
|
{
|
||||||
var directory = Path.GetDirectoryName(command)!;
|
var directory = Path.GetDirectoryName(command)!;
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ public sealed record ForeignRunContext
|
|||||||
return mounts;
|
return mounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What must be visible read-only for the MCP bridge: the application. Mounted whenever
|
||||||
|
/// there is MCP — not only when the launcher runs, which needs network or secrets.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string> HelperPaths() => HasMcp ? Helper!.ReadOnlyPaths : [];
|
||||||
|
|
||||||
/// <summary>What a program in the sandbox needs to reach the MCP server itself.</summary>
|
/// <summary>What a program in the sandbox needs to reach the MCP server itself.</summary>
|
||||||
public IReadOnlyDictionary<string, string> McpEnvironment()
|
public IReadOnlyDictionary<string, string> McpEnvironment()
|
||||||
{
|
{
|
||||||
@@ -72,6 +78,8 @@ public sealed record ForeignRunContext
|
|||||||
environment["CLAWD_MCP_SOCKET"] = McpSocketInSandbox;
|
environment["CLAWD_MCP_SOCKET"] = McpSocketInSandbox;
|
||||||
environment[Mcp.McpStdioBridge.TokenFileVariable] = Private + "/" + TokenFileName;
|
environment[Mcp.McpStdioBridge.TokenFileVariable] = Private + "/" + TokenFileName;
|
||||||
environment["CLAWD_HELPER"] = Helper!.Executable;
|
environment["CLAWD_HELPER"] = Helper!.Executable;
|
||||||
|
foreach (var (key, value) in Helper.Environment)
|
||||||
|
environment[key] = value;
|
||||||
}
|
}
|
||||||
return environment;
|
return environment;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ public sealed class BwrapSandbox : ISandboxRunner
|
|||||||
// #712: what the launcher needs — the helper itself, the proxy's socket, the secrets.
|
// #712: what the launcher needs — the helper itself, the proxy's socket, the secrets.
|
||||||
if (request.NeedsLauncher && helper is not null)
|
if (request.NeedsLauncher && helper is not null)
|
||||||
{
|
{
|
||||||
foreach (var path in helper.ReadOnlyPaths)
|
foreach (var path in helper.ReadOnlyPaths.Except(request.ReadOnlyPaths.Select(Path.GetFullPath)))
|
||||||
args.AddRange(["--ro-bind", path, path]);
|
args.AddRange(["--ro-bind", path, path]);
|
||||||
if (launch.EgressSocket is { } socket)
|
if (launch.EgressSocket is { } socket)
|
||||||
args.AddRange(["--bind", socket, SandboxPaths.EgressSocket]);
|
args.AddRange(["--bind", socket, SandboxPaths.EgressSocket]);
|
||||||
|
|||||||
@@ -452,6 +452,20 @@ public sealed class ForeignRuntimeTests : IDisposable
|
|||||||
sandbox.Request.Environment["CLAWD_TASK_ID"].ShouldBe("t-7");
|
sandbox.Request.Environment["CLAWD_TASK_ID"].ShouldBe("t-7");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void With_mcp_the_bridge_is_there_even_without_the_launcher()
|
||||||
|
{
|
||||||
|
// No network, no secrets — no launcher. The command still calls $CLAWD_HELPER.
|
||||||
|
_agent.Runtime = new AgentRuntimeConfig { Kind = RuntimeKinds.Command, Command = "/bin/sh" };
|
||||||
|
|
||||||
|
var request = new CommandRuntime().Prepare(Context());
|
||||||
|
|
||||||
|
request.NeedsLauncher.ShouldBeFalse();
|
||||||
|
request.ReadOnlyPaths.ShouldContain("/opt/clawd");
|
||||||
|
request.Environment["CLAWD_HELPER"].ShouldBe("/opt/clawd/ClawdDotNet");
|
||||||
|
request.Environment["DOTNET_ROOT"].ShouldBe("/usr/lib/dotnet");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task A_failing_command_is_a_failed_run_with_its_reason()
|
public async Task A_failing_command_is_a_failed_run_with_its_reason()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ public sealed class McpSocketTests : IDisposable
|
|||||||
"""m1='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'""",
|
"""m1='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'""",
|
||||||
"""m2='{"jsonrpc":"2.0","id":2,"method":"tools/list"}'""",
|
"""m2='{"jsonrpc":"2.0","id":2,"method":"tools/list"}'""",
|
||||||
"""m3='{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"Echo","arguments":{}}}'""",
|
"""m3='{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"Echo","arguments":{}}}'""",
|
||||||
"""printf '%s\n' "$m1" "$m2" "$m3" | "$CLAWD_HELPER" --mcp-bridge --socket "$CLAWD_MCP_SOCKET" """,
|
"""printf '%s\n' "$m1" "$m2" "$m3" | "$CLAWD_HELPER" --mcp-bridge --socket "$CLAWD_MCP_SOCKET" 2>&1""",
|
||||||
"echo done > /work/result.txt");
|
"echo done > /work/result.txt");
|
||||||
var agent = new AgentConfig
|
var agent = new AgentConfig
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user