From ba6f96b5a34b86ba02044e28d740e7582b7abc61 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 8 Jun 2026 21:59:52 +0100 Subject: [PATCH 1/3] chore: update dependencies --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index b64336f55..89138e7fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,20 +8,20 @@ "name": "openclaw-windows-node-mxc", "version": "0.0.0", "dependencies": { - "@microsoft/mxc-sdk": "^0.1.8" + "@microsoft/mxc-sdk": "^0.6.1" } }, "node_modules/@microsoft/mxc-sdk": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@microsoft/mxc-sdk/-/mxc-sdk-0.1.8.tgz", - "integrity": "sha512-sjywLhMc/eAnBxauw5Fj+7tXJtvoFKpUjD6++g44vPVauy4wJzWHlw8NmIwIuEWlkkyIXEijdTa+hCU+AqtkDQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@microsoft/mxc-sdk/-/mxc-sdk-0.6.1.tgz", + "integrity": "sha512-jpbJU/xfF4qLWcNMplDTUX/q13m2A6vYao1QN3lkZaQlzsRce95H+iU0Qu0wlweJZ2gx6eY1PRQU+/bnQki/dw==", "license": "MIT", "dependencies": { "node-pty": "^1.2.0-beta.12", "semver": "^7.7.4" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/node-addon-api": { diff --git a/package.json b/package.json index 7646d733e..f15e5a605 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,6 @@ "private": true, "description": "MXC sandbox dependency used by the OpenClaw tray build to copy wxc-exec.exe into the app output.", "dependencies": { - "@microsoft/mxc-sdk": "^0.1.8" + "@microsoft/mxc-sdk": "^0.6.1" } } From b704cf3cb011e207dfdefab2a5ec739b3c039b18 Mon Sep 17 00:00:00 2001 From: conanssam Date: Fri, 5 Jun 2026 12:51:08 +0900 Subject: [PATCH 2/3] fix: import Windows root CA certs into WSL before CLI install In corporate / enterprise environments, HTTPS traffic is often intercepted by a proxy that presents a self-signed or enterprise-issued root certificate. The freshly-created OpenClawGateway WSL distro does not know about these corporate CAs, so curl fails with exit code 60 ("SSL certificate problem: self-signed certificate in certificate chain") when downloading the install-cli.sh script, and setup aborts. Add ImportWindowsCaCertsStep (runs between ValidateWslLockdownStep and InstallCliStep) that: 1. Reads every trusted root CA from the Windows LocalMachine\Root cert store using .NET's X509Store API. 2. Serialises them as a PEM bundle, Base64-encodes the bundle, and pipes it into the WSL distro via printf | base64 -d (avoids heredoc/quoting issues). 3. Writes the bundle to /usr/local/share/ca-certificates/windows-root-ca.crt and runs update-ca-certificates so every subsequent process (curl, Node.js, openssl) trusts the corporate CAs automatically. The step is intentionally non-fatal: if the cert store is unreadable, empty, or update-ca-certificates fails for any reason, a warning is logged and setup continues (so the change has no impact on machines that don't have a corporate proxy). Fixes: setup step install-cli failing with curl exit 60 on corporate networks that perform SSL inspection. Co-Authored-By: Claude Sonnet 4.6 --- src/OpenClaw.SetupEngine/SetupPipeline.cs | 1 + src/OpenClaw.SetupEngine/SetupSteps.cs | 81 +++++++++++++++++++ .../SetupPipelineTests.cs | 8 +- 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/OpenClaw.SetupEngine/SetupPipeline.cs b/src/OpenClaw.SetupEngine/SetupPipeline.cs index b33a132e7..45c372165 100644 --- a/src/OpenClaw.SetupEngine/SetupPipeline.cs +++ b/src/OpenClaw.SetupEngine/SetupPipeline.cs @@ -50,6 +50,7 @@ public static List BuildDefaultSteps() new CreateWslInstanceStep(), new ConfigureWslInstanceStep(), new ValidateWslLockdownStep(), + new ImportWindowsCaCertsStep(), new InstallCliStep(), new ConfigureGatewayStep(), new InstallGatewayServiceStep(), diff --git a/src/OpenClaw.SetupEngine/SetupSteps.cs b/src/OpenClaw.SetupEngine/SetupSteps.cs index 458da89e9..68cdfa59a 100644 --- a/src/OpenClaw.SetupEngine/SetupSteps.cs +++ b/src/OpenClaw.SetupEngine/SetupSteps.cs @@ -1082,6 +1082,87 @@ private static void ValidateConfValue(Dictionary +/// Imports trusted root CA certificates from the Windows certificate store into +/// the WSL distro. This is necessary in corporate environments where HTTPS traffic +/// is inspected by a proxy that uses a self-signed or enterprise-issued root CA, +/// causing curl to fail with exit code 60 (SSL certificate problem: self-signed +/// certificate in certificate chain). +/// +public sealed class ImportWindowsCaCertsStep : SetupStep +{ + public override string Id => "import-windows-ca-certs"; + public override string DisplayName => "Import Windows trusted CA certificates"; + + public override async Task ExecuteAsync(SetupContext ctx, CancellationToken ct) + { + var distro = ctx.DistroName!; + + string pemBundle; + try + { + pemBundle = BuildWindowsCaPemBundle(); + } + catch (Exception ex) + { + ctx.Logger.Warn($"Could not read Windows certificate store: {ex.Message} — skipping CA import"); + return StepResult.Ok("Skipped: could not read Windows certificate store"); + } + + if (string.IsNullOrWhiteSpace(pemBundle)) + { + ctx.Logger.Warn("Windows root CA store returned no certificates — skipping CA import"); + return StepResult.Ok("Skipped: no certificates in Windows root store"); + } + + // Base64-encode the PEM bundle so it can be safely embedded in a bash command + // without heredoc quoting issues. + var pemBase64 = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(pemBundle)); + + var script = $""" + set -e + mkdir -p /usr/local/share/ca-certificates + printf '%s' '{pemBase64}' | base64 -d > /usr/local/share/ca-certificates/windows-root-ca.crt + update-ca-certificates + echo CA_IMPORT_OK + """; + + var result = await ctx.Commands.RunInWslAsync(distro, script, TimeSpan.FromSeconds(60), ct: ct, user: "root"); + + if (result.ExitCode != 0 || !result.Stdout.Contains("CA_IMPORT_OK", StringComparison.Ordinal)) + { + ctx.Logger.Warn($"CA certificate import failed (exit {result.ExitCode}): {result.Stderr.Trim()} — proceeding anyway"); + return StepResult.Ok("CA import encountered errors; setup will continue but network calls may fail in corporate environments"); + } + + ctx.Logger.Info("Windows root CA certificates imported into WSL distro"); + return StepResult.Ok("Imported Windows root CA certificates into WSL distro"); + } + + private static string BuildWindowsCaPemBundle() + { + var sb = new System.Text.StringBuilder(); + using var store = new System.Security.Cryptography.X509Certificates.X509Store( + System.Security.Cryptography.X509Certificates.StoreName.Root, + System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine); + store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadOnly); + + foreach (var cert in store.Certificates) + { + sb.AppendLine("-----BEGIN CERTIFICATE-----"); + sb.AppendLine(Convert.ToBase64String(cert.RawData, Base64FormattingOptions.InsertLineBreaks)); + sb.AppendLine("-----END CERTIFICATE-----"); + } + + return sb.ToString(); + } +} + + // ═══════════════════════════════════════════════════════════════════ // GATEWAY INSTALL STEPS // ═══════════════════════════════════════════════════════════════════ diff --git a/tests/OpenClaw.SetupEngine.Tests/SetupPipelineTests.cs b/tests/OpenClaw.SetupEngine.Tests/SetupPipelineTests.cs index 0e4debd0c..3d28caa4c 100644 --- a/tests/OpenClaw.SetupEngine.Tests/SetupPipelineTests.cs +++ b/tests/OpenClaw.SetupEngine.Tests/SetupPipelineTests.cs @@ -60,14 +60,20 @@ public void BuildDefaultSteps_IncludesCurrentSetupFlow() { var steps = SetupStepFactory.BuildDefaultSteps(); - Assert.Equal(18, steps.Count); + Assert.Equal(19, steps.Count); Assert.IsType(steps[0]); Assert.IsType(steps[1]); Assert.IsType(steps[2]); Assert.IsType(steps[3]); Assert.Contains(steps, s => s is ValidateWslLockdownStep); + Assert.Contains(steps, s => s is ImportWindowsCaCertsStep); + Assert.Contains(steps, s => s is InstallCliStep); Assert.Contains(steps, s => s is RunGatewayWizardStep); Assert.IsType(steps[^1]); + // ImportWindowsCaCertsStep must come immediately before InstallCliStep + var caCertsIdx = steps.FindIndex(s => s is ImportWindowsCaCertsStep); + var installCliIdx = steps.FindIndex(s => s is InstallCliStep); + Assert.Equal(installCliIdx - 1, caCertsIdx); } [Fact] From 29190db68bca81c3ff3bcdc9c81a298c932230d5 Mon Sep 17 00:00:00 2001 From: conanssam Date: Fri, 5 Jun 2026 13:02:52 +0900 Subject: [PATCH 3/3] fix: stream CA bundle via stdin instead of bash -c argument Addresses review finding: the previous approach embedded the full base64-encoded PEM bundle in the bash -c argument passed to wsl.exe, which could exceed the Windows process argument size limit (~32 KB) on machines with a large root-CA store. Changes: - Add stdinInput parameter to ICommandRunner.RunInWslAsync and CommandRunner.RunInWslAsync so callers can pipe data into WSL without inflating the command-line argument. - ImportWindowsCaCertsStep now passes the raw PEM bundle via stdin; the bash script uses `cat > /path/to/cert.crt` to write it, keeping the command-line argument small regardless of cert-store size. - Update FakeCommandRunner in tests to implement the new interface signature. Co-Authored-By: Claude Sonnet 4.6 --- src/OpenClaw.SetupEngine/CommandRunner.cs | 8 +++++--- src/OpenClaw.SetupEngine/SetupSteps.cs | 12 +++++------- tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs | 3 ++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/OpenClaw.SetupEngine/CommandRunner.cs b/src/OpenClaw.SetupEngine/CommandRunner.cs index b3818a35d..e42545f00 100644 --- a/src/OpenClaw.SetupEngine/CommandRunner.cs +++ b/src/OpenClaw.SetupEngine/CommandRunner.cs @@ -24,7 +24,8 @@ Task RunInWslAsync( TimeSpan timeout, IReadOnlyDictionary? environment = null, CancellationToken ct = default, - string? user = null); + string? user = null, + string? stdinInput = null); } public sealed class CommandRunner : ICommandRunner @@ -145,7 +146,8 @@ public Task RunInWslAsync( TimeSpan timeout, IReadOnlyDictionary? environment = null, CancellationToken ct = default, - string? user = null) + string? user = null, + string? stdinInput = null) { // Strip Windows \r to avoid bash "$'\r': command not found" errors command = command.Replace("\r", ""); @@ -171,7 +173,7 @@ public Task RunInWslAsync( : wslEnvKeys; } - return RunAsync("wsl.exe", args.ToArray(), timeout, env, ct: ct); + return RunAsync("wsl.exe", args.ToArray(), timeout, env, stdinInput: stdinInput, ct: ct); } private static void TryKill(Process process) diff --git a/src/OpenClaw.SetupEngine/SetupSteps.cs b/src/OpenClaw.SetupEngine/SetupSteps.cs index 68cdfa59a..1121fb582 100644 --- a/src/OpenClaw.SetupEngine/SetupSteps.cs +++ b/src/OpenClaw.SetupEngine/SetupSteps.cs @@ -1119,19 +1119,17 @@ public override async Task ExecuteAsync(SetupContext ctx, Cancellati return StepResult.Ok("Skipped: no certificates in Windows root store"); } - // Base64-encode the PEM bundle so it can be safely embedded in a bash command - // without heredoc quoting issues. - var pemBase64 = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(pemBundle)); - - var script = $""" + // Stream the PEM bundle via stdin to avoid exceeding Windows process argument + // size limits that would occur if the full bundle were embedded in the bash -c argument. + const string script = """ set -e mkdir -p /usr/local/share/ca-certificates - printf '%s' '{pemBase64}' | base64 -d > /usr/local/share/ca-certificates/windows-root-ca.crt + cat > /usr/local/share/ca-certificates/windows-root-ca.crt update-ca-certificates echo CA_IMPORT_OK """; - var result = await ctx.Commands.RunInWslAsync(distro, script, TimeSpan.FromSeconds(60), ct: ct, user: "root"); + var result = await ctx.Commands.RunInWslAsync(distro, script, TimeSpan.FromSeconds(60), ct: ct, user: "root", stdinInput: pemBundle); if (result.ExitCode != 0 || !result.Stdout.Contains("CA_IMPORT_OK", StringComparison.Ordinal)) { diff --git a/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs b/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs index e6405949d..715962a60 100644 --- a/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs +++ b/tests/OpenClaw.SetupEngine.Tests/SetupStepsTests.cs @@ -1214,7 +1214,8 @@ public Task RunInWslAsync( TimeSpan timeout, IReadOnlyDictionary? environment = null, CancellationToken ct = default, - string? user = null) + string? user = null, + string? stdinInput = null) { if (runInWsl == null) throw new NotSupportedException("RunInWslAsync is not expected in these tests.");