Compare commits

...

1 Commits

Author SHA1 Message Date
viyatb-oai
f7cd3a7e40 feat: support cloud managed config 2026-04-10 15:09:54 -07:00
17 changed files with 1100 additions and 6 deletions

View File

@@ -6920,6 +6920,15 @@
"null"
]
},
"apps": {
"additionalProperties": {
"$ref": "#/definitions/v2/ConfigRequirementsApp"
},
"type": [
"object",
"null"
]
},
"enforceResidency": {
"anyOf": [
{
@@ -6938,10 +6947,151 @@
"object",
"null"
]
},
"guardianPolicyConfig": {
"type": [
"string",
"null"
]
},
"mcpServers": {
"additionalProperties": {
"$ref": "#/definitions/v2/ConfigRequirementsMcpServer"
},
"type": [
"object",
"null"
]
},
"rules": {
"anyOf": [
{
"$ref": "#/definitions/v2/ConfigRequirementsRules"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"ConfigRequirementsApp": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"ConfigRequirementsMcpServer": {
"properties": {
"identity": {
"$ref": "#/definitions/v2/ConfigRequirementsMcpServerIdentity"
}
},
"required": [
"identity"
],
"type": "object"
},
"ConfigRequirementsMcpServerIdentity": {
"oneOf": [
{
"properties": {
"command": {
"type": "string"
},
"type": {
"enum": [
"command"
],
"title": "CommandConfigRequirementsMcpServerIdentityType",
"type": "string"
}
},
"required": [
"command",
"type"
],
"title": "CommandConfigRequirementsMcpServerIdentity",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"url"
],
"title": "UrlConfigRequirementsMcpServerIdentityType",
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"title": "UrlConfigRequirementsMcpServerIdentity",
"type": "object"
}
]
},
"ConfigRequirementsPatternToken": {
"properties": {
"anyOf": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"token": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"ConfigRequirementsPrefixRule": {
"properties": {
"decision": {
"anyOf": [
{
"$ref": "#/definitions/v2/ConfigRequirementsRuleDecision"
},
{
"type": "null"
}
]
},
"justification": {
"type": [
"string",
"null"
]
},
"pattern": {
"items": {
"$ref": "#/definitions/v2/ConfigRequirementsPatternToken"
},
"type": "array"
}
},
"required": [
"pattern"
],
"type": "object"
},
"ConfigRequirementsReadResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -6960,6 +7110,28 @@
"title": "ConfigRequirementsReadResponse",
"type": "object"
},
"ConfigRequirementsRuleDecision": {
"enum": [
"allow",
"prompt",
"forbidden"
],
"type": "string"
},
"ConfigRequirementsRules": {
"properties": {
"prefixRules": {
"items": {
"$ref": "#/definitions/v2/ConfigRequirementsPrefixRule"
},
"type": "array"
}
},
"required": [
"prefixRules"
],
"type": "object"
},
"ConfigValueWriteParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {

View File

@@ -3548,6 +3548,15 @@
"null"
]
},
"apps": {
"additionalProperties": {
"$ref": "#/definitions/ConfigRequirementsApp"
},
"type": [
"object",
"null"
]
},
"enforceResidency": {
"anyOf": [
{
@@ -3566,10 +3575,151 @@
"object",
"null"
]
},
"guardianPolicyConfig": {
"type": [
"string",
"null"
]
},
"mcpServers": {
"additionalProperties": {
"$ref": "#/definitions/ConfigRequirementsMcpServer"
},
"type": [
"object",
"null"
]
},
"rules": {
"anyOf": [
{
"$ref": "#/definitions/ConfigRequirementsRules"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"ConfigRequirementsApp": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"ConfigRequirementsMcpServer": {
"properties": {
"identity": {
"$ref": "#/definitions/ConfigRequirementsMcpServerIdentity"
}
},
"required": [
"identity"
],
"type": "object"
},
"ConfigRequirementsMcpServerIdentity": {
"oneOf": [
{
"properties": {
"command": {
"type": "string"
},
"type": {
"enum": [
"command"
],
"title": "CommandConfigRequirementsMcpServerIdentityType",
"type": "string"
}
},
"required": [
"command",
"type"
],
"title": "CommandConfigRequirementsMcpServerIdentity",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"url"
],
"title": "UrlConfigRequirementsMcpServerIdentityType",
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"title": "UrlConfigRequirementsMcpServerIdentity",
"type": "object"
}
]
},
"ConfigRequirementsPatternToken": {
"properties": {
"anyOf": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"token": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"ConfigRequirementsPrefixRule": {
"properties": {
"decision": {
"anyOf": [
{
"$ref": "#/definitions/ConfigRequirementsRuleDecision"
},
{
"type": "null"
}
]
},
"justification": {
"type": [
"string",
"null"
]
},
"pattern": {
"items": {
"$ref": "#/definitions/ConfigRequirementsPatternToken"
},
"type": "array"
}
},
"required": [
"pattern"
],
"type": "object"
},
"ConfigRequirementsReadResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
@@ -3588,6 +3738,28 @@
"title": "ConfigRequirementsReadResponse",
"type": "object"
},
"ConfigRequirementsRuleDecision": {
"enum": [
"allow",
"prompt",
"forbidden"
],
"type": "string"
},
"ConfigRequirementsRules": {
"properties": {
"prefixRules": {
"items": {
"$ref": "#/definitions/ConfigRequirementsPrefixRule"
},
"type": "array"
}
},
"required": [
"prefixRules"
],
"type": "object"
},
"ConfigValueWriteParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {

View File

@@ -88,6 +88,15 @@
"null"
]
},
"apps": {
"additionalProperties": {
"$ref": "#/definitions/ConfigRequirementsApp"
},
"type": [
"object",
"null"
]
},
"enforceResidency": {
"anyOf": [
{
@@ -106,10 +115,173 @@
"object",
"null"
]
},
"guardianPolicyConfig": {
"type": [
"string",
"null"
]
},
"mcpServers": {
"additionalProperties": {
"$ref": "#/definitions/ConfigRequirementsMcpServer"
},
"type": [
"object",
"null"
]
},
"rules": {
"anyOf": [
{
"$ref": "#/definitions/ConfigRequirementsRules"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"ConfigRequirementsApp": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"ConfigRequirementsMcpServer": {
"properties": {
"identity": {
"$ref": "#/definitions/ConfigRequirementsMcpServerIdentity"
}
},
"required": [
"identity"
],
"type": "object"
},
"ConfigRequirementsMcpServerIdentity": {
"oneOf": [
{
"properties": {
"command": {
"type": "string"
},
"type": {
"enum": [
"command"
],
"title": "CommandConfigRequirementsMcpServerIdentityType",
"type": "string"
}
},
"required": [
"command",
"type"
],
"title": "CommandConfigRequirementsMcpServerIdentity",
"type": "object"
},
{
"properties": {
"type": {
"enum": [
"url"
],
"title": "UrlConfigRequirementsMcpServerIdentityType",
"type": "string"
},
"url": {
"type": "string"
}
},
"required": [
"type",
"url"
],
"title": "UrlConfigRequirementsMcpServerIdentity",
"type": "object"
}
]
},
"ConfigRequirementsPatternToken": {
"properties": {
"anyOf": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"token": {
"type": [
"string",
"null"
]
}
},
"type": "object"
},
"ConfigRequirementsPrefixRule": {
"properties": {
"decision": {
"anyOf": [
{
"$ref": "#/definitions/ConfigRequirementsRuleDecision"
},
{
"type": "null"
}
]
},
"justification": {
"type": [
"string",
"null"
]
},
"pattern": {
"items": {
"$ref": "#/definitions/ConfigRequirementsPatternToken"
},
"type": "array"
}
},
"required": [
"pattern"
],
"type": "object"
},
"ConfigRequirementsRuleDecision": {
"enum": [
"allow",
"prompt",
"forbidden"
],
"type": "string"
},
"ConfigRequirementsRules": {
"properties": {
"prefixRules": {
"items": {
"$ref": "#/definitions/ConfigRequirementsPrefixRule"
},
"type": "array"
}
},
"required": [
"prefixRules"
],
"type": "object"
},
"NetworkDomainPermission": {
"enum": [
"allow",

View File

@@ -3,7 +3,10 @@
// 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 { ConfigRequirementsApp } from "./ConfigRequirementsApp";
import type { ConfigRequirementsMcpServer } from "./ConfigRequirementsMcpServer";
import type { ConfigRequirementsRules } from "./ConfigRequirementsRules";
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, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null};
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWebSearchModes: Array<WebSearchMode> | null, featureRequirements: { [key in string]?: boolean } | null, mcpServers: { [key in string]?: ConfigRequirementsMcpServer } | null, apps: { [key in string]?: ConfigRequirementsApp } | null, rules: ConfigRequirementsRules | null, enforceResidency: ResidencyRequirement | null, guardianPolicyConfig: string | null};

View File

@@ -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 ConfigRequirementsApp = { enabled: boolean | null, };

View File

@@ -0,0 +1,6 @@
// 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 { ConfigRequirementsMcpServerIdentity } from "./ConfigRequirementsMcpServerIdentity";
export type ConfigRequirementsMcpServer = { identity: ConfigRequirementsMcpServerIdentity, };

View File

@@ -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 ConfigRequirementsMcpServerIdentity = { "type": "command", command: string, } | { "type": "url", url: string, };

View File

@@ -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 ConfigRequirementsPatternToken = { token: string | null, anyOf: Array<string> | null, };

View File

@@ -0,0 +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 { ConfigRequirementsPatternToken } from "./ConfigRequirementsPatternToken";
import type { ConfigRequirementsRuleDecision } from "./ConfigRequirementsRuleDecision";
export type ConfigRequirementsPrefixRule = { pattern: Array<ConfigRequirementsPatternToken>, decision: ConfigRequirementsRuleDecision | null, justification: string | null, };

View File

@@ -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 ConfigRequirementsRuleDecision = "allow" | "prompt" | "forbidden";

View File

@@ -0,0 +1,6 @@
// 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 { ConfigRequirementsPrefixRule } from "./ConfigRequirementsPrefixRule";
export type ConfigRequirementsRules = { prefixRules: Array<ConfigRequirementsPrefixRule>, };

View File

@@ -66,7 +66,14 @@ export type { ConfigLayerSource } from "./ConfigLayerSource";
export type { ConfigReadParams } from "./ConfigReadParams";
export type { ConfigReadResponse } from "./ConfigReadResponse";
export type { ConfigRequirements } from "./ConfigRequirements";
export type { ConfigRequirementsApp } from "./ConfigRequirementsApp";
export type { ConfigRequirementsMcpServer } from "./ConfigRequirementsMcpServer";
export type { ConfigRequirementsMcpServerIdentity } from "./ConfigRequirementsMcpServerIdentity";
export type { ConfigRequirementsPatternToken } from "./ConfigRequirementsPatternToken";
export type { ConfigRequirementsPrefixRule } from "./ConfigRequirementsPrefixRule";
export type { ConfigRequirementsReadResponse } from "./ConfigRequirementsReadResponse";
export type { ConfigRequirementsRuleDecision } from "./ConfigRequirementsRuleDecision";
export type { ConfigRequirementsRules } from "./ConfigRequirementsRules";
export type { ConfigValueWriteParams } from "./ConfigValueWriteParams";
export type { ConfigWarningNotification } from "./ConfigWarningNotification";
export type { ConfigWriteResponse } from "./ConfigWriteResponse";

View File

@@ -860,9 +860,69 @@ pub struct ConfigRequirements {
pub allowed_sandbox_modes: Option<Vec<SandboxMode>>,
pub allowed_web_search_modes: Option<Vec<WebSearchMode>>,
pub feature_requirements: Option<BTreeMap<String, bool>>,
pub mcp_servers: Option<BTreeMap<String, ConfigRequirementsMcpServer>>,
pub apps: Option<BTreeMap<String, ConfigRequirementsApp>>,
pub rules: Option<ConfigRequirementsRules>,
pub enforce_residency: Option<ResidencyRequirement>,
#[experimental("configRequirements/read.network")]
pub network: Option<NetworkRequirements>,
pub guardian_policy_config: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigRequirementsMcpServer {
pub identity: ConfigRequirementsMcpServerIdentity,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum ConfigRequirementsMcpServerIdentity {
Command { command: String },
Url { url: String },
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigRequirementsApp {
pub enabled: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigRequirementsRules {
pub prefix_rules: Vec<ConfigRequirementsPrefixRule>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigRequirementsPrefixRule {
pub pattern: Vec<ConfigRequirementsPatternToken>,
pub decision: Option<ConfigRequirementsRuleDecision>,
pub justification: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ConfigRequirementsPatternToken {
pub token: Option<String>,
pub any_of: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "kebab-case")]
#[ts(rename_all = "kebab-case", export_to = "v2/")]
pub enum ConfigRequirementsRuleDecision {
Allow,
Prompt,
Forbidden,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -7514,8 +7574,12 @@ mod tests {
allowed_sandbox_modes: None,
allowed_web_search_modes: None,
feature_requirements: None,
mcp_servers: None,
apps: None,
rules: None,
enforce_residency: None,
network: None,
guardian_policy_config: None,
});
assert_eq!(reason, Some("askForApproval.granular"));

View File

@@ -197,7 +197,7 @@ Example with notification opt-out:
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home).
- `config/value/write` — write a single config key/value to the user's config.toml on disk.
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
- `configRequirements/read` — fetch loaded requirements constraints from cloud requirements, `requirements.toml`, and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), MCP/app requirements, rules, `guardianPolicyConfig`, `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
### Example: Start or resume a thread

View File

@@ -6,7 +6,14 @@ use codex_app_server_protocol::ConfigBatchWriteParams;
use codex_app_server_protocol::ConfigReadParams;
use codex_app_server_protocol::ConfigReadResponse;
use codex_app_server_protocol::ConfigRequirements;
use codex_app_server_protocol::ConfigRequirementsApp;
use codex_app_server_protocol::ConfigRequirementsMcpServer;
use codex_app_server_protocol::ConfigRequirementsMcpServerIdentity;
use codex_app_server_protocol::ConfigRequirementsPatternToken;
use codex_app_server_protocol::ConfigRequirementsPrefixRule;
use codex_app_server_protocol::ConfigRequirementsReadResponse;
use codex_app_server_protocol::ConfigRequirementsRuleDecision;
use codex_app_server_protocol::ConfigRequirementsRules;
use codex_app_server_protocol::ConfigValueWriteParams;
use codex_app_server_protocol::ConfigWriteErrorCode;
use codex_app_server_protocol::ConfigWriteResponse;
@@ -391,10 +398,85 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
feature_requirements: requirements
.feature_requirements
.map(|requirements| requirements.entries),
mcp_servers: requirements.mcp_servers.map(|servers| {
servers
.into_iter()
.map(|(name, requirement)| (name, map_mcp_server_requirement_to_api(requirement)))
.collect()
}),
apps: requirements.apps.map(|apps| {
apps.apps
.into_iter()
.map(|(app_id, requirement)| {
(
app_id,
ConfigRequirementsApp {
enabled: requirement.enabled,
},
)
})
.collect()
}),
rules: requirements.rules.map(map_requirements_rules_to_api),
enforce_residency: requirements
.enforce_residency
.map(map_residency_requirement_to_api),
network: requirements.network.map(map_network_requirements_to_api),
guardian_policy_config: requirements.guardian_policy_config,
}
}
fn map_mcp_server_requirement_to_api(
requirement: codex_core::config_loader::McpServerRequirement,
) -> ConfigRequirementsMcpServer {
ConfigRequirementsMcpServer {
identity: match requirement.identity {
codex_core::config_loader::McpServerIdentity::Command { command } => {
ConfigRequirementsMcpServerIdentity::Command { command }
}
codex_core::config_loader::McpServerIdentity::Url { url } => {
ConfigRequirementsMcpServerIdentity::Url { url }
}
},
}
}
fn map_requirements_rules_to_api(
rules: codex_config::RequirementsExecPolicyToml,
) -> ConfigRequirementsRules {
ConfigRequirementsRules {
prefix_rules: rules
.prefix_rules
.into_iter()
.map(|rule| ConfigRequirementsPrefixRule {
pattern: rule
.pattern
.into_iter()
.map(|token| ConfigRequirementsPatternToken {
token: token.token,
any_of: token.any_of,
})
.collect(),
decision: rule.decision.map(map_requirements_rule_decision_to_api),
justification: rule.justification,
})
.collect(),
}
}
fn map_requirements_rule_decision_to_api(
decision: codex_config::RequirementsExecPolicyDecisionToml,
) -> ConfigRequirementsRuleDecision {
match decision {
codex_config::RequirementsExecPolicyDecisionToml::Allow => {
ConfigRequirementsRuleDecision::Allow
}
codex_config::RequirementsExecPolicyDecisionToml::Prompt => {
ConfigRequirementsRuleDecision::Prompt
}
codex_config::RequirementsExecPolicyDecisionToml::Forbidden => {
ConfigRequirementsRuleDecision::Forbidden
}
}
}
@@ -564,16 +646,55 @@ mod tests {
allowed_web_search_modes: Some(vec![
codex_core::config_loader::WebSearchModeRequirement::Cached,
]),
guardian_policy_config: None,
guardian_policy_config: Some("Use managed guardian policy.".to_string()),
feature_requirements: Some(codex_core::config_loader::FeatureRequirementsToml {
entries: std::collections::BTreeMap::from([
("apps".to_string(), false),
("personality".to_string(), true),
]),
}),
mcp_servers: None,
apps: None,
rules: None,
mcp_servers: Some(std::collections::BTreeMap::from([
(
"docs".to_string(),
codex_core::config_loader::McpServerRequirement {
identity: codex_core::config_loader::McpServerIdentity::Command {
command: "codex-mcp".to_string(),
},
},
),
(
"remote".to_string(),
codex_core::config_loader::McpServerRequirement {
identity: codex_core::config_loader::McpServerIdentity::Url {
url: "https://example.com/mcp".to_string(),
},
},
),
])),
apps: Some(codex_core::config_loader::AppsRequirementsToml {
apps: std::collections::BTreeMap::from([(
"calendar".to_string(),
codex_core::config_loader::AppRequirementToml {
enabled: Some(false),
},
)]),
}),
rules: Some(codex_config::RequirementsExecPolicyToml {
prefix_rules: vec![codex_config::RequirementsExecPolicyPrefixRuleToml {
pattern: vec![
codex_config::RequirementsExecPolicyPatternTokenToml {
token: Some("rm".to_string()),
any_of: None,
},
codex_config::RequirementsExecPolicyPatternTokenToml {
token: None,
any_of: Some(vec!["-rf".to_string(), "-fr".to_string()]),
},
],
decision: Some(codex_config::RequirementsExecPolicyDecisionToml::Prompt),
justification: Some("Needs review".to_string()),
}],
}),
enforce_residency: Some(CoreResidencyRequirement::Us),
network: Some(CoreNetworkRequirementsToml {
enabled: Some(true),
@@ -637,6 +758,55 @@ mod tests {
("personality".to_string(), true),
])),
);
assert_eq!(
mapped.mcp_servers,
Some(std::collections::BTreeMap::from([
(
"docs".to_string(),
ConfigRequirementsMcpServer {
identity: ConfigRequirementsMcpServerIdentity::Command {
command: "codex-mcp".to_string(),
},
},
),
(
"remote".to_string(),
ConfigRequirementsMcpServer {
identity: ConfigRequirementsMcpServerIdentity::Url {
url: "https://example.com/mcp".to_string(),
},
},
),
])),
);
assert_eq!(
mapped.apps,
Some(std::collections::BTreeMap::from([(
"calendar".to_string(),
ConfigRequirementsApp {
enabled: Some(false),
},
)])),
);
assert_eq!(
mapped.rules,
Some(ConfigRequirementsRules {
prefix_rules: vec![ConfigRequirementsPrefixRule {
pattern: vec![
ConfigRequirementsPatternToken {
token: Some("rm".to_string()),
any_of: None,
},
ConfigRequirementsPatternToken {
token: None,
any_of: Some(vec!["-rf".to_string(), "-fr".to_string()]),
},
],
decision: Some(ConfigRequirementsRuleDecision::Prompt),
justification: Some("Needs review".to_string()),
}],
}),
);
assert_eq!(
mapped.enforce_residency,
Some(codex_app_server_protocol::ResidencyRequirement::Us),
@@ -666,6 +836,10 @@ mod tests {
allow_local_binding: Some(true),
}),
);
assert_eq!(
mapped.guardian_policy_config,
Some("Use managed guardian policy.".to_string()),
);
}
#[test]

View File

@@ -820,7 +820,24 @@ mod tests {
use super::*;
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use codex_config::RequirementsExecPolicyDecisionToml;
use codex_config::RequirementsExecPolicyPatternTokenToml;
use codex_config::RequirementsExecPolicyPrefixRuleToml;
use codex_config::RequirementsExecPolicyToml;
use codex_config::types::AuthCredentialsStoreMode;
use codex_core::config_loader::AppRequirementToml;
use codex_core::config_loader::AppsRequirementsToml;
use codex_core::config_loader::FeatureRequirementsToml;
use codex_core::config_loader::McpServerIdentity;
use codex_core::config_loader::McpServerRequirement;
use codex_core::config_loader::NetworkDomainPermissionToml;
use codex_core::config_loader::NetworkDomainPermissionsToml;
use codex_core::config_loader::NetworkRequirementsToml;
use codex_core::config_loader::NetworkUnixSocketPermissionToml;
use codex_core::config_loader::NetworkUnixSocketPermissionsToml;
use codex_core::config_loader::ResidencyRequirement;
use codex_core::config_loader::SandboxModeRequirement;
use codex_core::config_loader::WebSearchModeRequirement;
use codex_protocol::protocol::AskForApproval;
use pretty_assertions::assert_eq;
use serde_json::json;
@@ -1272,6 +1289,223 @@ mod tests {
);
}
#[tokio::test]
async fn fetch_cloud_requirements_parses_every_requirements_toml_field() {
let result = parse_for_fetch(Some(
r#"
allowed_approval_policies = ["never", "on-request"]
allowed_approvals_reviewers = ["user", "guardian_subagent"]
allowed_sandbox_modes = ["read-only", "workspace-write"]
allowed_web_search_modes = ["cached", "live"]
enforce_residency = "us"
guardian_policy_config = "Use managed guardian policy."
[features]
apps = false
personality = true
[mcp_servers.docs.identity]
command = "codex-mcp"
[mcp_servers.remote.identity]
url = "https://example.com/mcp"
[apps.calendar]
enabled = false
[rules]
prefix_rules = [
{ pattern = [{ token = "rm" }, { any_of = ["-rf", "-fr"] }], decision = "prompt", justification = "Needs review" },
]
[experimental_network]
enabled = true
allow_upstream_proxy = false
dangerously_allow_all_unix_sockets = true
managed_allowed_domains_only = true
danger_full_access_denylist_only = true
allow_local_binding = false
[experimental_network.domains]
"api.example.com" = "allow"
"blocked.example.com" = "deny"
[experimental_network.unix_sockets]
"/tmp/example.sock" = "allow"
"#,
))
.expect("cloud requirements should parse");
assert_eq!(
result,
ConfigRequirementsToml {
allowed_approval_policies: Some(vec![
AskForApproval::Never,
AskForApproval::OnRequest,
]),
allowed_approvals_reviewers: Some(vec![
codex_protocol::config_types::ApprovalsReviewer::User,
codex_protocol::config_types::ApprovalsReviewer::GuardianSubagent,
]),
allowed_sandbox_modes: Some(vec![
SandboxModeRequirement::ReadOnly,
SandboxModeRequirement::WorkspaceWrite,
]),
allowed_web_search_modes: Some(vec![
WebSearchModeRequirement::Cached,
WebSearchModeRequirement::Live,
]),
feature_requirements: Some(FeatureRequirementsToml {
entries: BTreeMap::from([
("apps".to_string(), false),
("personality".to_string(), true),
]),
}),
mcp_servers: Some(BTreeMap::from([
(
"docs".to_string(),
McpServerRequirement {
identity: McpServerIdentity::Command {
command: "codex-mcp".to_string(),
},
},
),
(
"remote".to_string(),
McpServerRequirement {
identity: McpServerIdentity::Url {
url: "https://example.com/mcp".to_string(),
},
},
),
])),
apps: Some(AppsRequirementsToml {
apps: BTreeMap::from([(
"calendar".to_string(),
AppRequirementToml {
enabled: Some(false),
},
)]),
}),
rules: Some(RequirementsExecPolicyToml {
prefix_rules: vec![RequirementsExecPolicyPrefixRuleToml {
pattern: vec![
RequirementsExecPolicyPatternTokenToml {
token: Some("rm".to_string()),
any_of: None,
},
RequirementsExecPolicyPatternTokenToml {
token: None,
any_of: Some(vec!["-rf".to_string(), "-fr".to_string()]),
},
],
decision: Some(RequirementsExecPolicyDecisionToml::Prompt),
justification: Some("Needs review".to_string()),
}],
}),
enforce_residency: Some(ResidencyRequirement::Us),
network: Some(NetworkRequirementsToml {
enabled: Some(true),
http_port: None,
socks_port: None,
allow_upstream_proxy: Some(false),
dangerously_allow_non_loopback_proxy: None,
dangerously_allow_all_unix_sockets: Some(true),
domains: Some(NetworkDomainPermissionsToml {
entries: BTreeMap::from([
(
"api.example.com".to_string(),
NetworkDomainPermissionToml::Allow,
),
(
"blocked.example.com".to_string(),
NetworkDomainPermissionToml::Deny,
),
]),
}),
managed_allowed_domains_only: Some(true),
danger_full_access_denylist_only: Some(true),
unix_sockets: Some(NetworkUnixSocketPermissionsToml {
entries: BTreeMap::from([(
"/tmp/example.sock".to_string(),
NetworkUnixSocketPermissionToml::Allow,
)]),
}),
allow_local_binding: Some(false),
}),
guardian_policy_config: Some("Use managed guardian policy.".to_string()),
},
);
}
#[tokio::test]
async fn fetch_cloud_requirements_parses_legacy_network_lists() {
let result = parse_for_fetch(Some(
r#"
[experimental_network]
enabled = true
danger_full_access_denylist_only = true
allow_local_binding = true
denied_domains = [
"pastebin.com",
"*.pastebin.com",
"**.ngrok-free.dev",
]
allowed_domains = [
"127.0.0.1",
"localhost",
"::1",
"*.openai.com",
"region*.v2.argotunnel.com",
]
"#,
))
.expect("cloud requirements should parse legacy network requirements");
assert_eq!(
result.network,
Some(NetworkRequirementsToml {
enabled: Some(true),
http_port: None,
socks_port: None,
allow_upstream_proxy: None,
dangerously_allow_non_loopback_proxy: None,
dangerously_allow_all_unix_sockets: None,
domains: Some(NetworkDomainPermissionsToml {
entries: BTreeMap::from([
(
"**.ngrok-free.dev".to_string(),
NetworkDomainPermissionToml::Deny,
),
(
"*.openai.com".to_string(),
NetworkDomainPermissionToml::Allow,
),
(
"*.pastebin.com".to_string(),
NetworkDomainPermissionToml::Deny,
),
("127.0.0.1".to_string(), NetworkDomainPermissionToml::Allow),
("::1".to_string(), NetworkDomainPermissionToml::Allow),
("localhost".to_string(), NetworkDomainPermissionToml::Allow,),
(
"pastebin.com".to_string(),
NetworkDomainPermissionToml::Deny,
),
(
"region*.v2.argotunnel.com".to_string(),
NetworkDomainPermissionToml::Allow,
),
]),
}),
managed_allowed_domains_only: None,
danger_full_access_denylist_only: Some(true),
unix_sockets: None,
allow_local_binding: Some(true),
}),
);
}
#[tokio::test]
async fn fetch_cloud_requirements_parses_apps_requirements_toml() {
let result = parse_for_fetch(Some(

View File

@@ -10,6 +10,10 @@ use crate::config_loader::ConfigLoadError;
use crate::config_loader::ConfigRequirements;
use crate::config_loader::ConfigRequirementsToml;
use crate::config_loader::ConfigRequirementsWithSources;
use crate::config_loader::NetworkConstraints;
use crate::config_loader::NetworkDomainPermissionToml;
use crate::config_loader::NetworkDomainPermissionsToml;
use crate::config_loader::NetworkRequirementsToml;
use crate::config_loader::RequirementSource;
use crate::config_loader::load_requirements_toml;
use crate::config_loader::version_for_toml;
@@ -763,6 +767,59 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()>
Ok(())
}
#[tokio::test]
async fn load_config_layers_includes_cloud_network_requirements() -> anyhow::Result<()> {
let tmp = tempdir()?;
let codex_home = tmp.path().join("home");
tokio::fs::create_dir_all(&codex_home).await?;
let cwd = AbsolutePathBuf::from_absolute_path(tmp.path())?;
let network = NetworkRequirementsToml {
enabled: Some(true),
domains: Some(NetworkDomainPermissionsToml {
entries: BTreeMap::from([
(
"*.openai.com".to_string(),
NetworkDomainPermissionToml::Allow,
),
(
"pastebin.com".to_string(),
NetworkDomainPermissionToml::Deny,
),
]),
}),
danger_full_access_denylist_only: Some(true),
allow_local_binding: Some(true),
..Default::default()
};
let expected_constraints = NetworkConstraints::from(network.clone());
let requirements = ConfigRequirementsToml {
network: Some(network.clone()),
..Default::default()
};
let cloud_requirements = CloudRequirementsLoader::new(async move { Ok(Some(requirements)) });
let layers = load_config_layers_state(
&codex_home,
Some(cwd),
&[] as &[(String, TomlValue)],
LoaderOverrides::default(),
cloud_requirements,
)
.await?;
assert_eq!(layers.requirements_toml().network, Some(network));
assert_eq!(
layers.requirements().network,
Some(crate::config_loader::Sourced::new(
expected_constraints,
RequirementSource::CloudRequirements,
)),
);
Ok(())
}
#[tokio::test]
async fn load_config_layers_fails_when_cloud_requirements_loader_fails() -> anyhow::Result<()> {
let tmp = tempdir()?;