chore(app-server) world-writable windows notification (#6880)

## Summary
On app-server startup, detect whether the experimental sandbox is
enabled, and send a notification .

**Note**
New conversations will not respect the feature because we [ignore cli
overrides in
NewConversation](a75321a64c/codex-rs/app-server/src/codex_message_processor.rs (L1237-L1252)).
However, this should be okay, since we don't actually use config for
this, we use a [global
variable](87cce88f48/codex-rs/core/src/safety.rs (L105-L110)).
We should carefully unwind this setup at some point.


## Testing
- [ ] In progress: testing locally

---------

Co-authored-by: jif-oai <jif@openai.com>
This commit is contained in:
Dylan Hurd
2025-11-19 03:19:34 -08:00
committed by GitHub
parent 4985a7a444
commit 44c747837a
6 changed files with 77 additions and 3 deletions

1
codex-rs/Cargo.lock generated
View File

@@ -849,6 +849,7 @@ dependencies = [
"codex-login",
"codex-protocol",
"codex-utils-json-to-toml",
"codex-windows-sandbox",
"core_test_support",
"mcp-types",
"opentelemetry-appender-tracing",

View File

@@ -494,6 +494,9 @@ server_notification_definitions! {
ReasoningSummaryPartAdded => "item/reasoning/summaryPartAdded" (v2::ReasoningSummaryPartAddedNotification),
ReasoningTextDelta => "item/reasoning/textDelta" (v2::ReasoningTextDeltaNotification),
/// Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.
WindowsWorldWritableWarning => "windows/worldWritableWarning" (v2::WindowsWorldWritableWarningNotification),
#[serde(rename = "account/login/completed")]
#[ts(rename = "account/login/completed")]
#[strum(serialize = "account/login/completed")]

View File

@@ -934,6 +934,15 @@ pub struct McpToolCallProgressNotification {
pub message: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WindowsWorldWritableWarningNotification {
pub sample_paths: Vec<String>,
pub extra_count: usize,
pub failed_scan: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]

View File

@@ -40,6 +40,7 @@ tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
opentelemetry-appender-tracing = { workspace = true }
uuid = { workspace = true, features = ["serde", "v7"] }
codex-windows-sandbox.workspace = true
[dev-dependencies]
app_test_support = { workspace = true }

View File

@@ -111,6 +111,7 @@ use codex_core::config_loader::load_config_as_toml;
use codex_core::default_client::get_codex_user_agent;
use codex_core::exec::ExecParams;
use codex_core::exec_env::create_env;
use codex_core::features::Feature;
use codex_core::find_conversation_path_by_id_str;
use codex_core::get_platform_sandbox;
use codex_core::git_info::git_diff_to_remote;
@@ -1249,7 +1250,17 @@ impl CodexMessageProcessor {
..Default::default()
};
let config = match derive_config_from_params(overrides, cli_overrides).await {
// Persist windows sandbox feature.
// TODO: persist default config in general.
let mut cli_overrides = cli_overrides.unwrap_or_default();
if cfg!(target_os = "windows") && self.config.features.enabled(Feature::WindowsSandbox) {
cli_overrides.insert(
"features.enable_experimental_windows_sandbox".to_string(),
serde_json::json!(true),
);
}
let config = match derive_config_from_params(overrides, Some(cli_overrides)).await {
Ok(config) => config,
Err(err) => {
let error = JSONRPCErrorError {

View File

@@ -6,17 +6,19 @@ use crate::outgoing_message::OutgoingMessageSender;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::InitializeResponse;
use codex_app_server_protocol::JSONRPCError;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCRequest;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::WindowsWorldWritableWarningNotification;
use codex_core::AuthManager;
use codex_core::ConversationManager;
use codex_core::config::Config;
use codex_core::default_client::USER_AGENT_SUFFIX;
use codex_core::default_client::get_codex_user_agent;
use codex_core::features::Feature;
use codex_feedback::CodexFeedback;
use codex_protocol::protocol::SessionSource;
use std::sync::Arc;
@@ -24,6 +26,7 @@ use std::sync::Arc;
pub(crate) struct MessageProcessor {
outgoing: Arc<OutgoingMessageSender>,
codex_message_processor: CodexMessageProcessor,
config: Arc<Config>,
initialized: bool,
}
@@ -51,13 +54,14 @@ impl MessageProcessor {
conversation_manager,
outgoing.clone(),
codex_linux_sandbox_exe,
config,
config.clone(),
feedback,
);
Self {
outgoing,
codex_message_processor,
config,
initialized: false,
}
}
@@ -118,6 +122,8 @@ impl MessageProcessor {
self.outgoing.send_response(request_id, response).await;
self.initialized = true;
self.handle_windows_world_writable_warning().await;
return;
}
}
@@ -156,4 +162,47 @@ impl MessageProcessor {
pub(crate) fn process_error(&mut self, err: JSONRPCError) {
tracing::error!("<- error: {:?}", err);
}
/// On Windows, when using the experimental sandbox, we need to warn the user about world-writable directories.
async fn handle_windows_world_writable_warning(&self) {
if !cfg!(windows) {
return;
}
if !self.config.features.enabled(Feature::WindowsSandbox) {
return;
}
if !matches!(
self.config.sandbox_policy,
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { .. }
| codex_protocol::protocol::SandboxPolicy::ReadOnly
) {
return;
}
if self
.config
.notices
.hide_world_writable_warning
.unwrap_or(false)
{
return;
}
// This function is stubbed out to return None on non-Windows platforms
if let Some((sample_paths, extra_count, failed_scan)) =
codex_windows_sandbox::world_writable_warning_details(self.config.codex_home.as_path())
{
self.outgoing
.send_server_notification(ServerNotification::WindowsWorldWritableWarning(
WindowsWorldWritableWarningNotification {
sample_paths,
extra_count,
failed_scan,
},
))
.await;
}
}
}