Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/OpenClaw.SetupEngine/SetupContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,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;
Expand All @@ -185,8 +185,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 ───
Expand Down
23 changes: 23 additions & 0 deletions src/OpenClaw.SetupEngine/SetupWizardRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ internal static bool ShouldReplaceOperatorDeviceId(

internal async Task<StepResult> SuspendReloadModeAsync()
{
var invalidUser = RejectInvalidLinuxUser();
if (invalidUser is not null)
return invalidUser;

try
{
// PATH prefix references $PATH. Pipe the script so wsl.exe cannot expand it on argv.
Expand All @@ -128,6 +132,7 @@ internal async Task<StepResult> 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.
// Stdin keeps the username and $PATH out of the wsl.exe argument vector.
ct: CancellationToken.None,
inputViaStdin: true);
if (result.ExitCode != 0)
Expand All @@ -154,6 +159,10 @@ internal async Task<StepResult> SuspendReloadModeAsync()

private async Task<StepResult> 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();

Expand Down Expand Up @@ -645,6 +654,10 @@ private async Task TryCancelWizardAsync(OpenClawGatewayClient client, string ses

internal async Task<StepResult> RestoreReloadModeAsync()
{
var invalidUser = RejectInvalidLinuxUser();
if (invalidUser is not null)
return invalidUser;

var reloadMode = ConfigureGatewayStep.GetEffectiveReloadMode(_ctx.Config.Gateway);
try
{
Expand Down Expand Up @@ -823,6 +836,16 @@ private async Task<CommandResult> 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.TimedOut && result.ExitCode != 0
&& (result.Stdout.Contains(StartupMigrationLeaseDiagnostic, StringComparison.Ordinal)
Expand Down
33 changes: 32 additions & 1 deletion tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4014,6 +4014,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();
Expand Down Expand Up @@ -4361,6 +4362,35 @@ 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(null)]
[InlineData("bad\"user")]
[InlineData("bad$(id)")]
[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! } },
commands);
ctx.DistroName = "test-distro";

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);
}

[Fact]
Expand Down Expand Up @@ -7147,7 +7177,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"));
Expand Down
Loading