Preserve filesystem authority when loading skills (#59279)

## Why

Skill discovery and metadata loading used raw filesystem handles with unrestricted reads, and cache keys distinguished filesystems without accounting for captured permissions.

## What changed

- Pass borrowed `EnvironmentAccess` accessors through skill root resolution, discovery, canonicalization, and metadata reads.
- Include captured permissions in skill snapshot and empty remote root cache keys.
- Add fixed-path watches to `EnvironmentAccess`, falling back to live discovery when read restrictions prevent using unsandboxed executor watches.
- Keep user and plugin roots under host filesystem authority independently of the selected repository executor.

## Testing

Extend remote root cache coverage to verify that changing captured permissions triggers a fresh scan and that restricted accessors decline unsandboxed watches.

GitOrigin-RevId: 97d4bfdfcba78a93fc8845773c6397d4364494e3
This commit is contained in:
Dmitriy Kovalenko
2026-11-04 01:13:54 +00:00
committed by copyberry
parent afefd0ba95
commit 526f6099f6
25 changed files with 403 additions and 273 deletions
+5 -1
View File
@@ -684,7 +684,11 @@ enabled = false
let snapshot = skills_service
.snapshot_for_config(
&skills_input,
Some(Arc::clone(&codex_exec_server::LOCAL_FS)),
Some(
&codex_exec_server::FileSystemEnvironmentAccessor::unrestricted(
&codex_exec_server::LOCAL_FS,
),
),
)
.await;
let outcome = snapshot.outcome();
@@ -77,7 +77,7 @@ enabled = false
plugins_manager.plugin_skill_snapshots_for_config(&plugins_input),
);
let snapshot = skills_service
.snapshot_for_config(&skills_input, /*fs*/ None)
.snapshot_for_config(&skills_input, /*repository*/ None)
.await;
assert_eq!(
+9 -1
View File
@@ -689,8 +689,16 @@ async fn warm_plugins_and_skills_for_session_init(
effective_skill_roots,
turn_environments,
);
let access = fs
.as_ref()
.map(codex_exec_server::FileSystemEnvironmentAccessor::unrestricted);
skills_service
.snapshot_for_config(&skills_input, fs)
.snapshot_for_config(
&skills_input,
access
.as_ref()
.map(|access| access as &dyn codex_file_system::EnvironmentAccess),
)
.await
.outcome()
.errors
+8 -2
View File
@@ -7196,7 +7196,10 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
let skills_snapshot = session
.services
.skills_service
.snapshot_for_config(&skills_input, Some(Arc::clone(&skill_fs)))
.snapshot_for_config(
&skills_input,
Some(&codex_exec_server::FileSystemEnvironmentAccessor::unrestricted(&skill_fs)),
)
.await;
let turn_context = Session::make_turn_context(
thread_id,
@@ -9531,7 +9534,10 @@ where
let skills_snapshot = session
.services
.skills_service
.snapshot_for_config(&skills_input, Some(Arc::clone(&skill_fs)))
.snapshot_for_config(
&skills_input,
Some(&codex_exec_server::FileSystemEnvironmentAccessor::unrestricted(&skill_fs)),
)
.await;
let turn_context = Arc::new(Session::make_turn_context(
thread_id,
+9 -1
View File
@@ -1364,9 +1364,17 @@ impl Session {
effective_skill_roots,
&turn_environments,
);
let access = fs
.as_ref()
.map(codex_exec_server::FileSystemEnvironmentAccessor::unrestricted);
self.services
.skills_service
.snapshot_for_config(&skills_input, fs)
.snapshot_for_config(
&skills_input,
access
.as_ref()
.map(|access| access as &dyn EnvironmentAccess),
)
.await
};
let step_settings = Arc::new(ResolvedStepSettings::new(
+1 -1
View File
@@ -1598,7 +1598,7 @@ async fn curated_plugin_skills_follow_auth_switch() -> Result<()> {
let skills_snapshot = test_codex
.thread_manager
.skills_service()
.snapshot_for_config(&skills_input, /*fs*/ None)
.snapshot_for_config(&skills_input, /*repository*/ None)
.await;
skills_snapshot
.outcome()
@@ -922,7 +922,7 @@ async fn repository_skill_cache_cannot_read_inaccessible_files(
Vec::new(),
test.config.config_layer_stack.clone(),
),
Some(Arc::clone(&file_system)),
Some(&open),
)
.await;
let [revoked_skill, gated_skill] = ["repo-revoked", "repo-gated"].map(|name| {