mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Constrain Windows sandbox requirements (#23766)
# Why Managed requirements can already constrain sandbox policy choices, but Windows sandbox implementation selection was still resolved independently from those requirements. That left the TUI able to continue through the unelevated fallback even when an organization wants to require the elevated Windows sandbox implementation. # What - Add `[windows].allowed_sandbox_implementations` requirements support for the Windows `elevated` and `unelevated` implementations. - Apply that allowlist during core config resolution so disallowed configured or feature-selected Windows sandbox implementations fall back to an allowed implementation with the existing requirements warning path. - Reuse the existing TUI Windows setup prompts to block disallowed unelevated continuation, keep required elevated setup in front of the user, and refuse to persist a TUI-selected Windows sandbox mode that requirements disallow. # Semantics | Allowed | Selected | Effective | | --- | --- | --- | | `["elevated"]` | `unelevated` / unset | `elevated` | | `["unelevated"]` | `elevated` / unset | `unelevated` | | `["elevated", "unelevated"]` | `elevated` | `elevated` | | `["elevated", "unelevated"]` | `unelevated` | `unelevated` | | `["elevated", "unelevated"]` | unset | `elevated` | Availability is handled by interactive setup surfaces after allowlist resolution. If the effective elevated implementation is not ready, elevated-only requirements block on setup. When unelevated is also allowed, the UI may offer the existing unelevated fallback. ## TUI Screens If elevated setup is not already complete: ``` Your organization requires the default Codex agent sandbox to continue. Set it up to protect your files and control network access. Learn more <https://developers.openai.com/codex/windows> › 1. Set up default sandbox (requires Administrator permissions) 2. Quit ``` If admin setup fails under `["elevated"]`: ``` Couldn't set up your sandbox with Administrator permissions Your organization requires the default sandbox before Codex can continue. Learn more <https://developers.openai.com/codex/windows> › 1. Try setting up admin sandbox again 2. Quit ``` # Next Steps - extend the requirements/readout surface, such as `configRequirements/read`, so clients can inspect the loaded `[windows].allowed_sandbox_implementations` requirement instead of inferring it from Windows setup state - consider extending `windowsSandbox/readiness` as well - update the App startup guide, setup flow, and banner surfaces so an elevated-only requirement omits any continue-unelevated escape hatch and blocks startup until a permitted implementation is ready; - preserve the existing unelevated fallback path when requirements allow it, including the `["unelevated"]` case where elevated is disallowed
This commit is contained in:
@@ -30,6 +30,7 @@ use codex_app_server_protocol::NetworkRequirements;
|
||||
use codex_app_server_protocol::NetworkUnixSocketPermission;
|
||||
use codex_app_server_protocol::SandboxMode;
|
||||
use codex_app_server_protocol::ServerNotification;
|
||||
use codex_app_server_protocol::WindowsSandboxSetupMode;
|
||||
use codex_chatgpt::connectors;
|
||||
use codex_config::ConfigRequirementsToml;
|
||||
use codex_config::HookEventsToml;
|
||||
@@ -420,6 +421,23 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
|
||||
.filter_map(map_sandbox_mode_requirement_to_api)
|
||||
.collect()
|
||||
}),
|
||||
allowed_windows_sandbox_implementations: requirements.windows.and_then(|windows| {
|
||||
windows
|
||||
.allowed_sandbox_implementations
|
||||
.map(|implementations| {
|
||||
implementations
|
||||
.into_iter()
|
||||
.map(|implementation| match implementation {
|
||||
codex_config::types::WindowsSandboxModeToml::Elevated => {
|
||||
WindowsSandboxSetupMode::Elevated
|
||||
}
|
||||
codex_config::types::WindowsSandboxModeToml::Unelevated => {
|
||||
WindowsSandboxSetupMode::Unelevated
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}),
|
||||
allowed_permissions: requirements.allowed_permissions,
|
||||
allowed_web_search_modes: requirements.allowed_web_search_modes.map(|modes| {
|
||||
let mut normalized = modes
|
||||
@@ -634,8 +652,10 @@ fn config_write_error(code: ConfigWriteErrorCode, message: impl Into<String>) ->
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::map_requirements_toml_to_api;
|
||||
use codex_app_server_protocol::WindowsSandboxSetupMode;
|
||||
use codex_config::ComputerUseRequirementsToml;
|
||||
use codex_config::ConfigRequirementsToml;
|
||||
use codex_config::WindowsRequirementsToml;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
@@ -687,4 +707,25 @@ mod tests {
|
||||
Some(false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requirements_api_includes_allowed_windows_sandbox_implementations() {
|
||||
let mapped = map_requirements_toml_to_api(ConfigRequirementsToml {
|
||||
windows: Some(WindowsRequirementsToml {
|
||||
allowed_sandbox_implementations: Some(vec![
|
||||
codex_config::types::WindowsSandboxModeToml::Elevated,
|
||||
codex_config::types::WindowsSandboxModeToml::Unelevated,
|
||||
]),
|
||||
}),
|
||||
..ConfigRequirementsToml::default()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
mapped.allowed_windows_sandbox_implementations,
|
||||
Some(vec![
|
||||
WindowsSandboxSetupMode::Elevated,
|
||||
WindowsSandboxSetupMode::Unelevated,
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,29 @@ impl WindowsSandboxRequestProcessor {
|
||||
request_id: &ConnectionRequestId,
|
||||
params: WindowsSandboxSetupStartParams,
|
||||
) -> Result<(), JSONRPCErrorError> {
|
||||
// Validate requirements before acknowledging setup so callers do not get a
|
||||
// `started` response for a Windows sandbox mode that cannot be persisted.
|
||||
let command_cwd = params
|
||||
.cwd
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| self.config.cwd.to_path_buf());
|
||||
let config = self
|
||||
.config_manager
|
||||
.load_for_cwd(
|
||||
/*request_overrides*/ None,
|
||||
ConfigOverrides {
|
||||
cwd: Some(command_cwd.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
Some(command_cwd.clone()),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| config_load_error(&err))?;
|
||||
let setup_mode = resolve_allowed_windows_sandbox_setup_mode(
|
||||
config.config_layer_stack.requirements(),
|
||||
params.mode,
|
||||
)?;
|
||||
|
||||
self.outgoing
|
||||
.send_response(
|
||||
request_id.clone(),
|
||||
@@ -48,46 +71,22 @@ impl WindowsSandboxRequestProcessor {
|
||||
)
|
||||
.await;
|
||||
|
||||
let mode = match params.mode {
|
||||
WindowsSandboxSetupMode::Elevated => CoreWindowsSandboxSetupMode::Elevated,
|
||||
WindowsSandboxSetupMode::Unelevated => CoreWindowsSandboxSetupMode::Unelevated,
|
||||
};
|
||||
let config = Arc::clone(&self.config);
|
||||
let config_manager = self.config_manager.clone();
|
||||
let command_cwd = params
|
||||
.cwd
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| config.cwd.to_path_buf());
|
||||
let outgoing = Arc::clone(&self.outgoing);
|
||||
let connection_id = request_id.connection_id;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let derived_config = config_manager
|
||||
.load_for_cwd(
|
||||
/*request_overrides*/ None,
|
||||
ConfigOverrides {
|
||||
cwd: Some(command_cwd.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
Some(command_cwd.clone()),
|
||||
)
|
||||
.await;
|
||||
let setup_result = match derived_config {
|
||||
Ok(config) => {
|
||||
let setup_request = WindowsSandboxSetupRequest {
|
||||
mode,
|
||||
permission_profile: config.permissions.effective_permission_profile(),
|
||||
workspace_roots: config.effective_workspace_roots(),
|
||||
command_cwd,
|
||||
env_map: std::env::vars().collect(),
|
||||
codex_home: config.codex_home.to_path_buf(),
|
||||
};
|
||||
codex_core::windows_sandbox::run_windows_sandbox_setup(setup_request).await
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
let setup_request = WindowsSandboxSetupRequest {
|
||||
mode: setup_mode,
|
||||
permission_profile: config.permissions.effective_permission_profile(),
|
||||
workspace_roots: config.effective_workspace_roots(),
|
||||
command_cwd,
|
||||
env_map: std::env::vars().collect(),
|
||||
codex_home: config.codex_home.to_path_buf(),
|
||||
};
|
||||
let setup_result =
|
||||
codex_core::windows_sandbox::run_windows_sandbox_setup(setup_request).await;
|
||||
let notification = WindowsSandboxSetupCompletedNotification {
|
||||
mode: match mode {
|
||||
mode: match setup_mode {
|
||||
CoreWindowsSandboxSetupMode::Elevated => WindowsSandboxSetupMode::Elevated,
|
||||
CoreWindowsSandboxSetupMode::Unelevated => WindowsSandboxSetupMode::Unelevated,
|
||||
},
|
||||
@@ -105,6 +104,28 @@ impl WindowsSandboxRequestProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the requested API mode after checking that managed requirements allow it.
|
||||
fn resolve_allowed_windows_sandbox_setup_mode(
|
||||
requirements: &codex_config::ConfigRequirements,
|
||||
requested_mode: WindowsSandboxSetupMode,
|
||||
) -> Result<CoreWindowsSandboxSetupMode, JSONRPCErrorError> {
|
||||
let (setup_mode, config_mode) = match requested_mode {
|
||||
WindowsSandboxSetupMode::Elevated => (
|
||||
CoreWindowsSandboxSetupMode::Elevated,
|
||||
codex_config::types::WindowsSandboxModeToml::Elevated,
|
||||
),
|
||||
WindowsSandboxSetupMode::Unelevated => (
|
||||
CoreWindowsSandboxSetupMode::Unelevated,
|
||||
codex_config::types::WindowsSandboxModeToml::Unelevated,
|
||||
),
|
||||
};
|
||||
requirements
|
||||
.windows_sandbox_mode
|
||||
.can_set(&Some(config_mode))
|
||||
.map_err(|err| invalid_request(format!("invalid Windows sandbox setup mode: {err}")))?;
|
||||
Ok(setup_mode)
|
||||
}
|
||||
|
||||
fn determine_windows_sandbox_readiness(config: &Config) -> WindowsSandboxReadinessResponse {
|
||||
if !cfg!(windows) {
|
||||
return WindowsSandboxReadinessResponse {
|
||||
@@ -140,6 +161,34 @@ fn determine_windows_sandbox_readiness_from_state(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::error_code::INVALID_REQUEST_ERROR_CODE;
|
||||
use codex_config::ConfigRequirements;
|
||||
use codex_config::Constrained;
|
||||
use codex_config::ConstrainedWithSource;
|
||||
use codex_config::types::WindowsSandboxModeToml;
|
||||
|
||||
#[test]
|
||||
fn resolve_allowed_windows_sandbox_setup_mode_rejects_disallowed_mode() {
|
||||
let requirements = ConfigRequirements {
|
||||
windows_sandbox_mode: ConstrainedWithSource::new(
|
||||
Constrained::allow_only(Some(WindowsSandboxModeToml::Elevated)),
|
||||
/*source*/ None,
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let err = resolve_allowed_windows_sandbox_setup_mode(
|
||||
&requirements,
|
||||
WindowsSandboxSetupMode::Unelevated,
|
||||
)
|
||||
.expect_err("unelevated setup should be rejected");
|
||||
|
||||
assert_eq!(err.code, INVALID_REQUEST_ERROR_CODE);
|
||||
assert!(
|
||||
err.message.contains("invalid Windows sandbox setup mode"),
|
||||
"{err:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn determine_windows_sandbox_readiness_reports_not_configured_when_disabled() {
|
||||
|
||||
Reference in New Issue
Block a user