Keep step settings and approval environments consistent (#46556)

## Why

Background processes can outlive their launching turn. Their approval reviews need the originating environment's filesystem restrictions, even when the active turn uses a different environment. Remote restrictions must also be resolved using executor paths rather than the local filesystem.

## What changed

- Capture settings and environment selections together in `StepInputs`. Preserve the captured selection during active model updates while allowing environment startup to finish.
- Carry environment IDs into Guardian network and `execve` requests, and use the owning process's environment with current review settings. Refresh environment readiness for network requests that fall back to the active turn.
- Resolve remote denied paths and globs using the executor's policy context. Decline automatic approval when the request's environment is unavailable or an explicitly denied temporary directory cannot be resolved.

## Testing

Add regression coverage for active model updates retaining their environment while the next turn adopts a new selection, background approvals using their owning environment, newly ready network environments, and remote denied paths and globs, including Windows paths and missing temporary-directory metadata.

GitOrigin-RevId: b620d4b030c597a4f430fe0500864fa5469db3d4
This commit is contained in:
sayan-oai
2026-09-19 01:27:26 +00:00
committed by copyberry
parent 328feb0c29
commit c7828dd010
44 changed files with 872 additions and 255 deletions
@@ -1,6 +1,8 @@
//! Executor-context policy operations that never consult controller paths or environment.
use super::FileSystemAccessMode;
use super::FileSystemPath;
use super::FileSystemSandboxKind;
use super::FileSystemSandboxPolicy;
use super::FileSystemSandboxPolicyContext;
use super::FileSystemSpecialPath;
@@ -8,8 +10,10 @@ use super::InvalidDenyReadGlobBehavior;
use super::ReadDenyMatcher;
use super::deny_read_validator::DenyReadValidator;
use super::deny_read_validator::has_context_read_denials;
use super::file_system_root;
use codex_utils_path_uri::PathConvention;
use codex_utils_path_uri::PathUri;
use std::collections::HashSet;
const INVALID_PATH: &str = "managed filesystem denial cannot be materialized safely";
const SHADOWED: &str = "managed filesystem denial is shadowed by a selected profile grant";
@@ -73,6 +77,45 @@ impl FileSystemSandboxPolicy {
.with_materialized_project_roots_for_path_uris(roots)
}
/// Returns explicitly denied roots using only the selected executor's paths.
/// A filesystem-root denial is omitted so it does not obscure narrower readable paths.
/// Fails rather than omit a denied temporary directory the executor did not report.
pub fn get_unreadable_roots_with_context(
&self,
context: &FileSystemSandboxPolicyContext<'_>,
) -> Result<Vec<PathUri>, String> {
if !matches!(self.kind, FileSystemSandboxKind::Restricted) {
return Ok(Vec::new());
}
if context.temporary_directories.is_none()
&& self.entries.iter().any(|entry| {
entry.access == FileSystemAccessMode::Deny
&& matches!(
&entry.path,
FileSystemPath::Special {
value: FileSystemSpecialPath::Tmpdir
}
)
})
{
return Err("executor did not report its denied temporary directories".into());
}
let filesystem_root = file_system_root(context);
let mut seen = HashSet::new();
Ok(self
.resolved_entries(context)
.into_iter()
.filter(|(path, access)| {
*access == FileSystemAccessMode::Deny
&& !filesystem_root
.as_ref()
.is_some_and(|root| path.starts_with(root) && root.starts_with(path))
&& seen.insert(path.clone())
})
.map(|(path, _)| path)
.collect())
}
/// Resolves deny glob text using the execution host's convention and home.
pub fn get_unreadable_globs_with_context(
&self,
@@ -43,6 +43,10 @@ fn read_denials_use_target_paths_and_native_glob_semantics() {
let matcher = ReadDenyMatcher::try_new_with_context(&policy, &context)
.unwrap()
.unwrap();
assert_eq!(
policy.get_unreadable_roots_with_context(&context),
Ok(vec![cwd.join("private").unwrap()]),
);
for (path, expected) in [
("private", true),
("private/file", true),
@@ -208,6 +212,17 @@ fn malformed_or_unresolvable_denials_fail_closed() {
path: uri("file:///C:/private"),
})]);
assert!(ReadDenyMatcher::try_new_with_context(&policy, &context).is_err());
let policy = FileSystemSandboxPolicy::restricted(vec![deny(FileSystemPath::Special {
value: FileSystemSpecialPath::Tmpdir,
})]);
assert!(
policy
.get_unreadable_roots_with_context(&FileSystemSandboxPolicyContext {
temporary_directories: None,
..context
})
.is_err()
);
}
#[test]