Skip to content

fix: restart Windows gateway via Scheduled Task#38825

Merged
obviyus merged 1 commit intomainfrom
fix/windows-gateway-schtasks-restart
Mar 7, 2026
Merged

fix: restart Windows gateway via Scheduled Task#38825
obviyus merged 1 commit intomainfrom
fix/windows-gateway-schtasks-restart

Conversation

@obviyus
Copy link
Copy Markdown
Contributor

@obviyus obviyus commented Mar 7, 2026

Summary

  • detect restart-capable supervisors per platform instead of treating all service markers as equivalent
  • relaunch Windows gateway restarts through Scheduled Task when task-managed
  • fall back to in-process restart on Windows when task markers are missing

Why

#38692 avoids the orphaned child, but this fixes the root cause: Windows had no explicit Scheduled Task restart path.

Closes #38508

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 7, 2026

🔒 Aisle Security Analysis

We found 3 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Local privilege escalation via insecure temp .cmd helper for Windows scheduled-task relaunch
2 🟡 Medium Arbitrary Scheduled Task execution via OPENCLAW_WINDOWS_TASK_NAME override
3 🔵 Low Windows command injection via unsafely-escaped scheduled task name in generated .cmd restart helper

1. 🟠 Local privilege escalation via insecure temp .cmd helper for Windows scheduled-task relaunch

Property Value
Severity High
CWE CWE-377
Location src/infra/windows-task-restart.ts:41-52

Description

The Windows Scheduled Task relaunch helper writes a batch script into a temp directory and then executes it via a detached cmd.exe.

If the gateway runs as a privileged account (e.g., Scheduled Task as SYSTEM or an admin) and resolvePreferredOpenClawTmpDir() resolves to a shared/user-writable temp location (common on Windows for services/tasks, e.g. C:\Windows\Temp), an unprivileged local attacker can:

  • Monitor the temp directory for newly-created openclaw-schtasks-restart-<uuid>.cmd files (UUID is unpredictable but discoverable via filesystem notifications)
  • Modify/replace the script contents in the window between writeFileSync() and cmd.exe execution (TOCTOU)
  • Achieve arbitrary command execution in the context of the privileged task (local privilege escalation)

This is a classic “temporary executable script in shared temp” issue.

Vulnerable code:

const scriptPath = path.join(
  resolvePreferredOpenClawTmpDir(),
  `openclaw-schtasks-restart-${randomUUID()}.cmd`,
);
...
fs.writeFileSync(scriptPath, `${buildScheduledTaskRestartScript(taskName)}\r\n`, "utf8");
const child = spawn("cmd.exe", ["/d", "/s", "/c", quotedScriptPath], {
  detached: true,
  stdio: "ignore",
  windowsHide: true,
});

Notes:

  • The batch file also self-deletes (del "%~f0"); if an attacker can swap the file to a link/reparse-point target before cleanup, that may also enable deletion of an unintended file.

Recommendation

Avoid writing an executable script into a potentially shared temp directory.

Preferred fixes (pick one):

  1. Remove the .cmd file entirely and implement the retry loop in-process:
import { spawnSync } from "node:child_process";

for (let i = 0; i < TASK_RESTART_RETRY_LIMIT; i++) {
  spawnSync("schtasks", ["/Run", "/TN", taskName], { stdio: "ignore" });// exit early on success (check status/error)
  await new Promise(r => setTimeout(r, TASK_RESTART_RETRY_DELAY_SEC * 1000));
}

This keeps the operation in the same process and avoids a writable-on-disk executable artifact.

  1. If you must keep a detached helper, write it into a directory with a restricted ACL that is not writable by other users (e.g., under %ProgramData%\\OpenClaw\\tmp created once with a locked-down DACL). Do not rely on POSIX mode on Windows.

Additional hardening (defense-in-depth):

  • Create the file with an exclusive flag to reduce clobbering/link attacks:
fs.writeFileSync(scriptPath, contents, { encoding: "utf8", flag: "wx" });
  • Ensure the temp directory is not a symlink/junction/reparse-point and is not writable by non-admin users before using it (Windows-specific ACL checks).

2. 🟡 Arbitrary Scheduled Task execution via OPENCLAW_WINDOWS_TASK_NAME override

Property Value
Severity Medium
CWE CWE-284
Location src/infra/windows-task-restart.ts:13-31

Description

The new Windows restart implementation (relaunchGatewayScheduledTask) takes the Scheduled Task name from the environment variable OPENCLAW_WINDOWS_TASK_NAME and passes it to schtasks /Run /TN.

  • Input: env.OPENCLAW_WINDOWS_TASK_NAME is accepted verbatim (after trim()) and can override the computed gateway task name.
  • Sink: A helper .cmd script is generated that executes schtasks /Run /TN <taskName>.
  • Impact: Any actor who can influence the gateway process environment (including via config-driven env var injection, service wrappers, or other local env manipulation) can cause a restart to run an arbitrary pre-existing Scheduled Task accessible to the gateway’s security context. If the gateway runs with elevated privileges, this becomes a confused-deputy vector that may enable execution of tasks with higher privileges than the attacker.

Vulnerable code:

const override = env.OPENCLAW_WINDOWS_TASK_NAME?.trim();
if (override) {
  return override;
}
...
`schtasks /Run /TN ${quotedTaskName} >nul 2>&1`,

Recommendation

Constrain the task name used for restarts so it cannot be redirected to unrelated tasks.

Recommended options (choose one):

  1. Remove the override for restart (keep it only for install/diagnostics), and always restart the expected canonical task name derived from profile.

  2. Allowlist validation: only accept OPENCLAW_WINDOWS_TASK_NAME if it matches the expected name(s) exactly (canonical + any explicitly supported legacy names), otherwise ignore it.

Example strict validation:

import { resolveGatewayWindowsTaskName } from "../daemon/constants.js";

function resolveWindowsTaskName(env: NodeJS.ProcessEnv): string {
  const expected = resolveGatewayWindowsTaskName(env.OPENCLAW_PROFILE);
  const override = env.OPENCLAW_WINDOWS_TASK_NAME?.trim();
  if (override && override === expected) {
    return override;
  }
  return expected;
}
  1. Bind restart to the installed task: query the task definition first and verify its action points to the expected OpenClaw script path before calling /Run.

Additionally, consider rejecting task names containing task-folder prefixes (e.g. starting with \\) if you do not intend to support running tasks outside your own namespace.


3. 🔵 Windows command injection via unsafely-escaped scheduled task name in generated .cmd restart helper

Property Value
Severity Low
CWE CWE-78
Location src/infra/windows-task-restart.ts:21-33

Description

The scheduled-task restart helper writes a .cmd file containing a schtasks command where the task name is derived from environment (OPENCLAW_WINDOWS_TASK_NAME or OPENCLAW_PROFILE) and is inserted using quoteCmdScriptArg().

quoteCmdScriptArg() is not safe for embedding attacker-controlled strings into a batch script command:

  • It does not escape CMD metacharacters such as & | ( ) < > ^.
  • It attempts to escape quotes using backslash (\"), but CMD does not treat backslash as an escape for ", so an embedded " can terminate the quoted argument.

As a result, an attacker who can influence OPENCLAW_WINDOWS_TASK_NAME (or a crafted profile value that flows into the task name) can break out of the /TN "..." argument and execute arbitrary commands when a restart is triggered.

Vulnerable code:

const quotedTaskName = quoteCmdScriptArg(taskName);
...
`schtasks /Run /TN ${quotedTaskName} >nul 2>&1`,

Example exploit value:

  • OPENCLAW_WINDOWS_TASK_NAME=OpenClaw Gateway" & whoami & "

This produces a script line that will execute whoami via & command chaining.

Recommendation

Do not embed potentially-variable strings directly into batch script command lines unless you use CMD-correct escaping/quoting.

Safer options:

  1. Prefer allowlisting/validation for task names (recommended): reject any task name containing CMD metacharacters or quotes.
function assertSafeTaskName(name: string): void {// allow: letters, numbers, space, dash, underscore, dot, parentheses
  if (!/^[A-Za-z0-9 _.\-()]+$/.test(name)) {
    throw new Error("Unsafe Windows task name");
  }
}

function buildScheduledTaskRestartScript(taskName: string): string {
  assertSafeTaskName(taskName);
  return [
    "@​echo off",
    "setlocal",
    `schtasks /Run /TN "${taskName}" >nul 2>&1`,
    ...
  ].join("\r\n");
}
  1. If you must support arbitrary task names, implement CMD batch escaping correctly (caret-escaping for &|()<>^ and ^" for quotes, plus % -> %%, and consider disabling delayed expansion).

Additionally, add tests that execute the generated .cmd under cmd.exe in integration tests to confirm metacharacter safety.


Analyzed PR: #38825 at commit a55cd45

Last updated on: 2026-03-07T13:48:22Z

@openclaw-barnacle openclaw-barnacle bot added gateway Gateway runtime cli CLI command changes size: M maintainer Maintainer-authored PR labels Mar 7, 2026
@obviyus obviyus self-assigned this Mar 7, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This PR fixes Windows gateway restarts by routing them through schtasks /Run via a self-deleting .cmd helper script instead of a detached child process (which would become orphaned on Windows). It also refactors supervisor detection from a single flat env-var list into per-platform typed detection (detectRespawnSupervisor), ensuring that launchd, systemd, and schtasks are each identified precisely, and that Windows without Scheduled Task markers correctly falls back to in-process restart rather than attempting an unsafe detached spawn.

Key changes:

  • New src/infra/windows-task-restart.ts: writes a retry-loop .cmd script to os.tmpdir(), spawns it detached via cmd.exe /d /c, and unrefs the child — the parent process can then exit cleanly and the task is restarted after a short delay.
  • src/infra/supervisor-markers.ts: replaces hasSupervisorHint with detectRespawnSupervisor(env, platform), routing each OS to its own detection logic.
  • src/infra/restart.ts: flattens the previously nested platform checks and delegates win32 to relaunchGatewayScheduledTask.
  • src/daemon/service-env.ts: injects OPENCLAW_WINDOWS_TASK_NAME into the gateway environment so the task name is always available without needing to re-derive it.
  • Concern: OPENCLAW_TASK_SCRIPT and OPENCLAW_TASK_SCRIPT_NAME are node-service env vars that are included in WINDOWS_TASK_SUPERVISOR_HINT_ENV_VARS. If a gateway process inherits a node-service environment, detectRespawnSupervisor would return "schtasks" and attempt a restart using the gateway task name, which may not exist, leaving the gateway dead after exit.
  • Minor: The tried log entry for the helper script path is not quoted, which is misleading when os.tmpdir() contains spaces (common on Windows).

Confidence Score: 4/5

  • Safe to merge; the core Windows Scheduled Task restart path is well-tested and the fallback behaviour is correct.
  • The implementation is logically sound and backed by new test coverage. The main deduction is the inclusion of node-service-specific env vars (OPENCLAW_TASK_SCRIPT, OPENCLAW_TASK_SCRIPT_NAME) in the gateway's Windows supervisor detection, which creates a cross-service coupling risk. The UTF-8-without-BOM script encoding and the unquoted path in the tried log are minor but worth addressing.
  • src/infra/supervisor-markers.ts — the WINDOWS_TASK_SUPERVISOR_HINT_ENV_VARS list includes node-service-specific vars; src/infra/windows-task-restart.ts — script encoding and tried-log quoting.

Last reviewed commit: 5bcb017

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e56c6f02af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1e169fbf67

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +41 to +45
const scriptPath = path.join(
resolvePreferredOpenClawTmpDir(),
`openclaw-schtasks-restart-${randomUUID()}.cmd`,
);
const quotedScriptPath = quoteCmdScriptArg(scriptPath);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wrap temp script path derivation in restart error handling

relaunchGatewayScheduledTask computes scriptPath (and quotes it) before entering the try block, so failures from resolvePreferredOpenClawTmpDir() or quoteCmdScriptArg(scriptPath) escape as uncaught exceptions instead of returning { ok: false }. On Windows hosts with an unsafe/uncreatable temp dir, this propagates through triggerOpenClawRestart() and prevents the caller from taking its normal fallback path, which can break restart handling rather than reporting a controlled restart failure.

Useful? React with 👍 / 👎.

@obviyus obviyus force-pushed the fix/windows-gateway-schtasks-restart branch from 1e169fb to a55cd45 Compare March 7, 2026 12:30
@obviyus obviyus merged commit 05c240f into main Mar 7, 2026
28 checks passed
@obviyus obviyus deleted the fix/windows-gateway-schtasks-restart branch March 7, 2026 12:30
@obviyus
Copy link
Copy Markdown
Contributor Author

obviyus commented Mar 7, 2026

Landed via temp rebase onto main.

  • Gate: pnpm check && pnpm build && pnpm test
  • Land commit: a55cd45
  • Merge commit: 05c240f

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a55cd45b1d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +54 to +57
return {
ok: true,
method: "schtasks",
tried: [`schtasks /Run /TN "${taskName}"`, `cmd.exe /d /s /c ${quotedScriptPath}`],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Verify Scheduled Task before reporting restart success

relaunchGatewayScheduledTask reports ok: true immediately after spawning detached cmd.exe, but the real schtasks /Run call happens later inside the helper script and can still fail (for example, when the task name is stale/renamed or the task was removed). In that failure mode restartGatewayProcessWithFreshPid() treats the restart as supervised and runGatewayLoop exits, so the gateway can go down without taking the in-process fallback path. Please gate success on a synchronous task probe (e.g. /Query or an immediate /Run check) before returning success.

Useful? React with 👍 / 👎.

mcaxtr pushed a commit to mcaxtr/openclaw that referenced this pull request Mar 7, 2026
vincentkoc pushed a commit to BryanTegomoh/openclaw-fork that referenced this pull request Mar 8, 2026
Saitop pushed a commit to NomiciAI/openclaw that referenced this pull request Mar 8, 2026
GordonSH-oss pushed a commit to GordonSH-oss/openclaw that referenced this pull request Mar 9, 2026
jenawant pushed a commit to jenawant/openclaw that referenced this pull request Mar 10, 2026
dhoman pushed a commit to dhoman/chrono-claw that referenced this pull request Mar 11, 2026
senw-developers pushed a commit to senw-developers/va-openclaw that referenced this pull request Mar 17, 2026
V-Gutierrez pushed a commit to V-Gutierrez/openclaw-vendor that referenced this pull request Mar 17, 2026
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 20, 2026
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes gateway Gateway runtime maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] SIGUSR1 gateway restart on Windows Scheduled Task creates orphaned process — gateway stays down permanently

1 participant