Files
codex/codex-rs/config/src/path_context.rs
T
Sean Huang 9e22e74e8d Resolve permission profiles with explicit execution-host path context (#44676)
## Why

Permission paths need to follow the execution host's path conventions and home directory. Literal directory names containing glob syntax must not change the meaning of deny patterns, and profile availability checks need to account for configured workspace roots.

## What changed

- Use `ConfigPathContext` to compile built-in and custom profiles, returning the resolved profile and deduplicated `PathUri` workspace roots. Materialize configured roots while retaining runtime workspace symbols.
- Use the same compiler for configuration loading, persisted profile validation, and profile catalogs. Resolve roots against the requested `cwd` when listing profiles.
- Resolve home-relative scoped rules using the supplied home directory and reject unsafe directory prefixes when constructing globs.
- Share workspace-root materialization across native paths and URIs. Deny the affected root when a workspace glob cannot be safely resolved, and clear grants for legacy home-relative workspace denials whose target is unknown.

## Testing

Add coverage for POSIX, Windows, and UNC path resolution, inherited workspace roots, scoped home denials, missing home context, and conservative denial behavior for unsafe globs. Add an app-server test verifying that profile availability reflects the requested `cwd`.

GitOrigin-RevId: ca259434742365c16d0b72629cabfbab41513a80
2026-09-11 00:03:40 +00:00

116 lines
3.7 KiB
Rust

//! Path facts for configuration parsing. Native callers and callers resolving
//! another platform use the same resolver after fact capture.
use codex_utils_absolute_path::AbsolutePathBufGuard;
use codex_utils_path_uri::LegacyAppPathStringError;
use codex_utils_path_uri::PathConvention;
use codex_utils_path_uri::PathUri;
use std::cell::RefCell;
/// The owning environment's path convention, base directory, and home for configuration.
#[derive(Clone, Debug)]
pub struct ConfigPathContext {
convention: PathConvention,
base_dir: Option<PathUri>,
user_home_dir: Option<PathUri>,
}
impl ConfigPathContext {
/// Supplies the grammar and directories of the environment owning the layer.
/// Relative paths require a base; home-relative paths require a home directory.
pub fn new(
convention: PathConvention,
base_dir: Option<PathUri>,
user_home_dir: Option<PathUri>,
) -> Self {
Self {
convention,
base_dir,
user_home_dir,
}
}
/// Returns the path grammar supplied by the owning environment.
pub fn convention(&self) -> PathConvention {
self.convention
}
/// Resolves configuration text to a URI using the supplied directory and home facts.
pub fn resolve_path(&self, input: &str) -> Result<PathUri, LegacyAppPathStringError> {
PathUri::resolve_config_path_uri(
input,
self.convention,
self.base_dir.as_ref(),
self.user_home_dir.as_ref(),
)
}
/// Resolves against a supplied base using this context's grammar and home.
pub fn resolve_against(
&self,
input: &str,
base: &PathUri,
) -> Result<PathUri, LegacyAppPathStringError> {
PathUri::resolve_config_path_uri(
input,
self.convention,
Some(base),
self.user_home_dir.as_ref(),
)
}
pub(crate) fn enter(&self) -> PathContextGuard {
PathContextGuard(PATH_CONTEXT.with(|current| current.replace(Some(self.clone()))))
}
}
thread_local! {
static PATH_CONTEXT: RefCell<Option<ConfigPathContext>> = const { RefCell::new(None) };
}
pub(crate) struct PathContextGuard(Option<ConfigPathContext>);
impl Drop for PathContextGuard {
fn drop(&mut self) {
PATH_CONTEXT.with(|current| *current.borrow_mut() = self.0.take());
}
}
pub(crate) fn convention() -> PathConvention {
PATH_CONTEXT.with(|current| {
current
.borrow()
.as_ref()
.map_or_else(PathConvention::native, |context| context.convention)
})
}
pub(crate) fn resolve(input: &str) -> Result<String, String> {
let context = match PATH_CONTEXT.with(|current| current.borrow().clone()) {
Some(context) => context,
None => {
let convention = PathConvention::native();
let base = AbsolutePathBufGuard::deserialization_base(std::path::Path::new(input))
.map_err(str::to_owned)?
.map(PathUri::from_host_native_path)
.transpose()
.map_err(|error| error.to_string())?;
let home = convention
.home_relative_suffix(input)
.and_then(|_| AbsolutePathBufGuard::home_directory())
.map(PathUri::from_host_native_path)
.transpose()
.map_err(|error| error.to_string())?;
ConfigPathContext::new(convention, base, home)
}
};
context
.resolve_path(input)
.and_then(|path| path.to_config_path_string(context.convention()))
.map_err(|error| error.to_string())
}
#[cfg(test)]
#[path = "path_context_tests.rs"]
mod tests;