mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
sandboxing: migrate cwd inputs to PathUri (#27816)
## Why Sandbox cwd values can cross app-server and exec-server host boundaries. They should retain URI semantics until the receiving host validates them instead of being interpreted early as native paths. ## What - Carry `PathUri` through filesystem sandbox contexts, sandbox commands, and transform inputs. - Convert command and policy cwd once in `SandboxManager::transform`, then keep launch requests native. - Preserve sandbox cwd over remote filesystem transport and reject non-native URIs without fallback. - Cache paired native/URI turn-environment cwd values during migration, with immutable access to keep them synchronized. - Extend existing protocol, forwarding, transform, and core runtime tests.
This commit is contained in:
@@ -2,6 +2,13 @@
|
||||
|
||||
use codex_app_server_protocol::JSONRPCMessage;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
@@ -25,9 +32,9 @@ use crate::protocol::INITIALIZED_METHOD;
|
||||
use crate::protocol::InitializeResponse;
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_file_system_sends_path_uris_without_native_conversion() {
|
||||
let (websocket_url, captured_paths, server) =
|
||||
record_read_file_paths(/*expected_requests*/ 2).await;
|
||||
async fn remote_file_system_sends_path_and_sandbox_cwd_uris_without_native_conversion() {
|
||||
let (websocket_url, captured_params, server) =
|
||||
record_read_file_params(/*expected_requests*/ 2).await;
|
||||
let file_system = RemoteFileSystem::new(LazyRemoteExecServerClient::new(
|
||||
ExecServerTransportParams::websocket_url(websocket_url),
|
||||
));
|
||||
@@ -35,33 +42,54 @@ async fn remote_file_system_sends_path_uris_without_native_conversion() {
|
||||
PathUri::parse("file:///C:/Users/Alice/src/main.rs").expect("valid drive URI"),
|
||||
PathUri::parse("file://server/share/src/main.rs").expect("valid UNC URI"),
|
||||
];
|
||||
let sandbox_cwd = non_native_cwd();
|
||||
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]);
|
||||
let sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
|
||||
sandbox_cwd,
|
||||
);
|
||||
|
||||
for path in &paths {
|
||||
assert_eq!(
|
||||
file_system
|
||||
.read_file(path, /*sandbox*/ None)
|
||||
.read_file(path, Some(&sandbox))
|
||||
.await
|
||||
.expect("remote read should succeed"),
|
||||
Vec::<u8>::new()
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(captured_paths.await.expect("captured paths"), paths);
|
||||
let expected_params = paths
|
||||
.into_iter()
|
||||
.map(|path| FsReadFileParams {
|
||||
path,
|
||||
sandbox: Some(sandbox.clone()),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
captured_params.await.expect("captured params"),
|
||||
expected_params
|
||||
);
|
||||
server.await.expect("recording server should succeed");
|
||||
}
|
||||
|
||||
async fn record_read_file_paths(
|
||||
async fn record_read_file_params(
|
||||
expected_requests: usize,
|
||||
) -> (
|
||||
String,
|
||||
oneshot::Receiver<Vec<PathUri>>,
|
||||
oneshot::Receiver<Vec<FsReadFileParams>>,
|
||||
tokio::task::JoinHandle<()>,
|
||||
) {
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.expect("listener should bind");
|
||||
let websocket_url = format!("ws://{}", listener.local_addr().expect("listener address"));
|
||||
let (captured_paths_tx, captured_paths_rx) = oneshot::channel();
|
||||
let (captured_params_tx, captured_params_rx) = oneshot::channel();
|
||||
let server = tokio::spawn(async move {
|
||||
let (stream, _) = listener.accept().await.expect("listener should accept");
|
||||
let mut websocket = accept_async(stream)
|
||||
@@ -69,7 +97,7 @@ async fn record_read_file_paths(
|
||||
.expect("websocket handshake should succeed");
|
||||
complete_websocket_initialize(&mut websocket).await;
|
||||
|
||||
let mut captured_paths = Vec::with_capacity(expected_requests);
|
||||
let mut captured_params = Vec::with_capacity(expected_requests);
|
||||
for _ in 0..expected_requests {
|
||||
let request = match read_jsonrpc_websocket(&mut websocket).await {
|
||||
JSONRPCMessage::Request(request) if request.method == FS_READ_FILE_METHOD => {
|
||||
@@ -80,7 +108,7 @@ async fn record_read_file_paths(
|
||||
let params: FsReadFileParams =
|
||||
serde_json::from_value(request.params.expect("fs/readFile params should exist"))
|
||||
.expect("fs/readFile params should deserialize");
|
||||
captured_paths.push(params.path);
|
||||
captured_params.push(params);
|
||||
write_jsonrpc_websocket(
|
||||
&mut websocket,
|
||||
JSONRPCMessage::Response(JSONRPCResponse {
|
||||
@@ -93,12 +121,21 @@ async fn record_read_file_paths(
|
||||
)
|
||||
.await;
|
||||
}
|
||||
captured_paths_tx
|
||||
.send(captured_paths)
|
||||
.expect("captured paths receiver should stay open");
|
||||
captured_params_tx
|
||||
.send(captured_params)
|
||||
.expect("captured params receiver should stay open");
|
||||
});
|
||||
|
||||
(websocket_url, captured_paths_rx, server)
|
||||
(websocket_url, captured_params_rx, server)
|
||||
}
|
||||
|
||||
fn non_native_cwd() -> PathUri {
|
||||
#[cfg(unix)]
|
||||
let uri = "file://server/share/checkout";
|
||||
#[cfg(windows)]
|
||||
let uri = "file:///usr/local/checkout";
|
||||
|
||||
PathUri::parse(uri).expect("non-native cwd URI")
|
||||
}
|
||||
|
||||
async fn complete_websocket_initialize(websocket: &mut WebSocketStream<TcpStream>) {
|
||||
|
||||
Reference in New Issue
Block a user