Files
codex/codex-rs/windows-sandbox-rs/Cargo.toml
efrazer-oai b885c3f8b1 Filter Windows sandbox roots from SSH config dependencies (#18493)
## Stack

1. Base PR: #18443 stops granting ACLs on `USERPROFILE`.
2. This PR: filters additional SSH-owned profile roots discovered from
SSH config.

## Bug

The base PR removes the broadest bad grant: `USERPROFILE` itself.

That still leaves one important case. A user profile child can be
SSH-owned even when its name is not one of our fixed exclusions.

For example:

```sshconfig
Host devbox
  IdentityFile ~/.keys/devbox
  CertificateFile ~/.certs/devbox-cert.pub
  UserKnownHostsFile ~/.known_hosts_custom
  Include ~/.ssh/conf.d/*.conf
```

After profile expansion, the sandbox might see these as normal profile
children:

```text
C:\Users\me\.keys
C:\Users\me\.certs
C:\Users\me\.known_hosts_custom
C:\Users\me\.ssh
```

Those paths have another owner: OpenSSH and the tools that manage SSH
identity and host-key state. Codex should not add sandbox ACLs to them.

OpenSSH describes this dependency tree in
[`ssh_config(5)`](https://man.openbsd.org/ssh_config.5), and the client
parser follows the same shape in `readconf.c`:

- `Include` recursively reads more config files and expands globs
- `IdentityFile` and `CertificateFile` name authentication files
- `UserKnownHostsFile`, `GlobalKnownHostsFile`, and `RevokedHostKeys`
name host-key files
- `ControlPath` and `IdentityAgent` can name profile-owned sockets or
control files
- these path directives can use forms such as `~`, `%d`, and `${HOME}`

## Change

This PR adds a small SSH config dependency scanner.

It starts at:

```text
~/.ssh/config
```

Then it returns concrete paths named by `Include` and by path-valued SSH
config directives:

```text
IdentityFile
CertificateFile
UserKnownHostsFile
GlobalKnownHostsFile
RevokedHostKeys
ControlPath
IdentityAgent
```

For example:

```sshconfig
IdentityFile ~/.keys/devbox
CertificateFile ~/.certs/devbox-cert.pub
Include ~/.ssh/conf.d/*.conf
```

returns paths like:

```text
C:\Users\me\.keys\devbox
C:\Users\me\.certs\devbox-cert.pub
C:\Users\me\.ssh\conf.d\devbox.conf
```

The setup code then maps those paths back to their top-level
`USERPROFILE` child and filters matching sandbox roots out of both the
writable and readable root lists.

## Why this shape

The parser reports what SSH config references. The sandbox setup code
decides which `USERPROFILE` roots are unsafe to grant.

That keeps the policy simple:

1. expand broad profile grants
2. remove the profile root
3. remove fixed sensitive profile folders
4. remove profile folders referenced by SSH config dependencies

If a path has two possible owners, the sandbox steps back. SSH keeps
control of SSH config, keys, certificates, known-hosts files, sockets,
and included config files.

## Tests

- `cargo test -p codex-windows-sandbox --lib`
- `just bazel-lock-check`
- `just fix -p codex-windows-sandbox`
- `git diff --check`
2026-04-19 14:58:33 -07:00

98 lines
2.3 KiB
TOML

[package]
build = "build.rs"
edition.workspace = true
license.workspace = true
name = "codex-windows-sandbox"
version.workspace = true
[lib]
name = "codex_windows_sandbox"
path = "src/lib.rs"
[[bin]]
name = "codex-windows-sandbox-setup"
path = "src/bin/setup_main.rs"
[[bin]]
name = "codex-command-runner"
path = "src/bin/command_runner.rs"
[lints]
workspace = true
[dependencies]
anyhow = "1.0"
base64 = { workspace = true }
chrono = { version = "0.4.42", default-features = false, features = [
"clock",
"std",
] }
codex-utils-pty = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-string = { workspace = true }
dunce = "1.0"
glob = { workspace = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tempfile = "3"
tokio = { workspace = true, features = ["sync", "rt"] }
windows = { version = "0.58", features = [
"Win32_Foundation",
"Win32_NetworkManagement_WindowsFirewall",
"Win32_System_Com",
"Win32_System_Variant",
] }
[dependencies.codex-protocol]
package = "codex-protocol"
path = "../protocol"
[dependencies.rand]
default-features = false
features = ["std", "small_rng"]
version = "0.8"
[dependencies.dirs-next]
version = "2.0"
[target.'cfg(windows)'.dependencies.windows-sys]
features = [
"Win32_Foundation",
"Win32_System_Diagnostics_Debug",
"Win32_Security",
"Win32_Security_Authorization",
"Win32_System_Threading",
"Win32_System_JobObjects",
"Win32_System_SystemServices",
"Win32_System_Environment",
"Win32_System_Pipes",
"Win32_System_WindowsProgramming",
"Win32_System_IO",
"Win32_System_Memory",
"Win32_System_Kernel",
"Win32_System_Console",
"Win32_Storage_FileSystem",
"Win32_System_Diagnostics_ToolHelp",
"Win32_NetworkManagement_NetManagement",
"Win32_Networking_WinSock",
"Win32_System_LibraryLoader",
"Win32_System_Com",
"Win32_Security_Cryptography",
"Win32_Security_Authentication_Identity",
"Win32_Graphics_Gdi",
"Win32_System_StationsAndDesktops",
"Win32_UI_WindowsAndMessaging",
"Win32_UI_Shell",
"Win32_System_Registry",
]
version = "0.52"
[dev-dependencies]
pretty_assertions = { workspace = true }
[build-dependencies]
winres = "0.1"
[package.metadata.cargo-shear]
ignored = ["codex-utils-pty", "tokio"]