Skip to content

Fix setup TUI hatch terminal handoff#69524

Merged
shakkernerd merged 8 commits intomainfrom
fix/tui-hatch-tty
Apr 21, 2026
Merged

Fix setup TUI hatch terminal handoff#69524
shakkernerd merged 8 commits intomainfrom
fix/tui-hatch-tty

Conversation

@shakkernerd
Copy link
Copy Markdown
Member

@shakkernerd shakkernerd commented Apr 21, 2026

Summary

  • launch setup hatch TUI in a fresh openclaw tui child process instead of reusing the onboarding process
  • restore terminal state before and after the handoff
  • keep the setup hatch safety behavior: no auto-delivery to last provider/recipient

Why

Choosing Hatch in TUI from onboarding could leave the setup prompt and TUI sharing the same terminal/input state. In that state, normal typing could inject raw terminal escape sequences into the TUI input area and the setup prompt could remain visually mixed with the TUI.

Before

image

The TUI could show raw terminal input such as ^[[104;1:3u... after hatch, even though the TUI had connected.

After

image

The hatch flow starts a fresh TUI process, so the terminal belongs cleanly to the TUI and normal typing stays normal.

Verification

  • pnpm vitest run src/wizard/setup.finalize.test.ts src/wizard/setup.test.ts
  • pnpm check
  • manually reproduced the broken flow on main
  • manually verified the fixed flow on this branch

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 21, 2026

🔒 Aisle Security Analysis

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

# Severity Title
1 🟠 High Potential local file execution when relaunching TUI via process.execPath + relative entry
2 🟠 High Node child TUI inherits NODE_OPTIONS / execArgv enabling arbitrary code loading
3 🟡 Medium Gateway token/password exposed via child-process command-line arguments when relaunching TUI
1. 🟠 Potential local file execution when relaunching TUI via `process.execPath` + relative entry
Property Value
Severity High
CWE CWE-426
Location src/tui/tui-launch.ts:53-63

Description

launchTuiCli() respawns a child process using spawn(process.execPath, args, ...). The relaunch argv is built as:

  • buildCurrentCliEntryArgs() returns [] when process.argv[1] is not an absolute path
  • buildTuiCliArgs() then appends the literal string "tui" as the next argument

If process.execPath is the Node.js binary (common when running from source / via node ./openclaw.mjs), and process.argv[1] is relative (e.g., ./openclaw.mjs), the child process command becomes effectively:

  • node tui ...

In Node, the first non-flag argument is treated as the script path to execute. That makes the relaunch execute a file named tui resolved relative to the current working directory. If an attacker can cause execution from a directory containing an attacker-controlled ./tui file (e.g., a malicious repo), this can lead to unintended code execution.

Vulnerable code:

function buildCurrentCliEntryArgs(): string[] {
  const entry = process.argv[1]?.trim();
  if (!entry) {
    throw new Error("unable to relaunch TUI: current CLI entry path is unavailable");
  }
  return path.isAbsolute(entry) ? [entry] : [];
}

function buildTuiCliArgs(opts: TuiOptions): string[] {
  const args = [...filterTuiExecArgv(process.execArgv), ...buildCurrentCliEntryArgs(), "tui"];// ...
}

Recommendation

Ensure the respawn always invokes the intended CLI entry file when process.execPath is Node, even if process.argv[1] is relative.

Safer options:

  1. Resolve relative entry to an absolute path (preferred):
function buildCurrentCliEntryArgs(): string[] {
  const entry = process.argv[1]?.trim();
  if (!entry) {
    throw new Error("unable to relaunch TUI: current CLI entry path is unavailable");
  }// Resolve relative paths against the current working directory.
  return [path.isAbsolute(entry) ? entry : path.resolve(entry)];
}
  1. If compiled/bundled mode is expected to have no entry script, then explicitly detect non-Node executables before omitting the entry path. For example, only allow [] when you can positively identify process.execPath is the packaged OpenClaw binary.

Also consider setting an explicit cwd for the child (e.g., a trusted directory) if applicable, to reduce the impact of CWD-based script resolution.

2. 🟠 Node child TUI inherits NODE_OPTIONS / execArgv enabling arbitrary code loading
Property Value
Severity High
CWE CWE-94
Location src/tui/tui-launch.ts:61-101

Description

launchTuiCli() spawns a new Node process for the TUI while inheriting the caller's Node runtime flags and environment.

  • The child arguments include almost all of process.execArgv (only --inspect* flags are filtered).
  • The child environment is process.env (or a shallow copy of it), which includes NODE_OPTIONS.
  • NODE_OPTIONS supports dangerous loaders like --require, --import, and --loader, which can force Node to execute attacker-controlled code at startup.

If OpenClaw is invoked in a context where environment variables or Node flags can be influenced by a less-trusted party (e.g., wrappers, service managers, sudo configurations that preserve env, or multi-user systems), spawning the TUI can become a code-execution vector in the security context of the OpenClaw process.

Vulnerable code:

const args = [...filterTuiExecArgv(process.execArgv), ...buildCurrentCliEntryArgs(), "tui"];
...
const env = ... ? { ...process.env, ... } : process.env;
...
const child = spawn(process.execPath, args, { stdio: "inherit", env });

Recommendation

Scrub Node runtime injection vectors when spawning the child process.

  1. Remove NODE_OPTIONS (and other Node-influencing env vars as appropriate) from the child environment.
  2. Use an allowlist for execArgv, or explicitly drop known code-loading flags such as --require, --import, --loader, --eval, -e, and --experimental-loader.

Example hardening:

function sanitizeChildEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
  const out = { ...env };
  delete out.NODE_OPTIONS;// consider also: delete out.NODE_PATH;
  return out;
}

function filterSafeExecArgv(execArgv: readonly string[]): string[] {
  const deniedPrefixes = ["--require", "--import", "--loader", "--experimental-loader", "--eval", "-e", "--inspect", "--inspect-brk", "--inspect-wait", "--inspect-port"]; 
  return execArgv.filter((arg) => !deniedPrefixes.some((p) => arg === p || arg.startsWith(p + "=") ));
}

const args = [...filterSafeExecArgv(process.execArgv), ...buildCurrentCliEntryArgs(), "tui"];
const child = spawn(process.execPath, args, { stdio: "inherit", env: sanitizeChildEnv(process.env) });

If you need some flags for debugging, gate them behind an explicit --allow-node-options/--debug mode rather than inheriting ambient process state.

3. 🟡 Gateway token/password exposed via child-process command-line arguments when relaunching TUI
Property Value
Severity Medium
CWE CWE-200
Location src/tui/tui-launch.ts:61-66

Description

launchTuiCli() constructs a child Node.js process argv that includes sensitive authentication material (--token, --password) when those fields are present in TuiOptions.

Why this is a problem:

  • Command-line arguments are commonly observable by other local users/processes (e.g., ps, /proc/<pid>/cmdline, crash dumps, telemetry, process managers)
  • This can leak gateway credentials even if the parent process is otherwise secure
  • The new helper makes it easy for future call sites to accidentally pass real secrets

Vulnerable code:

appendOption(args, "--token", opts.token);
appendOption(args, "--password", opts.password);
...
spawn(process.execPath, args, { stdio: "inherit", env });

Recommendation

Avoid passing secrets in argv. Prefer one of:

  1. Environment variables (still potentially readable by same-uid processes on some OSes, but typically less exposed than argv)
  2. Stdin/IPC to transmit the secret after spawn
  3. Config-file based auth (as already done for setup via authSource: "config")

Example (env-based) change:

function buildTuiCliArgs(opts: TuiOptions): string[] {
  const args = [...filterTuiExecArgv(process.execArgv), ...buildCurrentCliEntryArgs(), "tui"];
  appendOption(args, "--url", opts.url);// Do NOT append --token/--password
  return args;
}

export async function launchTuiCli(opts: TuiOptions, launchOptions: TuiLaunchOptions = {}) {
  const args = buildTuiCliArgs(opts);
  const env = {
    ...process.env,
    ...(opts.token ? { OPENCLAW_GATEWAY_TOKEN: opts.token } : {}),
    ...(opts.password ? { OPENCLAW_GATEWAY_PASSWORD: opts.password } : {}),
  };
  spawn(process.execPath, args, { stdio: "inherit", env });
}

Then update the tui CLI command / gateway auth resolution to read these environment variables (and consider a suppressEnvAuthFallback-style control to avoid unintended precedence).


Analyzed PR: #69524 at commit 7c6ebcb

Last updated on: 2026-04-21T02:26:07Z

@openclaw-barnacle openclaw-barnacle Bot added size: S maintainer Maintainer-authored PR labels Apr 21, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 21, 2026

Greptile Summary

This PR fixes terminal state corruption during the onboarding "Hatch in TUI" flow by spawning a fresh openclaw tui child process (via the new tui-launch.ts module) instead of running the TUI in-process, and adds restoreTerminalState calls before and after the handoff.

  • P1 – setup.finalize.ts: The restoreTerminalState("post-setup tui", ...) call is placed sequentially after await launchTuiCli(...) with no try/finally. If the TUI exits abnormally (non-zero code or signal), launchTuiCli rejects, the post-restore is skipped, and the terminal is left in raw/alternate mode for whatever the wizard renders next.

Confidence Score: 4/5

Safe to merge after adding a try/finally around launchTuiCli so the post-restore runs on abnormal TUI exit.

One P1 finding: the post-TUI terminal restore is skipped when the TUI subprocess crashes or exits with a signal. All other findings are P2 (execArgv debug-flag forwarding). The core approach is correct and the test updates are consistent.

src/wizard/setup.finalize.ts — the try/finally gap around launchTuiCli

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/tui/tui-launch.ts
Line: 19

Comment:
**`--inspect` / debug flags forwarded to child process**

`process.execArgv` is spread into the child args. If the parent was started with `--inspect` or `--inspect-brk` for debugging, the child TUI process will try to bind the same debugger port, causing an `EADDRINUSE` failure. Consider filtering out inspector flags from `execArgv` before forwarding them.

```suggestion
  const args = [...process.execArgv.filter(f => !f.startsWith("--inspect")), entry, "tui"];
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/wizard/setup.finalize.ts
Line: 424-435

Comment:
**Missing `try/finally` for post-TUI terminal restore**

`restoreTerminalState("post-setup tui", ...)` on line 434 is only reached when `launchTuiCli` resolves. If the TUI subprocess crashes or is killed by a signal, `launchTuiCli` rejects and the restore is skipped — leaving the terminal in whatever raw/alternate-mode state the TUI left behind for the wizard's remaining output. Wrapping `launchTuiCli` in a `try/finally` that calls `restoreTerminalState("post-setup tui", { resumeStdinIfPaused: true })` would ensure cleanup regardless of exit outcome.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: relaunch setup tui in a fresh proce..." | Re-trigger Greptile

Comment thread src/tui/tui-launch.ts Outdated
Comment thread src/wizard/setup.finalize.ts
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: 4aeb81eeeb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/tui/tui-launch.ts
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: 3669babf8f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/tui/tui-launch.ts Outdated
Comment thread src/tui/tui-launch.ts
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: 459f1fc12d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/wizard/setup.finalize.ts Outdated
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: 85726647f3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/wizard/setup.finalize.ts
@shakkernerd shakkernerd requested a review from a team as a code owner April 21, 2026 02:24
@openclaw-barnacle openclaw-barnacle Bot added the gateway Gateway runtime label Apr 21, 2026
@shakkernerd shakkernerd self-assigned this Apr 21, 2026
@shakkernerd shakkernerd merged commit aae4b1b into main Apr 21, 2026
104 checks passed
@shakkernerd shakkernerd deleted the fix/tui-hatch-tty branch April 21, 2026 02:46
@shakkernerd
Copy link
Copy Markdown
Member Author

Merged via squash.

  • Prepared head SHA: 7c6ebcb
  • Merge commit: aae4b1b
  • Checks: GitHub checks passed before merge; local targeted gates passed during preparation.

Thanks @shakkernerd!

loongfay pushed a commit to yb-claw/openclaw that referenced this pull request Apr 21, 2026
* fix: relaunch setup tui in a fresh process

* fix: harden setup tui handoff

* fix: preserve tui hatch exit flow

* Revert "fix: preserve tui hatch exit flow"

This reverts commit f4f119a.

* fix: let setup tui resolve gateway auth

* fix: support packaged tui relaunch

* fix: pin setup tui gateway target

* fix: preserve setup tui auth source
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant