From f2f7c33bf480958225a02339aa3241a8806db87f Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Tue, 22 Sep 2026 19:06:40 -0700 Subject: [PATCH 1/4] fix(setup): reject an invalid Linux user before wizard WSL commands Signed-off-by: Sebastien Tardif --- src/OpenClaw.SetupEngine/SetupWizardRunner.cs | 25 +++++++++++++++-- .../SetupStepsTests.cs | 27 ++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/OpenClaw.SetupEngine/SetupWizardRunner.cs b/src/OpenClaw.SetupEngine/SetupWizardRunner.cs index 6c70ec18e..b7a81b591 100644 --- a/src/OpenClaw.SetupEngine/SetupWizardRunner.cs +++ b/src/OpenClaw.SetupEngine/SetupWizardRunner.cs @@ -104,6 +104,10 @@ internal async Task RunWithReloadRestorationAsync( internal async Task SuspendReloadModeAsync() { + var invalidUser = RejectInvalidLinuxUser(); + if (invalidUser is not null) + return invalidUser; + try { var result = await _ctx.Commands.RunInWslAsync( @@ -111,7 +115,9 @@ internal async Task SuspendReloadModeAsync() $"{_ctx.WslPathPrefix} && openclaw config set gateway.reload.mode off", TimeSpan.FromSeconds(15), // This bounded handoff must finish so we know whether restoration is required. - ct: CancellationToken.None); + // Stdin keeps the username and $PATH out of the wsl.exe argument vector. + ct: CancellationToken.None, + inputViaStdin: true); if (result.ExitCode != 0) { return StepResult.Fail( @@ -597,6 +603,10 @@ private async Task TryCancelWizardAsync(OpenClawGatewayClient client, string ses internal async Task RestoreReloadModeAsync() { + var invalidUser = RejectInvalidLinuxUser(); + if (invalidUser is not null) + return invalidUser; + var reloadMode = ConfigureGatewayStep.GetEffectiveReloadMode(_ctx.Config.Gateway); try { @@ -657,7 +667,8 @@ private async Task RunReloadModeRestorationCommandAsync(string re _ctx.DistroName!, command, remaining, - ct: CancellationToken.None); + ct: CancellationToken.None, + inputViaStdin: true); lastResult = result; if (result.ExitCode == 0 || !IsStartupMigrationLeaseContention(result)) @@ -683,6 +694,16 @@ private async Task RunReloadModeRestorationCommandAsync(string re } } + private StepResult? RejectInvalidLinuxUser() + { + var user = _ctx.Config.Wsl.User; + if (WslConfig.IsValidLinuxUserName(user)) + return null; + + return StepResult.Terminal( + $"Invalid WSL user '{user}'. Use a Linux username matching [a-z_][a-z0-9_-]{{0,31}}."); + } + internal static bool IsStartupMigrationLeaseContention(CommandResult result) => result.ExitCode != 0 && (result.Stdout.Contains(StartupMigrationLeaseDiagnostic, StringComparison.Ordinal) diff --git a/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs b/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs index 3a568a07f..64ce86559 100644 --- a/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs +++ b/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs @@ -4245,6 +4245,30 @@ var value when value.Contains("curl -s") => Ok("200"), "config set gateway.reload.mode off", commands.WslCalls[0].Command); Assert.Contains("curl -s", commands.WslCalls[1].Command); + Assert.True(commands.WslCalls[0].InputViaStdin); + } + + [Theory] + [InlineData("bad\"user")] + [InlineData("bad$(id)")] + public async Task SetupWizard_RejectsInvalidLinuxUserBeforeAnyWslCommand(string user) + { + var commands = new FakeCommandRunner( + _ => Ok(), + (_, _, _) => Fail("WSL must not run for an invalid Linux user")); + var ctx = CreateContext( + new SetupConfig { Wsl = new WslConfig { User = user } }, + commands); + ctx.DistroName = "test-distro"; + + var suspend = await new SetupWizardRunner(ctx).SuspendReloadModeAsync(); + var restore = await new SetupWizardRunner(ctx).RestoreReloadModeAsync(); + + Assert.Equal(StepOutcome.FailedTerminal, suspend.Outcome); + Assert.Equal(StepOutcome.FailedTerminal, restore.Outcome); + Assert.Contains("Invalid WSL user", suspend.Message); + Assert.Contains("Invalid WSL user", restore.Message); + Assert.Empty(commands.WslCalls); } [Fact] @@ -6266,7 +6290,8 @@ private static void AssertReloadRestorationCompleted( { Assert.Contains( commands.WslCalls, - call => call.Command.Contains("config set gateway.reload.mode 'hybrid'")); + call => call.Command.Contains("config set gateway.reload.mode 'hybrid'") + && call.InputViaStdin); Assert.Contains( commands.WslCalls, call => call.Command.Contains("openclaw gateway restart")); From f2979e3164608047bb8523570571871cdaf5f717 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Sun, 27 Sep 2026 12:33:05 -0700 Subject: [PATCH 2/4] fix(setup): reject an invalid Linux user before operator pairing The username check ran only when reload mode was suspended or restored. Wizard startup with auto-approval reached the pairing WSL command first. RunCoreAsync now rejects the same invalid user before registry lookup, pairing, or any other wizard command. Tests: SetupWizard_RejectsInvalidLinuxUserBeforeAnyWslCommand passed, 2 of 2. ./build.ps1 exit 0. Shared 4093 passed, 32 skipped. Tray 3059 passed. Five source-contract tests still fail on an LF checkout because they expect CRLF snippets. Signed-off-by: Sebastien Tardif --- src/OpenClaw.SetupEngine/SetupWizardRunner.cs | 4 ++++ tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/OpenClaw.SetupEngine/SetupWizardRunner.cs b/src/OpenClaw.SetupEngine/SetupWizardRunner.cs index b7a81b591..50edc90fd 100644 --- a/src/OpenClaw.SetupEngine/SetupWizardRunner.cs +++ b/src/OpenClaw.SetupEngine/SetupWizardRunner.cs @@ -142,6 +142,10 @@ internal async Task SuspendReloadModeAsync() private async Task RunCoreAsync(CancellationToken ct) { + var invalidUser = RejectInvalidLinuxUser(); + if (invalidUser is not null) + return invalidUser; + var registry = new GatewayRegistry(_ctx.DataDir, logger: new SetupOpenClawLogger(_ctx.Logger)); registry.Load(); diff --git a/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs b/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs index 64ce86559..3b010362f 100644 --- a/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs +++ b/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs @@ -4263,9 +4263,12 @@ public async Task SetupWizard_RejectsInvalidLinuxUserBeforeAnyWslCommand(string var suspend = await new SetupWizardRunner(ctx).SuspendReloadModeAsync(); var restore = await new SetupWizardRunner(ctx).RestoreReloadModeAsync(); + var run = await new SetupWizardRunner(ctx).RunAsync(CancellationToken.None); Assert.Equal(StepOutcome.FailedTerminal, suspend.Outcome); Assert.Equal(StepOutcome.FailedTerminal, restore.Outcome); + Assert.Equal(StepOutcome.FailedTerminal, run.Outcome); + Assert.Contains("Invalid WSL user", run.Message); Assert.Contains("Invalid WSL user", suspend.Message); Assert.Contains("Invalid WSL user", restore.Message); Assert.Empty(commands.WslCalls); From 37f4448b3669b6932964b3e26994d08bee1289b2 Mon Sep 17 00:00:00 2001 From: Scott Hanselman Date: Mon, 28 Sep 2026 11:22:39 -0700 Subject: [PATCH 3/4] fix(setup): harden Linux username validation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ac1f11f-ab65-454b-b841-11c6cafe7381 --- src/OpenClaw.SetupEngine/SetupContext.cs | 7 ++++--- tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/OpenClaw.SetupEngine/SetupContext.cs b/src/OpenClaw.SetupEngine/SetupContext.cs index aad8f82dd..6ce81382b 100644 --- a/src/OpenClaw.SetupEngine/SetupContext.cs +++ b/src/OpenClaw.SetupEngine/SetupContext.cs @@ -158,7 +158,7 @@ public sealed class LocalAiConfig public sealed class WslConfig { private static readonly System.Text.RegularExpressions.Regex s_linuxUserNamePattern = - new("^[a-z_][a-z0-9_-]{0,31}$", System.Text.RegularExpressions.RegexOptions.Compiled); + new(@"\A[a-z_][a-z0-9_-]{0,31}\z", System.Text.RegularExpressions.RegexOptions.Compiled); public string User { get; set; } = "openclaw"; public bool Systemd { get; set; } = true; @@ -170,8 +170,9 @@ public sealed class WslConfig public string? Memory { get; set; } public string? Swap { get; set; } - public static bool IsValidLinuxUserName(string value) - => s_linuxUserNamePattern.IsMatch(value); + public static bool IsValidLinuxUserName(string? value) + => !string.IsNullOrWhiteSpace(value) + && s_linuxUserNamePattern.IsMatch(value); } // ─── Gateway Configuration ─── diff --git a/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs b/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs index ce9d4647c..218ce621b 100644 --- a/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs +++ b/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs @@ -3898,6 +3898,7 @@ private static int GetFreeTcpPort() [InlineData("BadUser")] [InlineData("bad user")] [InlineData("bad$user")] + [InlineData("openclaw\n")] public async Task ConfigureWsl_RejectsInvalidLinuxUserName(string user) { var ctx = CreateContext(); @@ -4249,15 +4250,17 @@ var value when value.Contains("curl -s") => Ok("200"), } [Theory] + [InlineData(null)] [InlineData("bad\"user")] [InlineData("bad$(id)")] - public async Task SetupWizard_RejectsInvalidLinuxUserBeforeAnyWslCommand(string user) + [InlineData("openclaw\n")] + public async Task SetupWizard_RejectsInvalidLinuxUserBeforeAnyWslCommand(string? user) { var commands = new FakeCommandRunner( _ => Ok(), (_, _, _) => Fail("WSL must not run for an invalid Linux user")); var ctx = CreateContext( - new SetupConfig { Wsl = new WslConfig { User = user } }, + new SetupConfig { Wsl = new WslConfig { User = user! } }, commands); ctx.DistroName = "test-distro"; From f4006f0c957863948089b10e15e60e56aa516c83 Mon Sep 17 00:00:00 2001 From: Scott Hanselman Date: Mon, 28 Sep 2026 12:35:38 -0700 Subject: [PATCH 4/4] chore: refresh setup validation after restart fix Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ac1f11f-ab65-454b-b841-11c6cafe7381