Skip to content

Commit a16d990

Browse files
committed
feat: add managed hooks lockdown
1 parent e046cba commit a16d990

File tree

19 files changed

+278
-4
lines changed

19 files changed

+278
-4
lines changed

codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6727,6 +6727,12 @@
67276727
},
67286728
"ConfigRequirements": {
67296729
"properties": {
6730+
"allowManagedHooksOnly": {
6731+
"type": [
6732+
"boolean",
6733+
"null"
6734+
]
6735+
},
67306736
"allowedApprovalPolicies": {
67316737
"items": {
67326738
"$ref": "#/definitions/v2/AskForApproval"

codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3386,6 +3386,12 @@
33863386
},
33873387
"ConfigRequirements": {
33883388
"properties": {
3389+
"allowManagedHooksOnly": {
3390+
"type": [
3391+
"boolean",
3392+
"null"
3393+
]
3394+
},
33893395
"allowedApprovalPolicies": {
33903396
"items": {
33913397
"$ref": "#/definitions/AskForApproval"

codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@
5353
},
5454
"ConfigRequirements": {
5555
"properties": {
56+
"allowManagedHooksOnly": {
57+
"type": [
58+
"boolean",
59+
"null"
60+
]
61+
},
5662
"allowedApprovalPolicies": {
5763
"items": {
5864
"$ref": "#/definitions/AskForApproval"

codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirements.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ import type { AskForApproval } from "./AskForApproval";
66
import type { ResidencyRequirement } from "./ResidencyRequirement";
77
import type { SandboxMode } from "./SandboxMode";
88

9-
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWebSearchModes: Array<WebSearchMode> | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null};
9+
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};

codex-rs/app-server-protocol/src/protocol/v2.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,7 @@ pub struct ConfigRequirements {
850850
pub allowed_approval_policies: Option<Vec<AskForApproval>>,
851851
pub allowed_sandbox_modes: Option<Vec<SandboxMode>>,
852852
pub allowed_web_search_modes: Option<Vec<WebSearchMode>>,
853+
pub allow_managed_hooks_only: Option<bool>,
853854
pub feature_requirements: Option<BTreeMap<String, bool>>,
854855
pub enforce_residency: Option<ResidencyRequirement>,
855856
#[experimental("configRequirements/read.network")]
@@ -7050,6 +7051,7 @@ mod tests {
70507051
}]),
70517052
allowed_sandbox_modes: None,
70527053
allowed_web_search_modes: None,
7054+
allow_managed_hooks_only: None,
70537055
feature_requirements: None,
70547056
enforce_residency: None,
70557057
network: None,

codex-rs/app-server/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ Example with notification opt-out:
194194
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home).
195195
- `config/value/write` — write a single config key/value to the user's config.toml on disk.
196196
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads.
197-
- `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.
197+
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), `allowManagedHooksOnly`, pinned feature values (`featureRequirements`), `enforceResidency`, and `network` constraints.
198198

199199
### Example: Start or resume a thread
200200

codex-rs/app-server/src/config_api.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
380380
}
381381
normalized
382382
}),
383+
allow_managed_hooks_only: requirements.allow_managed_hooks_only,
383384
feature_requirements: requirements
384385
.feature_requirements
385386
.map(|requirements| requirements.entries),
@@ -487,6 +488,7 @@ mod tests {
487488
allowed_web_search_modes: Some(vec![
488489
codex_core::config_loader::WebSearchModeRequirement::Cached,
489490
]),
491+
allow_managed_hooks_only: Some(true),
490492
guardian_developer_instructions: None,
491493
feature_requirements: Some(codex_core::config_loader::FeatureRequirementsToml {
492494
entries: std::collections::BTreeMap::from([
@@ -530,6 +532,7 @@ mod tests {
530532
mapped.allowed_web_search_modes,
531533
Some(vec![WebSearchMode::Cached, WebSearchMode::Disabled]),
532534
);
535+
assert_eq!(mapped.allow_managed_hooks_only, Some(true));
533536
assert_eq!(
534537
mapped.feature_requirements,
535538
Some(std::collections::BTreeMap::from([
@@ -564,6 +567,7 @@ mod tests {
564567
allowed_approval_policies: None,
565568
allowed_sandbox_modes: None,
566569
allowed_web_search_modes: Some(Vec::new()),
570+
allow_managed_hooks_only: None,
567571
guardian_developer_instructions: None,
568572
feature_requirements: None,
569573
mcp_servers: None,

codex-rs/config/src/config_requirements.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ pub struct ConfigRequirements {
7979
pub approval_policy: ConstrainedWithSource<AskForApproval>,
8080
pub sandbox_policy: ConstrainedWithSource<SandboxPolicy>,
8181
pub web_search_mode: ConstrainedWithSource<WebSearchMode>,
82+
pub allow_managed_hooks_only: Option<Sourced<bool>>,
8283
pub feature_requirements: Option<Sourced<FeatureRequirementsToml>>,
8384
pub mcp_servers: Option<Sourced<BTreeMap<String, McpServerRequirement>>>,
8485
pub exec_policy: Option<Sourced<RequirementsExecPolicy>>,
@@ -102,6 +103,7 @@ impl Default for ConfigRequirements {
102103
Constrained::allow_any(WebSearchMode::Cached),
103104
/*source*/ None,
104105
),
106+
allow_managed_hooks_only: None,
105107
feature_requirements: None,
106108
mcp_servers: None,
107109
exec_policy: None,
@@ -291,6 +293,7 @@ pub struct ConfigRequirementsToml {
291293
pub allowed_approval_policies: Option<Vec<AskForApproval>>,
292294
pub allowed_sandbox_modes: Option<Vec<SandboxModeRequirement>>,
293295
pub allowed_web_search_modes: Option<Vec<WebSearchModeRequirement>>,
296+
pub allow_managed_hooks_only: Option<bool>,
294297
#[serde(rename = "features", alias = "feature_requirements")]
295298
pub feature_requirements: Option<FeatureRequirementsToml>,
296299
pub mcp_servers: Option<BTreeMap<String, McpServerRequirement>>,
@@ -329,6 +332,7 @@ pub struct ConfigRequirementsWithSources {
329332
pub allowed_approval_policies: Option<Sourced<Vec<AskForApproval>>>,
330333
pub allowed_sandbox_modes: Option<Sourced<Vec<SandboxModeRequirement>>>,
331334
pub allowed_web_search_modes: Option<Sourced<Vec<WebSearchModeRequirement>>>,
335+
pub allow_managed_hooks_only: Option<Sourced<bool>>,
332336
pub feature_requirements: Option<Sourced<FeatureRequirementsToml>>,
333337
pub mcp_servers: Option<Sourced<BTreeMap<String, McpServerRequirement>>>,
334338
pub apps: Option<Sourced<AppsRequirementsToml>>,
@@ -360,6 +364,7 @@ impl ConfigRequirementsWithSources {
360364
allowed_approval_policies: _,
361365
allowed_sandbox_modes: _,
362366
allowed_web_search_modes: _,
367+
allow_managed_hooks_only: _,
363368
feature_requirements: _,
364369
mcp_servers: _,
365370
apps: _,
@@ -385,6 +390,7 @@ impl ConfigRequirementsWithSources {
385390
allowed_approval_policies,
386391
allowed_sandbox_modes,
387392
allowed_web_search_modes,
393+
allow_managed_hooks_only,
388394
feature_requirements,
389395
mcp_servers,
390396
rules,
@@ -408,6 +414,7 @@ impl ConfigRequirementsWithSources {
408414
allowed_approval_policies,
409415
allowed_sandbox_modes,
410416
allowed_web_search_modes,
417+
allow_managed_hooks_only,
411418
feature_requirements,
412419
mcp_servers,
413420
apps,
@@ -420,6 +427,7 @@ impl ConfigRequirementsWithSources {
420427
allowed_approval_policies: allowed_approval_policies.map(|sourced| sourced.value),
421428
allowed_sandbox_modes: allowed_sandbox_modes.map(|sourced| sourced.value),
422429
allowed_web_search_modes: allowed_web_search_modes.map(|sourced| sourced.value),
430+
allow_managed_hooks_only: allow_managed_hooks_only.map(|sourced| sourced.value),
423431
feature_requirements: feature_requirements.map(|sourced| sourced.value),
424432
mcp_servers: mcp_servers.map(|sourced| sourced.value),
425433
apps: apps.map(|sourced| sourced.value),
@@ -470,6 +478,7 @@ impl ConfigRequirementsToml {
470478
self.allowed_approval_policies.is_none()
471479
&& self.allowed_sandbox_modes.is_none()
472480
&& self.allowed_web_search_modes.is_none()
481+
&& self.allow_managed_hooks_only.is_none()
473482
&& self
474483
.feature_requirements
475484
.as_ref()
@@ -497,6 +506,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
497506
allowed_approval_policies,
498507
allowed_sandbox_modes,
499508
allowed_web_search_modes,
509+
allow_managed_hooks_only,
500510
feature_requirements,
501511
mcp_servers,
502512
apps: _apps,
@@ -682,6 +692,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
682692
approval_policy,
683693
sandbox_policy,
684694
web_search_mode,
695+
allow_managed_hooks_only,
685696
feature_requirements,
686697
mcp_servers,
687698
exec_policy,
@@ -718,6 +729,7 @@ mod tests {
718729
allowed_approval_policies,
719730
allowed_sandbox_modes,
720731
allowed_web_search_modes,
732+
allow_managed_hooks_only,
721733
feature_requirements,
722734
mcp_servers,
723735
apps,
@@ -733,6 +745,8 @@ mod tests {
733745
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
734746
allowed_web_search_modes: allowed_web_search_modes
735747
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
748+
allow_managed_hooks_only: allow_managed_hooks_only
749+
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
736750
feature_requirements: feature_requirements
737751
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
738752
mcp_servers: mcp_servers.map(|value| Sourced::new(value, RequirementSource::Unknown)),
@@ -774,6 +788,7 @@ mod tests {
774788
allowed_approval_policies: Some(allowed_approval_policies.clone()),
775789
allowed_sandbox_modes: Some(allowed_sandbox_modes.clone()),
776790
allowed_web_search_modes: Some(allowed_web_search_modes.clone()),
791+
allow_managed_hooks_only: Some(true),
777792
feature_requirements: Some(feature_requirements.clone()),
778793
mcp_servers: None,
779794
apps: None,
@@ -797,6 +812,7 @@ mod tests {
797812
allowed_web_search_modes,
798813
enforce_source.clone(),
799814
)),
815+
allow_managed_hooks_only: Some(Sourced::new(true, enforce_source.clone())),
800816
feature_requirements: Some(Sourced::new(
801817
feature_requirements,
802818
enforce_source.clone(),
@@ -838,6 +854,7 @@ mod tests {
838854
)),
839855
allowed_sandbox_modes: None,
840856
allowed_web_search_modes: None,
857+
allow_managed_hooks_only: None,
841858
feature_requirements: None,
842859
mcp_servers: None,
843860
apps: None,
@@ -881,6 +898,7 @@ mod tests {
881898
)),
882899
allowed_sandbox_modes: None,
883900
allowed_web_search_modes: None,
901+
allow_managed_hooks_only: None,
884902
feature_requirements: None,
885903
mcp_servers: None,
886904
apps: None,

codex-rs/config/src/state.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ impl ConfigLayerEntry {
7474
self.disabled_reason.is_some()
7575
}
7676

77+
/// Returns true for config layers controlled by managed policy sources.
78+
pub fn is_managed(&self) -> bool {
79+
matches!(
80+
self.name,
81+
ConfigLayerSource::Mdm { .. }
82+
| ConfigLayerSource::System { .. }
83+
| ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. }
84+
| ConfigLayerSource::LegacyManagedConfigTomlFromMdm
85+
)
86+
}
87+
7788
pub fn raw_toml(&self) -> Option<&str> {
7889
self.raw_toml.as_deref()
7990
}

codex-rs/core/src/config/config_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4889,6 +4889,7 @@ fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset() -> any
48894889
allowed_web_search_modes: Some(vec![
48904890
crate::config_loader::WebSearchModeRequirement::Cached,
48914891
]),
4892+
allow_managed_hooks_only: None,
48924893
feature_requirements: None,
48934894
mcp_servers: None,
48944895
apps: None,
@@ -5489,6 +5490,7 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s
54895490
allowed_approval_policies: None,
54905491
allowed_sandbox_modes: Some(vec![crate::config_loader::SandboxModeRequirement::ReadOnly]),
54915492
allowed_web_search_modes: None,
5493+
allow_managed_hooks_only: None,
54925494
feature_requirements: None,
54935495
mcp_servers: None,
54945496
apps: None,

0 commit comments

Comments
 (0)