fix: trust-gate project hooks and exec policies (#14718)

## Summary
- trust-gate project `.codex` layers consistently, including repos that
have `.codex/hooks.json` or `.codex/execpolicy/*.rules` but no
`.codex/config.toml`
- keep disabled project layers in the config stack so nested trusted
project layers still resolve correctly, while preventing hooks and exec
policies from loading until the project is trusted
- update app-server/TUI onboarding copy to make the trust boundary
explicit and add regressions for loader, hooks, exec-policy, and
onboarding coverage

## Security
Before this change, an untrusted repo could auto-load project hooks or
exec policies from `.codex/` as long as `config.toml` was absent. This
makes trust the single gate for project-local config, hooks, and exec
policies.

## Stack
- Parent of #15936

## Test
- cargo test -p codex-core without_config_toml

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
viyatb-oai
2026-04-17 17:56:58 -07:00
committed by GitHub
co-authored by Codex
parent 06f8ec54db
commit 370bed4bf4
12 changed files with 684 additions and 123 deletions
+11 -16
View File
@@ -280,21 +280,16 @@ fn project_config_warning(config: &Config) -> Option<ConfigWarningNotification>
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
) {
if !matches!(layer.name, ConfigLayerSource::Project { .. })
|| layer.disabled_reason.is_none()
{
let ConfigLayerSource::Project { dot_codex_folder } = &layer.name else {
continue;
}
if let ConfigLayerSource::Project { dot_codex_folder } = &layer.name {
disabled_folders.push((
dot_codex_folder.as_path().display().to_string(),
layer
.disabled_reason
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| "config.toml is disabled.".to_string()),
));
}
};
let Some(disabled_reason) = &layer.disabled_reason else {
continue;
};
disabled_folders.push((
dot_codex_folder.as_path().display().to_string(),
disabled_reason.clone(),
));
}
if disabled_folders.is_empty() {
@@ -302,8 +297,8 @@ fn project_config_warning(config: &Config) -> Option<ConfigWarningNotification>
}
let mut message = concat!(
"Project config.toml files are disabled in the following folders. ",
"Settings in those files are ignored, but skills and exec policies still load.\n",
"Project-local config, hooks, and exec policies are disabled in the following folders ",
"until the project is trusted, but skills still load.\n",
)
.to_string();
for (index, (folder, reason)) in disabled_folders.iter().enumerate() {