diff --git a/src/OpenClaw.SetupEngine/ConfigureGatewayStep.cs b/src/OpenClaw.SetupEngine/ConfigureGatewayStep.cs index ae72546eb..98144c656 100644 --- a/src/OpenClaw.SetupEngine/ConfigureGatewayStep.cs +++ b/src/OpenClaw.SetupEngine/ConfigureGatewayStep.cs @@ -125,10 +125,10 @@ internal static string BuildConfigCommands( openclaw plugins registry --refresh openclaw config set gateway.mode local openclaw config set gateway.port {port} - openclaw config set gateway.bind {gw.Bind} - openclaw config set gateway.auth.mode {gw.AuthMode} + openclaw config set gateway.bind {WslShellQuoting.QuotePosixSingleQuote(gw.Bind)} + openclaw config set gateway.auth.mode {WslShellQuoting.QuotePosixSingleQuote(gw.AuthMode)} openclaw config set gateway.auth.token "$OPENCLAW_GATEWAY_TOKEN" - openclaw config set gateway.reload.mode {gw.ReloadMode} + openclaw config set gateway.reload.mode {WslShellQuoting.QuotePosixSingleQuote(gw.ReloadMode)} openclaw config set {nodeCommandsAllowKey} {escapedAllowedCommands} """; diff --git a/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs b/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs index 00bfe8659..c9a7d75c7 100644 --- a/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs +++ b/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs @@ -4102,7 +4102,7 @@ public void ConfigureGateway_DefaultsReloadModeToHybrid() 18789, "'[]'"); - Assert.Contains("openclaw config set gateway.reload.mode hybrid", commands); + Assert.Contains("openclaw config set gateway.reload.mode 'hybrid'", commands); } [Fact] @@ -5165,6 +5165,111 @@ public void ConfigureGateway_QuotesExtraConfigValueWithEmbeddedSingleQuote() commands); } + // Bind is allowlisted by ExecuteAsync; the builder also quotes it defensively. + [Fact] + public async Task ConfigureGateway_QuotesBindAndPipesScriptThroughStdin() + { + const string bind = "lan;$PATH"; + var commands = ConfigureGatewayStep.BuildConfigCommands( + new GatewayConfig + { + Bind = bind, + AuthMode = "token;$(id)", + ReloadMode = "hybrid;$PATH", + }, + 18789, + "'[]'"); + + Assert.Contains("openclaw config set gateway.bind 'lan;$PATH'", commands); + Assert.Contains("openclaw config set gateway.auth.mode 'token;$(id)'", commands); + Assert.Contains("openclaw config set gateway.reload.mode 'hybrid;$PATH'", commands); + Assert.DoesNotContain("openclaw config set gateway.bind lan;$PATH", commands); + + var runner = new FakeCommandRunner( + _ => Ok(), + (_, _, _) => Ok("GATEWAY_CONFIGURED")); + var ctx = CreateContext( + new SetupConfig + { + Gateway = new GatewayConfig { Bind = "loopback" }, + }, + runner); + ctx.DistroName = "test-distro"; + + var result = await new ConfigureGatewayStep().ExecuteAsync(ctx, CancellationToken.None); + + Assert.True(result.IsSuccess, result.Message); + var call = Assert.Single(runner.WslCalls); + Assert.True(call.InputViaStdin); + Assert.Contains("$PATH", call.Command); + Assert.Contains("openclaw config set gateway.bind 'loopback'", call.Command); + } + + // Exercise the direct properties, not the already-quoted ExtraConfig override. + [Theory] + [InlineData("token", "off", "'token'", "'off'")] + [InlineData("token';$(id); echo PWNED", "hybrid", "'token'\\'';$(id); echo PWNED'", "'hybrid'")] + [InlineData("token", "hybrid';$PATH; echo PWNED", "'token'", "'hybrid'\\'';$PATH; echo PWNED'")] + public async Task ConfigureGateway_ExecuteAsyncQuotesDirectAuthAndReloadModes( + string authMode, + string reloadMode, + string quotedAuthMode, + string quotedReloadMode) + { + var safeRunner = new FakeCommandRunner( + _ => Ok(), + (_, _, _) => Ok("GATEWAY_CONFIGURED")); + var safeResult = await new ConfigureGatewayStep().ExecuteAsync( + CreateGatewayModesContext(safeRunner, "token", "hybrid"), + CancellationToken.None); + Assert.True(safeResult.IsSuccess, safeResult.Message); + var safeCall = Assert.Single(safeRunner.WslCalls); + + var runner = new FakeCommandRunner( + _ => Ok(), + (_, _, _) => Ok("GATEWAY_CONFIGURED")); + var result = await new ConfigureGatewayStep().ExecuteAsync( + CreateGatewayModesContext(runner, authMode, reloadMode), + CancellationToken.None); + + Assert.True(result.IsSuccess, result.Message); + var call = Assert.Single(runner.WslCalls); + Assert.True(call.InputViaStdin); + Assert.Contains($"openclaw config set gateway.auth.mode {quotedAuthMode}", call.Command); + Assert.Contains($"openclaw config set gateway.reload.mode {quotedReloadMode}", call.Command); + Assert.Equal( + safeCall.Command + .Replace( + "openclaw config set gateway.auth.mode 'token'", + $"openclaw config set gateway.auth.mode {quotedAuthMode}", + StringComparison.Ordinal) + .Replace( + "openclaw config set gateway.reload.mode 'hybrid'", + $"openclaw config set gateway.reload.mode {quotedReloadMode}", + StringComparison.Ordinal), + call.Command); + } + + private SetupContext CreateGatewayModesContext( + ICommandRunner runner, + string authMode, + string reloadMode) + { + var ctx = CreateContext( + new SetupConfig + { + Gateway = new GatewayConfig + { + Bind = "loopback", + AuthMode = authMode, + ReloadMode = reloadMode, + }, + }, + runner); + ctx.DistroName = "test-distro"; + return ctx; + } + [Fact] public async Task ConfigureGateway_UsesExtendedTimeoutForWslConfig() {