mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Enforce filesystem permissions when loading AGENTS.md (#39653)
## Why Project instructions must respect the selected environment's filesystem read permissions. Tightening those permissions for a later turn must not allow previously cached instructions to reach the model. ## What changed - Apply each environment's filesystem sandbox while discovering and reading `AGENTS.md` files. - Fail thread or turn setup when sandboxing blocks a discovered instruction file, while allowing a restricted project with no instructions to start. - Clear cached instructions before refresh and include the Windows sandbox level in the cache key. - Ignore inaccessible ancestor marker probes so readable instructions in the selected working directory can still load. ## Testing Added coverage for restricted projects, denied instruction files, cache invalidation after permissions tighten, and unreadable ancestor markers. GitOrigin-RevId: 6ea1a27b9c873a6260e4f87d42ae1317a6a4ae4b
This commit is contained in:
1 parent
ab82cddd04
commit
7ece061767
12 files changed
+407
-60
No files matched your search
@@ -18,14 +18,17 @@
|
||||
use crate::config::Config;
|
||||
use crate::context::UserInstructions as ContextUserInstructions;
|
||||
use crate::environment_selection::TurnEnvironmentSnapshot;
|
||||
use crate::tools::sandboxing::executor_windows_sandbox_level;
|
||||
use codex_config::ConfigLayerSource;
|
||||
use codex_config::default_project_root_markers;
|
||||
use codex_config::merge_toml_values;
|
||||
use codex_config::project_root_markers_from_config;
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_extension_api::UserInstructions;
|
||||
use codex_file_system::FileSystemSandboxContext;
|
||||
use codex_file_system::FindUpErrorPolicy;
|
||||
use codex_file_system::find_nearest_ancestor_with_markers;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use futures::StreamExt;
|
||||
@@ -53,7 +56,8 @@ pub(crate) async fn load_project_instructions(
|
||||
config: &Config,
|
||||
user_instructions: Option<UserInstructions>,
|
||||
environments: &TurnEnvironmentSnapshot,
|
||||
) -> Option<LoadedAgentsMd> {
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
) -> io::Result<Option<LoadedAgentsMd>> {
|
||||
let mut loaded = LoadedAgentsMd::from_user_instructions(user_instructions);
|
||||
let mut remaining = config.project_doc_max_bytes;
|
||||
for turn_environment in environments.turn_environments() {
|
||||
@@ -62,12 +66,31 @@ pub(crate) async fn load_project_instructions(
|
||||
}
|
||||
|
||||
let filesystem = turn_environment.environment.get_filesystem();
|
||||
let sandbox = (!turn_environment
|
||||
.permission_profile()
|
||||
.file_system_sandbox_policy()
|
||||
.has_full_disk_read_access())
|
||||
.then(|| {
|
||||
// TODO(anp): Move sandbox context construction to a method on TurnEnvironment.
|
||||
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
|
||||
turn_environment.permission_profile().clone(),
|
||||
turn_environment.cwd().clone(),
|
||||
);
|
||||
sandbox.workspace_roots = turn_environment.workspace_roots().to_vec();
|
||||
sandbox.windows_sandbox_level =
|
||||
executor_windows_sandbox_level(windows_sandbox_level, turn_environment.cwd());
|
||||
sandbox.windows_sandbox_private_desktop =
|
||||
config.permissions.windows_sandbox_private_desktop;
|
||||
sandbox.use_legacy_landlock = config.features.use_legacy_landlock();
|
||||
sandbox
|
||||
});
|
||||
match read_agents_md(
|
||||
config,
|
||||
filesystem.as_ref(),
|
||||
&turn_environment.selection.environment_id,
|
||||
turn_environment.cwd(),
|
||||
remaining,
|
||||
sandbox.as_ref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -78,16 +101,25 @@ pub(crate) async fn load_project_instructions(
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
Err(error) if sandbox.is_none() => {
|
||||
error!(
|
||||
environment_id = turn_environment.selection.environment_id,
|
||||
"error trying to find AGENTS.md docs: {e:#}"
|
||||
"error trying to find AGENTS.md docs: {error:#}"
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
return Err(io::Error::new(
|
||||
error.kind(),
|
||||
format!(
|
||||
"failed to load AGENTS.md instructions for environment `{}`: {error}",
|
||||
turn_environment.selection.environment_id
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(!loaded.is_empty()).then_some(loaded)
|
||||
Ok((!loaded.is_empty()).then_some(loaded))
|
||||
}
|
||||
|
||||
/// Attempt to locate and load AGENTS.md documentation.
|
||||
@@ -102,12 +134,13 @@ async fn read_agents_md(
|
||||
environment_id: &str,
|
||||
cwd: &PathUri,
|
||||
max_total: usize,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> io::Result<Option<LoadedAgentsMd>> {
|
||||
if max_total == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let paths = agents_md_paths(config, cwd, fs).await?;
|
||||
let paths = agents_md_paths(config, cwd, fs, sandbox).await?;
|
||||
if paths.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -120,7 +153,7 @@ async fn read_agents_md(
|
||||
break;
|
||||
}
|
||||
|
||||
let mut data = match fs.read_file(&p, /*sandbox*/ None).await {
|
||||
let mut data = match fs.read_file(&p, sandbox).await {
|
||||
Ok(data) => data,
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
|
||||
Err(err) => return Err(err),
|
||||
@@ -165,6 +198,7 @@ async fn agents_md_paths(
|
||||
config: &Config,
|
||||
cwd: &PathUri,
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> io::Result<Vec<PathUri>> {
|
||||
let dir = cwd.clone();
|
||||
|
||||
@@ -187,8 +221,8 @@ async fn agents_md_paths(
|
||||
fs,
|
||||
&dir,
|
||||
project_root_markers,
|
||||
FindUpErrorPolicy::Propagate,
|
||||
/*sandbox*/ None,
|
||||
FindUpErrorPolicy::Ignore,
|
||||
sandbox,
|
||||
)
|
||||
.await?;
|
||||
let search_dirs = if let Some(root) = project_root {
|
||||
@@ -218,7 +252,7 @@ async fn agents_md_paths(
|
||||
let candidate = directory
|
||||
.join(name)
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
|
||||
match fs.get_metadata(&candidate, /*sandbox*/ None).await {
|
||||
match fs.get_metadata(&candidate, sandbox).await {
|
||||
Ok(metadata) if metadata.is_file => return Ok(Some(candidate)),
|
||||
Ok(_) => {}
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {}
|
||||
|
||||
Reference in new issue
Block a user