Add allow_managed_hooks_only hook requirement (#20319)

## Why

Enterprise-managed hook policy needs a narrow way to require Codex to
ignore user-controlled lifecycle hooks without adopting the broader
trust-precedence model from earlier hook work. This keeps the policy
anchored in `requirements.toml`, so admins can opt into managed hooks
only while normal `config.toml` files cannot enable the restriction
themselves.

## What changed

- Added `allow_managed_hooks_only` to the requirements data flow and
preserved explicit `false` values.
- Also adds it to /debug-config
- Marked MDM, system, and legacy managed config layers as managed for
hook discovery.
- Updated hook discovery so `allow_managed_hooks_only = true`:
  - keeps managed requirements hooks and managed config-layer hooks,
- skips user/project/session `hooks.json` and `[hooks]` entries with
concise startup warnings,
  - skips current unmanaged plugin hooks,
- ignores any `allow_managed_hooks_only` key placed in ordinary
`config.toml` layers.
This commit is contained in:
Andrei Eternal
2026-05-12 19:05:25 -07:00
committed by GitHub
parent fbfbfe5fc5
commit 913aad4d3c
17 changed files with 650 additions and 68 deletions
+18
View File
@@ -145,6 +145,17 @@ fn render_debug_config_lines(stack: &ConfigLayerStack) -> Vec<Line<'static>> {
));
}
if let Some(allow_managed_hooks_only) = requirements_toml.allow_managed_hooks_only {
requirement_lines.push(requirement_line(
"allow_managed_hooks_only",
allow_managed_hooks_only.to_string(),
requirements
.allow_managed_hooks_only
.as_ref()
.map(|sourced| &sourced.source),
));
}
if requirements_toml.guardian_policy_config.is_some() {
requirement_lines.push(requirement_line(
"guardian_policy_config",
@@ -647,6 +658,10 @@ mod tests {
Constrained::allow_any(WebSearchMode::Cached),
Some(RequirementSource::CloudRequirements),
),
allow_managed_hooks_only: Some(Sourced::new(
/*value*/ true,
RequirementSource::CloudRequirements,
)),
feature_requirements: Some(Sourced::new(
FeatureRequirementsToml {
entries: BTreeMap::from([("guardian_approval".to_string(), true)]),
@@ -684,6 +699,7 @@ mod tests {
allowed_sandbox_modes: Some(vec![SandboxModeRequirement::ReadOnly]),
remote_sandbox_config: None,
allowed_web_search_modes: Some(vec![WebSearchModeRequirement::Cached]),
allow_managed_hooks_only: Some(true),
guardian_policy_config: Some("Use the managed guardian policy.".to_string()),
feature_requirements: Some(FeatureRequirementsToml {
entries: BTreeMap::from([("guardian_approval".to_string(), true)]),
@@ -741,6 +757,7 @@ mod tests {
"allowed_web_search_modes: cached, disabled (source: cloud requirements)"
)
);
assert!(rendered.contains("allow_managed_hooks_only: true (source: cloud requirements)"));
assert!(
rendered.contains("guardian_policy_config: configured (source: cloud requirements)")
);
@@ -893,6 +910,7 @@ approval_policy = "never"
allowed_sandbox_modes: None,
remote_sandbox_config: None,
allowed_web_search_modes: Some(Vec::new()),
allow_managed_hooks_only: None,
guardian_policy_config: None,
feature_requirements: None,
hooks: None,