app-server: require absolute cwd for windowsSandbox/setupStart (#13833)

## Summary
- require windowsSandbox/setupStart.cwd to be an AbsolutePathBuf
- reject relative cwd values at request parsing instead of normalizing
them later in the setup flow
- add RPC-layer coverage for relative cwd rejection and update the
checked-in protocol schemas/docs

## Why
windowsSandbox/setupStart was carrying the client-provided cwd as a raw
PathBuf for command_cwd while config derivation normalized the same
value into an absolute policy_cwd.

That left room for relative-path ambiguity in the setup path, especially
for inputs like cwd: "repo". Making the RPC accept only absolute paths
removes that split entirely: the handler now receives one
already-validated absolute path and uses it for both config derivation
and setup.

This keeps the trust model unchanged. Trusted clients could already
choose the session cwd; this change is only about making the setup RPC
reject relative paths so command_cwd and policy_cwd cannot diverge.

## Testing
- cargo test -p codex-app-server windows_sandbox_setup (run locally by
user)
- cargo test -p codex-app-server-protocol windows_sandbox (run locally
by user)
This commit is contained in:
iceweasel-oai
2026-03-06 22:47:08 -08:00
committed by GitHub
Unverified
parent b0ce16c47a
commit 4b4f61d379
9 changed files with 71 additions and 20 deletions
@@ -3041,9 +3041,13 @@
"WindowsSandboxSetupStartParams": {
"properties": {
"cwd": {
"type": [
"string",
"null"
"anyOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
},
{
"type": "null"
}
]
},
"mode": {
@@ -4213,4 +4217,4 @@
}
],
"title": "ClientRequest"
}
}
@@ -17002,9 +17002,13 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cwd": {
"type": [
"string",
"null"
"anyOf": [
{
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
{
"type": "null"
}
]
},
"mode": {
@@ -17067,4 +17071,4 @@
},
"title": "CodexAppServerProtocol",
"type": "object"
}
}
@@ -15258,9 +15258,13 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cwd": {
"type": [
"string",
"null"
"anyOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
},
{
"type": "null"
}
]
},
"mode": {
@@ -15322,4 +15326,4 @@
},
"title": "CodexAppServerProtocolV2",
"type": "object"
}
}
@@ -1,6 +1,10 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
},
"WindowsSandboxSetupMode": {
"enum": [
"elevated",
@@ -11,9 +15,13 @@
},
"properties": {
"cwd": {
"type": [
"string",
"null"
"anyOf": [
{
"$ref": "#/definitions/AbsolutePathBuf"
},
{
"type": "null"
}
]
},
"mode": {
@@ -25,4 +33,4 @@
],
"title": "WindowsSandboxSetupStartParams",
"type": "object"
}
}
@@ -1,6 +1,7 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { WindowsSandboxSetupMode } from "./WindowsSandboxSetupMode";
export type WindowsSandboxSetupStartParams = { mode: WindowsSandboxSetupMode, cwd?: string | null, };
export type WindowsSandboxSetupStartParams = { mode: WindowsSandboxSetupMode, cwd?: AbsolutePathBuf | null, };
@@ -4226,7 +4226,7 @@ pub enum WindowsSandboxSetupMode {
pub struct WindowsSandboxSetupStartParams {
pub mode: WindowsSandboxSetupMode,
#[ts(optional = nullable)]
pub cwd: Option<PathBuf>,
pub cwd: Option<AbsolutePathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
+1 -1
View File
@@ -163,7 +163,7 @@ Example with notification opt-out:
- `tool/requestUserInput` — prompt the user with 13 short questions for a tool call and return their answers (experimental).
- `config/mcpServer/reload` — reload MCP server config from disk and queue a refresh for loaded threads (applied on each thread's next active turn); returns `{}`. Use this after editing `config.toml` without restarting the server.
- `mcpServerStatus/list` — enumerate configured MCP servers with their tools, resources, resource templates, and auth status; supports cursor+limit pagination.
- `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); accepts an optional `cwd` to target setup for a specific workspace, returns `{ started: true }` immediately, and later emits `windowsSandbox/setupCompleted`.
- `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); accepts an optional absolute `cwd` to target setup for a specific workspace, returns `{ started: true }` immediately, and later emits `windowsSandbox/setupCompleted`.
- `feedback/upload` — submit a feedback report (classification + optional reason/logs, conversation_id, and optional `extraLogFiles` attachments array); returns the tracking thread id.
- `config/read` — fetch the effective config on disk after resolving config layering.
- `externalAgentConfig/detect` — detect migratable external-agent artifacts with `includeHome` and optional `cwds`; each detected item includes `cwd` (`null` for home).
@@ -6653,7 +6653,10 @@ impl CodexMessageProcessor {
let config = Arc::clone(&self.config);
let cli_overrides = self.cli_overrides.clone();
let cloud_requirements = self.current_cloud_requirements();
let command_cwd = params.cwd.unwrap_or_else(|| config.cwd.clone());
let command_cwd = params
.cwd
.map(PathBuf::from)
.unwrap_or_else(|| config.cwd.clone());
let outgoing = Arc::clone(&self.outgoing);
let connection_id = request_id.connection_id;
@@ -62,3 +62,30 @@ async fn windows_sandbox_setup_start_emits_completion_notification() -> Result<(
assert_eq!(payload.mode, WindowsSandboxSetupMode::Unelevated);
Ok(())
}
#[tokio::test]
async fn windows_sandbox_setup_start_rejects_relative_cwd() -> Result<()> {
let codex_home = TempDir::new()?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_raw_request(
"windowsSandbox/setupStart",
Some(serde_json::json!({
"mode": "unelevated",
"cwd": "relative-root",
})),
)
.await?;
let err = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
)
.await??;
assert_eq!(err.error.code, -32600);
assert!(err.error.message.contains("Invalid request"));
Ok(())
}