Surface skill permission profiles in zsh-fork exec approvals (#12753)

## Summary

- Preserve each skill’s raw permissions block as a permission_profile on
SkillMetadata during skill loading.
- Keep compiling that same metadata into the existing runtime
Permissions object, so current enforcement
    behavior stays intact.
- When zsh-fork intercepts execution of a script that belongs to a
skill, include the skill’s
    permission_profile in the exec approval request.
- This lets approval UIs show the extra filesystem access the skill
declared when prompting for approval.
This commit is contained in:
Celia Chen
2026-02-25 01:23:10 -08:00
committed by GitHub
parent c4ec6be4ab
commit 6a3233da64
10 changed files with 137 additions and 23 deletions

View File

@@ -2,6 +2,10 @@
use anyhow::Result;
use codex_core::features::Feature;
#[cfg(unix)]
use codex_protocol::models::FileSystemPermissions;
#[cfg(unix)]
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::Op;
@@ -45,6 +49,14 @@ description: {name} skill
Ok(script_path)
}
#[cfg(unix)]
fn write_skill_metadata(home: &Path, name: &str, contents: &str) -> Result<()> {
let metadata_dir = home.join("skills").join(name).join("agents");
fs::create_dir_all(&metadata_dir)?;
fs::write(metadata_dir.join("openai.yaml"), contents)?;
Ok(())
}
fn shell_command_arguments(command: &str) -> Result<String> {
Ok(serde_json::to_string(&json!({
"command": command,
@@ -441,6 +453,19 @@ async fn shell_zsh_fork_prompts_for_skill_script_execution() -> Result<()> {
let mut builder = test_codex()
.with_pre_build_hook(|home| {
write_skill_with_shell_script(home, "mbolin-test-skill", "hello-mbolin.sh").unwrap();
write_skill_metadata(
home,
"mbolin-test-skill",
r#"
permissions:
file_system:
read:
- "./data"
write:
- "./output"
"#,
)
.unwrap();
})
.with_config(move |config| {
config.features.enable(Feature::ShellTool);
@@ -493,6 +518,16 @@ async fn shell_zsh_fork_prompts_for_skill_script_execution() -> Result<()> {
};
assert_eq!(approval.call_id, tool_call_id);
assert_eq!(approval.command, vec![script_path_str.clone()]);
assert_eq!(
approval.additional_permissions,
Some(PermissionProfile {
file_system: Some(FileSystemPermissions {
read: Some(vec![PathBuf::from("./data")]),
write: Some(vec![PathBuf::from("./output")]),
}),
..Default::default()
})
);
test.codex
.submit(Op::ExecApproval {