Add CUA requirements subsection for locked computer use (#23555)

Adds a new top-level section for "CUA" requirements that can allow for
disablement of specific features as needed for enterprises.
This commit is contained in:
adams-oai
2026-05-19 15:41:44 -07:00
committed by GitHub
Unverified
parent c53da029bc
commit d86352d520
17 changed files with 189 additions and 2 deletions
@@ -7089,6 +7089,17 @@
],
"type": "object"
},
"ComputerUseRequirements": {
"properties": {
"allowLockedComputerUse": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"Config": {
"additionalProperties": true,
"properties": {
@@ -7635,6 +7646,16 @@
"null"
]
},
"computerUse": {
"anyOf": [
{
"$ref": "#/definitions/v2/ComputerUseRequirements"
},
{
"type": "null"
}
]
},
"enforceResidency": {
"anyOf": [
{
@@ -3478,6 +3478,17 @@
],
"type": "object"
},
"ComputerUseRequirements": {
"properties": {
"allowLockedComputerUse": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"Config": {
"additionalProperties": true,
"properties": {
@@ -4024,6 +4035,16 @@
"null"
]
},
"computerUse": {
"anyOf": [
{
"$ref": "#/definitions/ComputerUseRequirements"
},
{
"type": "null"
}
]
},
"enforceResidency": {
"anyOf": [
{
@@ -60,6 +60,17 @@
}
]
},
"ComputerUseRequirements": {
"properties": {
"allowLockedComputerUse": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"ConfigRequirements": {
"properties": {
"allowManagedHooksOnly": {
@@ -95,6 +106,16 @@
"null"
]
},
"computerUse": {
"anyOf": [
{
"$ref": "#/definitions/ComputerUseRequirements"
},
{
"type": "null"
}
]
},
"enforceResidency": {
"anyOf": [
{
@@ -0,0 +1,5 @@
// 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.
export type ComputerUseRequirements = { allowLockedComputerUse: boolean | null, };
@@ -3,7 +3,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { WebSearchMode } from "../WebSearchMode";
import type { AskForApproval } from "./AskForApproval";
import type { ComputerUseRequirements } from "./ComputerUseRequirements";
import type { ResidencyRequirement } from "./ResidencyRequirement";
import type { SandboxMode } from "./SandboxMode";
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWebSearchModes: Array<WebSearchMode> | null, allowManagedHooksOnly: boolean | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null};
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWebSearchModes: Array<WebSearchMode> | null, allowManagedHooksOnly: boolean | null, computerUse: ComputerUseRequirements | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null};
@@ -62,6 +62,7 @@ export type { CommandExecutionRequestApprovalResponse } from "./CommandExecution
export type { CommandExecutionSource } from "./CommandExecutionSource";
export type { CommandExecutionStatus } from "./CommandExecutionStatus";
export type { CommandMigration } from "./CommandMigration";
export type { ComputerUseRequirements } from "./ComputerUseRequirements";
export type { Config } from "./Config";
export type { ConfigBatchWriteParams } from "./ConfigBatchWriteParams";
export type { ConfigEdit } from "./ConfigEdit";
@@ -388,6 +388,7 @@ pub struct ConfigRequirements {
pub allowed_sandbox_modes: Option<Vec<SandboxMode>>,
pub allowed_web_search_modes: Option<Vec<WebSearchMode>>,
pub allow_managed_hooks_only: Option<bool>,
pub computer_use: Option<ComputerUseRequirements>,
pub feature_requirements: Option<BTreeMap<String, bool>>,
#[experimental("configRequirements/read.hooks")]
pub hooks: Option<ManagedHooksRequirements>,
@@ -396,6 +397,13 @@ pub struct ConfigRequirements {
pub network: Option<NetworkRequirements>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ComputerUseRequirements {
pub allow_locked_computer_use: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -1730,6 +1730,7 @@ fn config_requirements_granular_allowed_approval_policy_is_marked_experimental()
allowed_sandbox_modes: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
feature_requirements: None,
hooks: None,
enforce_residency: None,
+1 -1
View File
@@ -223,7 +223,7 @@ Example with notification opt-out:
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home) and any plugin/session `details` returned by detect. When a request includes migration items, the server emits `externalAgentConfig/import/completed` once after the full import finishes (immediately after the response when everything completed synchronously, or after background imports finish).
- `config/value/write` — write a single config key/value to the user's config.toml on disk; dotted paths such as `desktop.someKey` use the same generic write surface.
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads, including multiple `desktop.*` edits.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), lifecycle hook lockdown (`allowManagedHooksOnly`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), lifecycle hook lockdown (`allowManagedHooksOnly`), computer use policy (`computerUse`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
### Example: Start or resume a thread
@@ -9,6 +9,7 @@ use crate::outgoing_message::OutgoingMessageSender;
use codex_analytics::AnalyticsEventsClient;
use codex_app_server_protocol::AppListUpdatedNotification;
use codex_app_server_protocol::ClientResponsePayload;
use codex_app_server_protocol::ComputerUseRequirements;
use codex_app_server_protocol::ConfigBatchWriteParams;
use codex_app_server_protocol::ConfigReadParams;
use codex_app_server_protocol::ConfigReadResponse;
@@ -429,6 +430,9 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
normalized
}),
allow_managed_hooks_only: requirements.allow_managed_hooks_only,
computer_use: requirements
.computer_use
.map(map_computer_use_requirements_to_api),
feature_requirements: requirements
.feature_requirements
.map(|requirements| requirements.entries),
@@ -440,6 +444,14 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
}
}
fn map_computer_use_requirements_to_api(
computer_use: codex_config::ComputerUseRequirementsToml,
) -> ComputerUseRequirements {
ComputerUseRequirements {
allow_locked_computer_use: computer_use.allow_locked_computer_use,
}
}
fn map_hooks_requirements_to_api(hooks: ManagedHooksRequirementsToml) -> ManagedHooksRequirements {
let ManagedHooksRequirementsToml {
managed_dir,
@@ -617,6 +629,7 @@ fn config_write_error(code: ConfigWriteErrorCode, message: impl Into<String>) ->
#[cfg(test)]
mod tests {
use super::map_requirements_toml_to_api;
use codex_config::ComputerUseRequirementsToml;
use codex_config::ConfigRequirementsToml;
use pretty_assertions::assert_eq;
@@ -630,4 +643,21 @@ mod tests {
assert_eq!(mapped.allow_managed_hooks_only, Some(true));
assert_eq!(mapped.hooks, None);
}
#[test]
fn requirements_api_includes_computer_use_requirements() {
let mapped = map_requirements_toml_to_api(ConfigRequirementsToml {
computer_use: Some(ComputerUseRequirementsToml {
allow_locked_computer_use: Some(false),
}),
..ConfigRequirementsToml::default()
});
assert_eq!(
mapped
.computer_use
.and_then(|requirements| requirements.allow_locked_computer_use),
Some(false)
);
}
}
+16
View File
@@ -1205,6 +1205,7 @@ mod tests {
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -1288,6 +1289,7 @@ mod tests {
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -1322,6 +1324,7 @@ mod tests {
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -1373,6 +1376,7 @@ mod tests {
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -1524,6 +1528,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -1605,6 +1610,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -1684,6 +1690,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -1891,6 +1898,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -1932,6 +1940,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -1993,6 +2002,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -2050,6 +2060,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -2109,6 +2120,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -2169,6 +2181,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -2229,6 +2242,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -2319,6 +2333,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -2351,6 +2366,7 @@ command = "sample-mcp"
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,
@@ -88,6 +88,7 @@ pub struct ConfigRequirements {
pub permission_profile: ConstrainedWithSource<PermissionProfile>,
pub web_search_mode: ConstrainedWithSource<WebSearchMode>,
pub allow_managed_hooks_only: Option<Sourced<bool>>,
pub computer_use: Option<Sourced<ComputerUseRequirementsToml>>,
pub feature_requirements: Option<Sourced<FeatureRequirementsToml>>,
pub managed_hooks: Option<ConstrainedWithSource<ManagedHooksRequirementsToml>>,
pub mcp_servers: Option<Sourced<BTreeMap<String, McpServerRequirement>>>,
@@ -122,6 +123,7 @@ impl Default for ConfigRequirements {
/*source*/ None,
),
allow_managed_hooks_only: None,
computer_use: None,
feature_requirements: None,
managed_hooks: None,
mcp_servers: None,
@@ -588,6 +590,17 @@ impl fmt::Display for WebSearchModeRequirement {
}
}
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
pub struct ComputerUseRequirementsToml {
pub allow_locked_computer_use: Option<bool>,
}
impl ComputerUseRequirementsToml {
pub fn is_empty(&self) -> bool {
self.allow_locked_computer_use.is_none()
}
}
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
pub struct FeatureRequirementsToml {
#[serde(flatten)]
@@ -691,6 +704,7 @@ pub struct ConfigRequirementsToml {
pub remote_sandbox_config: Option<Vec<RemoteSandboxConfigToml>>,
pub allowed_web_search_modes: Option<Vec<WebSearchModeRequirement>>,
pub allow_managed_hooks_only: Option<bool>,
pub computer_use: Option<ComputerUseRequirementsToml>,
#[serde(rename = "features", alias = "feature_requirements")]
pub feature_requirements: Option<FeatureRequirementsToml>,
pub hooks: Option<ManagedHooksRequirementsToml>,
@@ -740,6 +754,7 @@ pub struct ConfigRequirementsWithSources {
pub allowed_sandbox_modes: Option<Sourced<Vec<SandboxModeRequirement>>>,
pub allowed_web_search_modes: Option<Sourced<Vec<WebSearchModeRequirement>>>,
pub allow_managed_hooks_only: Option<Sourced<bool>>,
pub computer_use: Option<Sourced<ComputerUseRequirementsToml>>,
pub feature_requirements: Option<Sourced<FeatureRequirementsToml>>,
pub hooks: Option<Sourced<ManagedHooksRequirementsToml>>,
pub mcp_servers: Option<Sourced<BTreeMap<String, McpServerRequirement>>>,
@@ -777,6 +792,7 @@ impl ConfigRequirementsWithSources {
remote_sandbox_config: _,
allowed_web_search_modes: _,
allow_managed_hooks_only: _,
computer_use: _,
feature_requirements: _,
hooks: _,
mcp_servers: _,
@@ -807,6 +823,7 @@ impl ConfigRequirementsWithSources {
allowed_sandbox_modes,
allowed_web_search_modes,
allow_managed_hooks_only,
computer_use,
feature_requirements,
hooks,
mcp_servers,
@@ -835,6 +852,7 @@ impl ConfigRequirementsWithSources {
allowed_sandbox_modes,
allowed_web_search_modes,
allow_managed_hooks_only,
computer_use,
feature_requirements,
hooks,
mcp_servers,
@@ -853,6 +871,7 @@ impl ConfigRequirementsWithSources {
remote_sandbox_config: None,
allowed_web_search_modes: allowed_web_search_modes.map(|sourced| sourced.value),
allow_managed_hooks_only: allow_managed_hooks_only.map(|sourced| sourced.value),
computer_use: computer_use.map(|sourced| sourced.value),
feature_requirements: feature_requirements.map(|sourced| sourced.value),
hooks: hooks.map(|sourced| sourced.value),
mcp_servers: mcp_servers.map(|sourced| sourced.value),
@@ -937,6 +956,10 @@ impl ConfigRequirementsToml {
&& self.remote_sandbox_config.is_none()
&& self.allowed_web_search_modes.is_none()
&& self.allow_managed_hooks_only.is_none()
&& self
.computer_use
.as_ref()
.is_none_or(ComputerUseRequirementsToml::is_empty)
&& self
.feature_requirements
.as_ref()
@@ -975,6 +998,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
allowed_sandbox_modes,
allowed_web_search_modes,
allow_managed_hooks_only,
computer_use,
feature_requirements,
hooks,
mcp_servers,
@@ -1211,6 +1235,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
permission_profile,
web_search_mode,
allow_managed_hooks_only,
computer_use,
feature_requirements,
managed_hooks,
mcp_servers,
@@ -1279,6 +1304,7 @@ mod tests {
remote_sandbox_config: _,
allowed_web_search_modes,
allow_managed_hooks_only,
computer_use,
feature_requirements,
hooks,
mcp_servers,
@@ -1301,6 +1327,7 @@ mod tests {
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
allow_managed_hooks_only: allow_managed_hooks_only
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
computer_use: computer_use.map(|value| Sourced::new(value, RequirementSource::Unknown)),
feature_requirements: feature_requirements
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
hooks: hooks.map(|value| Sourced::new(value, RequirementSource::Unknown)),
@@ -1343,6 +1370,25 @@ mod tests {
Ok(())
}
#[test]
fn deserialize_computer_use_requirements() -> Result<()> {
let requirements: ConfigRequirementsToml = from_str(
r#"
[computer_use]
allow_locked_computer_use = false
"#,
)?;
assert_eq!(
requirements.computer_use,
Some(ComputerUseRequirementsToml {
allow_locked_computer_use: Some(false),
})
);
assert!(!requirements.is_empty());
Ok(())
}
#[test]
fn merge_unset_fields_copies_every_field_and_sets_sources() {
let mut target = ConfigRequirementsWithSources::default();
@@ -1362,6 +1408,9 @@ mod tests {
let feature_requirements = FeatureRequirementsToml {
entries: BTreeMap::from([("personality".to_string(), true)]),
};
let computer_use = ComputerUseRequirementsToml {
allow_locked_computer_use: Some(false),
};
let enforce_residency = ResidencyRequirement::Us;
let enforce_source = source.clone();
let guardian_policy_config = "Use the company-managed guardian policy.".to_string();
@@ -1375,6 +1424,7 @@ mod tests {
remote_sandbox_config: None,
allowed_web_search_modes: Some(allowed_web_search_modes.clone()),
allow_managed_hooks_only: Some(true),
computer_use: Some(computer_use.clone()),
feature_requirements: Some(feature_requirements.clone()),
hooks: None,
mcp_servers: None,
@@ -1409,6 +1459,7 @@ mod tests {
/*value*/ true,
enforce_source.clone(),
)),
computer_use: Some(Sourced::new(computer_use, enforce_source.clone())),
feature_requirements: Some(Sourced::new(
feature_requirements,
enforce_source.clone(),
@@ -1452,6 +1503,7 @@ mod tests {
allowed_sandbox_modes: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
feature_requirements: None,
hooks: None,
mcp_servers: None,
@@ -1500,6 +1552,7 @@ mod tests {
allowed_sandbox_modes: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
feature_requirements: None,
hooks: None,
mcp_servers: None,
+1
View File
@@ -39,6 +39,7 @@ pub use config_requirements::AppRequirementToml;
pub use config_requirements::AppToolRequirementToml;
pub use config_requirements::AppToolsRequirementsToml;
pub use config_requirements::AppsRequirementsToml;
pub use config_requirements::ComputerUseRequirementsToml;
pub use config_requirements::ConfigRequirements;
pub use config_requirements::ConfigRequirementsToml;
pub use config_requirements::ConfigRequirementsWithSources;
@@ -1098,6 +1098,7 @@ allowed_approval_policies = ["on-request"]
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
feature_requirements: None,
hooks: None,
mcp_servers: None,
@@ -1156,6 +1157,7 @@ allowed_approval_policies = ["on-request"]
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
feature_requirements: None,
hooks: None,
mcp_servers: None,
@@ -1363,6 +1365,7 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()>
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
feature_requirements: None,
hooks: None,
mcp_servers: None,
+2
View File
@@ -8636,6 +8636,7 @@ async fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset()
remote_sandbox_config: None,
allowed_web_search_modes: Some(vec![codex_config::WebSearchModeRequirement::Cached]),
allow_managed_hooks_only: None,
computer_use: None,
feature_requirements: None,
hooks: None,
mcp_servers: None,
@@ -9419,6 +9420,7 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s
remote_sandbox_config: None,
allowed_web_search_modes: None,
allow_managed_hooks_only: None,
computer_use: None,
feature_requirements: None,
hooks: None,
mcp_servers: None,
+1
View File
@@ -2426,6 +2426,7 @@ impl Config {
permission_profile: mut constrained_permission_profile,
web_search_mode: mut constrained_web_search_mode,
allow_managed_hooks_only: _,
computer_use: _,
feature_requirements,
managed_hooks: _,
mcp_servers,
+2
View File
@@ -700,6 +700,7 @@ mod tests {
remote_sandbox_config: None,
allowed_web_search_modes: Some(vec![WebSearchModeRequirement::Cached]),
allow_managed_hooks_only: Some(true),
computer_use: None,
guardian_policy_config: Some("Use the managed guardian policy.".to_string()),
feature_requirements: Some(FeatureRequirementsToml {
entries: BTreeMap::from([("guardian_approval".to_string(), true)]),
@@ -914,6 +915,7 @@ approval_policy = "never"
remote_sandbox_config: None,
allowed_web_search_modes: Some(Vec::new()),
allow_managed_hooks_only: None,
computer_use: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,