[Windows Remote SSH] Desktop pre-creates app-server-control with inherited ACL, conflicting with #42326 socket validation
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of the Codex App are you using?
Codex GUI 26.901.51231 on Gentoo Linux (AppImage).
Remote Codex CLI: codex-cli 0.154.0 (Scoop, native Windows binary).
What platform is your computer?
Client:
- Gentoo Linux on a ThinkPad
- Codex GUI
26.901.51231
Remote SSH host:
- Native Windows,
10.0.26200 - Windows OpenSSH
- MSYS/zsh login environment
- Codex CLI
0.154.0
What issue are you seeing?
Codex Desktop Remote SSH authenticates successfully and can invoke the remote codex CLI, but the GUI fails with socket hang up because its SSH bootstrap pre-creates $CODEX_HOME/app-server-control using POSIX mkdir -p.
On native Windows through MSYS, that creates the directory with an inherited Windows DACL. Codex CLI 0.154.0 then rejects the existing directory under the Windows control-socket hardening introduced by #42326:
Error: socket directory is not private to the current user
The conflict is deterministic:
Desktop creates a directory which the CLI it immediately launches is required to reject.
The current GUI bundle was inspected directly (app.asar). Its Remote SSH bootstrap contains the equivalent of:
umask 077
mkdir -p -- "${CODEX_HOME:-$HOME/.codex}/app-server-control"
codex -c features.code_mode_host=true app-server --listen unix://
In this build there is no chmod 700 before launching the app-server.
On Windows, umask 077 does not produce the protected, current-user-only DACL required by the CLI's Windows socket validation.
Controlled A/B/C reproduction
I ran a controlled comparison against the same host, account, CLI, socket path, and SSH environment.
| Case | Directory creator | ACL state | Result |
|---|---|---|---|
| Original failure | Desktop/MSYS | Protected=false, 4 allow ACEs |
app-server rejects directory; GUI socket hang up |
| A | native codex.exe |
Protected=true, 1 allow ACE (current user, full control) |
HTTP 101 + app-server initialize succeeds; proxy exits 0 |
| B | Desktop Remote SSH bootstrap | Protected=false, 4 allow ACEs |
same socket directory is not private error |
| C | native CLI creates directory first; Desktop then reconnects | protected single-ACE ACL remains unchanged | Desktop starts its own app-server and reaches connected, initialized=true |
The four allow ACEs produced in the failing Desktop-created directory were:
- current user
SYSTEMAdministratorsCodexSandboxUsers
A — CLI creates the directory
- Stop any app-server/proxy using the default control socket.
- Move the existing
app-server-controldirectory aside so the target path does not exist. - Start native Windows Codex directly with the same effective app-server arguments:
codex.exe -c features.code_mode_host=true app-server --listen unix://
- The CLI creates
$CODEX_HOME/app-server-controlwith:
Protected=true
Entries=1
- Through SSH, run
codex app-server proxyand send a real WebSocket Upgrade request. - Receive HTTP
101 Switching Protocols. - Send app-server
initialize; receive a valid response reporting the Windows platform, correctcodexHome, and version0.154.0. - Close the WebSocket normally; proxy exits
0.
This establishes that the native Windows app-server/socket/proxy path works when the CLI creates its own rendezvous directory.
B — Desktop creates the directory
- Stop the A app-server.
- Move the CLI-created directory aside so the target path again does not exist.
- Click Connect in the same Codex GUI.
- Desktop creates
app-server-controlbefore the CLI starts. - The new directory has:
Protected=false
Entries=4
- The new
app-server.logimmediately contains:
Error: socket directory is not private to the current user
- Subsequent proxy attempts fail with Windows connection refused (
10061) and the GUI reportssocket hang up.
The failing DACL is materially the same as the original directory's DACL.
C — retain the CLI-created private directory and let Desktop launch the server
To separate "Desktop can only attach to a manually started server" from the directory-creation bug:
- Remove the Desktop-created failing directory.
- Let native CLI create a correct protected single-ACE directory.
- Stop the manually started app-server but leave that directory in place.
- Allow Desktop to reconnect/retry normally.
- Desktop's
mkdir -psees the existing directory and does not alter its DACL. - Desktop then starts a new remote app-server itself.
- The GUI reaches:
connected
initialized=true
connectionError=null
- A separate WebSocket +
initializeprobe against that GUI-started server also succeeds.
The final directory remains Protected=true, Entries=1.
This demonstrates that preserving the CLI-created Windows ACL is sufficient for the current Desktop Remote SSH flow to work; the blocker is the bootstrap's pre-creation of the directory.
Source-code match
Codex 0.154.0 Windows socket validation:
https://github.com/openai/codex/blob/rust-v0.154.0/codex-rs/uds/src/windows_socket_validation.rs
PR #42326 explicitly changed Windows control-socket handling to:
- create socket directories with a protected, inheritable, current-user-only DACL;
- reject existing directories with broader permissions rather than repairing them.
https://github.com/openai/codex/pull/42326
That security behavior is reasonable on its own: repairing an already-existing broader directory cannot revoke access through handles that may already have been opened.
The integration problem is that Desktop Remote SSH creates the rendezvous directory first using a POSIX bootstrap, preventing the Windows CLI from creating it with the required DACL.
Expected behavior
Desktop Remote SSH should not pre-create the Windows control-socket directory in a form the Windows CLI is required to reject.
Possible fixes include:
- Let
codex app-server --listen unix://create$CODEX_HOME/app-server-controlitself. - Detect native Windows remotes and use a Windows-aware bootstrap that creates the directory with the same protected DACL as
codex.exe. - Move ownership of rendezvous-directory creation entirely into the CLI/app-server so Desktop does not need to duplicate platform-specific security semantics.
The CLI should retain the fail-closed validation introduced by #42326.
Workaround
A working workaround is to let native codex.exe create app-server-control once, then keep that directory. The current Desktop bootstrap's mkdir -p preserves the already-correct ACL, after which Desktop can start and connect to the app-server normally.
Deleting/recreating app-server-control reintroduces the problem if Desktop wins the creation race.
Additional observation (separate from the proven ACL root cause)
The SSH bootstrap also intermittently logs an MSYS fork failure involving 0xC0000142 / Resource temporarily unavailable, and one cold-start bootstrap attempt timed out after 60 seconds.
I have not established the cause of that error. It is not required for the ACL failure: in case C, the GUI reached initialized=true despite an MSYS fork error being logged. I am therefore treating it as a separate observation rather than part of this issue's root cause.
Related issues
- #29015 — native Windows Remote SSH needs a Windows-aware app-server lifecycle/bootstrap path
- #22965 — native Windows + Git Bash reaches app-server/proxy but Remote SSH fails later
- #24090 — earlier native Windows
unix:/// proxy failure on 0.131 - #20636 — historical evidence that Desktop itself pre-creates
app-server-controlin the SSH bootstrap - #42326 — Windows control-socket hardening that now intentionally rejects broad existing ACLs
This report is narrower than #29015 and differs from #22965/#24090 because the failure on 0.154.0 has a deterministic ACL cause and an A/B/C reproduction.
Additional information
The experiments did not modify global Codex configuration, SSH configuration, CLI installation, or project files. The original failing directory and the A/B experiment artifacts were preserved. I can provide sanitized SDDL values, timestamps, Desktop log extracts, the extracted bootstrap source snippet, and the WebSocket/initialize probe results if useful.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with codex-rs/uds/src/windows_socket_validation.rs and the Remote SSH bootstrap that runs umask 077 and mkdir -p before codex app-server --listen unix://. Reproduce the native Windows ACL difference described in cases A and B, then trace the bootstrap entry point. Done means Desktop no longer creates a directory the CLI rejects and the connection reaches initialized=true.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, shell
- Domain
- cli, networking, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100