add --dangerously-bypass-hook-trust CLI flag (#21768)

# Why

Hook trust happens through the TUI in `/hooks` so it can block
non-interactive use cases. This flag will allow users that are using
codex headlessly to bypass hooks when they want to.

# What

This adds one invocation-scoped escape hatch.

- the CLI flag sets a runtime-only `bypass_hook_trust` override; there
is no durable `config.toml` setting
- hook discovery still respects normal enablement, so explicitly
disabled hooks remain disabled
- we show a `--dangerously-bypass-hook-trust is enabled. Enabled hooks
may run without review for this invocation.` message on startup so
accidental use is visible in both interactive and exec flows

This keeps “enabled” and “trusted” as separate concepts in the normal
path, while giving CI/E2E callers a stable way to opt into the
exceptional path when they already control the hook set.
This commit is contained in:
Abhinav authored and GitHub committed 2026-05-13 07:13:57 +00:00
1 parent 934a40c7d9
commit 392e94e9ea
16 files changed
+252 -16

No files matched your search

+27
View File
@@ -7322,6 +7322,7 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
startup_warnings: Vec::new(),
history: History::default(),
ephemeral: false,
bypass_hook_trust: false,
file_opener: UriBasedFileOpener::VsCode,
codex_self_exe: None,
codex_linux_sandbox_exe: None,
@@ -7769,6 +7770,7 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
startup_warnings: Vec::new(),
history: History::default(),
ephemeral: false,
bypass_hook_trust: false,
file_opener: UriBasedFileOpener::VsCode,
codex_self_exe: None,
codex_linux_sandbox_exe: None,
@@ -7930,6 +7932,7 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
startup_warnings: Vec::new(),
history: History::default(),
ephemeral: false,
bypass_hook_trust: false,
file_opener: UriBasedFileOpener::VsCode,
codex_self_exe: None,
codex_linux_sandbox_exe: None,
@@ -8076,6 +8079,7 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
startup_warnings: Vec::new(),
history: History::default(),
ephemeral: false,
bypass_hook_trust: false,
file_opener: UriBasedFileOpener::VsCode,
codex_self_exe: None,
codex_linux_sandbox_exe: None,
@@ -8973,6 +8977,29 @@ async fn active_profile_is_cleared_when_requirements_force_fallback() -> std::io
Ok(())
}
#[tokio::test]
async fn bypass_hook_trust_adds_startup_warning() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let config = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.harness_overrides(ConfigOverrides {
bypass_hook_trust: Some(true),
..Default::default()
})
.build()
.await?;
assert!(
config.startup_warnings.iter().any(|warning| warning
== "`--dangerously-bypass-hook-trust` is enabled. Enabled hooks may run without review for this invocation."),
"{:?}",
config.startup_warnings
);
Ok(())
}
#[tokio::test]
async fn permission_profile_override_preserves_split_write_roots() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
+16
View File
@@ -668,6 +668,11 @@ pub struct Config {
/// When true, session is not persisted on disk. Default to `false`
pub ephemeral: bool,
/// Whether enabled hooks should run without requiring persisted hook trust for this session.
///
/// This is a runtime-only knob populated from invocation overrides, not from config files.
pub bypass_hook_trust: bool,
/// Optional URI-based file opener. If set, citations to files in the model
/// output will be hyperlinked using the specified URI scheme.
pub file_opener: UriBasedFileOpener,
@@ -1886,6 +1891,7 @@ pub struct ConfigOverrides {
pub show_raw_agent_reasoning: Option<bool>,
pub tools_web_search_request: Option<bool>,
pub ephemeral: Option<bool>,
pub bypass_hook_trust: Option<bool>,
/// Additional directories that should be treated as writable roots for this session.
pub additional_writable_roots: Vec<PathBuf>,
}
@@ -2169,8 +2175,17 @@ impl Config {
show_raw_agent_reasoning,
tools_web_search_request: override_tools_web_search_request,
ephemeral,
bypass_hook_trust,
additional_writable_roots,
} = overrides;
let bypass_hook_trust = bypass_hook_trust.unwrap_or_default();
if bypass_hook_trust {
startup_warnings.push(
"`--dangerously-bypass-hook-trust` is enabled. Enabled hooks may run without review for this invocation."
.to_string(),
);
}
if sandbox_mode.is_some() && permission_profile.is_some() {
return Err(std::io::Error::new(
@@ -3103,6 +3118,7 @@ impl Config {
config_layer_stack,
history,
ephemeral: ephemeral.unwrap_or_default(),
bypass_hook_trust,
file_opener: cfg.file_opener.unwrap_or(UriBasedFileOpener::VsCode),
codex_self_exe,
codex_linux_sandbox_exe,
+1
View File
@@ -3362,6 +3362,7 @@ async fn build_hooks_for_config(
Hooks::new(HooksConfig {
legacy_notify_argv: config.notify.clone(),
feature_enabled: config.features.enabled(Feature::CodexHooks),
bypass_hook_trust: config.bypass_hook_trust,
config_layer_stack: Some(config.config_layer_stack.clone()),
plugin_hook_sources,
plugin_hook_load_warnings,