git-utils: centralize repository authority and trusted Git launch

This commit is contained in:
Chris Bookholt
2026-07-01 21:19:17 -07:00
parent 9a21b607e3
commit bcf026cbca
21 changed files with 4820 additions and 389 deletions
@@ -0,0 +1,161 @@
use std::io;
use std::path::Component;
use std::path::Path;
use std::path::PathBuf;
use same_file::Handle;
use crate::git_config::path_is_within;
use super::CandidateBoundary;
use super::RepositoryRouteBoundaries;
use super::classify_candidate;
use super::is_missing;
use super::route_walker::RouteObservationSnapshot;
use super::route_walker::observe_route;
#[cfg(windows)]
use super::windows_path_is_ambiguous;
impl RepositoryRouteBoundaries {
pub(crate) fn route_cancels_worktree_descendant(&self, route: &Path) -> io::Result<bool> {
if route_requires_fail_closed(route)? {
return Ok(true);
}
observation_cancels_worktree_descendant(&observe_route(route)?, self)
}
pub(crate) fn retained_route_is_untrusted(&self, route: &Path) -> io::Result<bool> {
if route_requires_fail_closed(route)? {
return Ok(true);
}
let observation = observe_route(route)?;
if observation_cancels_worktree_descendant(&observation, self)? {
return Ok(true);
}
// Unlike active metadata, retained registry routes are not rebound
// before every child. A symlink or junction entry below a worktree is
// therefore mutable authority even when it currently aliases metadata.
// Parent identity classification covers Windows Unicode case aliases.
for hop in &observation.symlink_hops {
if matches!(
classify_candidate(
&hop.parent,
&self.worktree_roots,
&self.metadata_dirs,
&self.worktree_identities,
&self.metadata_identities,
)?,
CandidateBoundary::ExactWorktree | CandidateBoundary::Worktree
) {
return Ok(true);
}
}
Ok(false)
}
}
fn observation_cancels_worktree_descendant(
observation: &RouteObservationSnapshot,
boundaries: &RepositoryRouteBoundaries,
) -> io::Result<bool> {
if raw_spelling_cancels_worktree_descendant(&observation.raw.spelling, boundaries)? {
return Ok(true);
}
for hop in &observation.symlink_hops {
if raw_spelling_cancels_worktree_descendant(&hop.target.spelling, boundaries)?
|| raw_spelling_cancels_worktree_descendant(&hop.projected.spelling, boundaries)?
{
return Ok(true);
}
}
Ok(false)
}
fn route_requires_fail_closed(route: &Path) -> io::Result<bool> {
#[cfg(windows)]
{
let route = route.to_str().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"non-UTF-8 Windows authority path",
)
})?;
Ok(windows_path_is_ambiguous(route))
}
#[cfg(not(windows))]
{
let _ = route;
Ok(false)
}
}
fn raw_spelling_cancels_worktree_descendant(
spelling: &Path,
boundaries: &RepositoryRouteBoundaries,
) -> io::Result<bool> {
let mut prefix = PathBuf::new();
for component in spelling.components() {
match component {
Component::ParentDir => {
if candidate_is_proper_worktree_descendant(
&prefix,
&boundaries.worktree_roots,
&boundaries.metadata_dirs,
&boundaries.worktree_identities,
&boundaries.metadata_identities,
)? {
return Ok(true);
}
prefix.pop();
}
Component::CurDir => {}
_ => prefix.push(component.as_os_str()),
}
}
Ok(false)
}
pub(super) fn candidate_is_proper_worktree_descendant(
path: &Path,
worktree_roots: &[PathBuf],
metadata_dirs: &[PathBuf],
worktree_identities: &[Handle],
metadata_identities: &[Handle],
) -> io::Result<bool> {
if metadata_dirs
.iter()
.any(|metadata| path_is_within(path, metadata))
{
return Ok(false);
}
// A worktree-controlled spelling cannot become trusted merely because a
// mutable symlink or junction currently aliases protected metadata. Keep
// genuinely metadata-spelled paths exempt above, but reject lexical
// worktree descendants before following identities.
if worktree_roots
.iter()
.any(|root| path_is_within(path, root) && !path_is_within(root, path))
{
return Ok(true);
}
let mut ancestor_identities = Vec::new();
for (depth, ancestor) in path.ancestors().enumerate() {
match std::fs::metadata(ancestor) {
Ok(metadata) if metadata.is_dir() => {}
Ok(_) => continue,
Err(error) if is_missing(&error) => continue,
Err(error) => return Err(error),
}
let identity = Handle::from_path(ancestor)?;
if metadata_identities.contains(&identity) {
return Ok(false);
}
ancestor_identities.push((depth, identity));
}
Ok(ancestor_identities
.into_iter()
.any(|(depth, identity)| depth > 0 && worktree_identities.contains(&identity)))
}
@@ -0,0 +1,221 @@
use std::collections::BTreeSet;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use codex_utils_absolute_path::AbsolutePathBuf;
#[cfg(windows)]
use super::windows_path::windows_path_is_ambiguous;
#[derive(Clone, Debug)]
pub(super) struct RawRouteObservation {
pub(super) spelling: PathBuf,
pub(super) normalized_prefixes: Vec<PathBuf>,
pub(super) normalized: PathBuf,
pub(super) projected: PathBuf,
}
#[derive(Clone, Debug)]
pub(super) struct SymlinkRouteObservation {
pub(super) entry: PathBuf,
pub(super) parent: PathBuf,
pub(super) target: RawRouteObservation,
pub(super) projected: RawRouteObservation,
}
#[derive(Clone, Debug)]
pub(super) struct RouteObservationSnapshot {
pub(super) raw: RawRouteObservation,
pub(super) symlink_hops: Vec<SymlinkRouteObservation>,
}
impl RouteObservationSnapshot {
pub(super) fn observed_paths(&self) -> Vec<PathBuf> {
let mut paths = Vec::new();
append_raw_observation_paths(&mut paths, &self.raw);
for hop in &self.symlink_hops {
push_unique_spelling(&mut paths, hop.parent.clone());
push_unique_spelling(&mut paths, hop.entry.clone());
append_raw_observation_paths(&mut paths, &hop.target);
append_raw_observation_paths(&mut paths, &hop.projected);
}
paths
}
}
#[derive(Clone, Debug)]
struct SymlinkRouteHop {
entry: PathBuf,
parent: PathBuf,
target: PathBuf,
projected: PathBuf,
}
pub(super) fn observe_route(path: &Path) -> io::Result<RouteObservationSnapshot> {
let raw = observe_raw_route(path)?;
let symlink_hops = symlink_route_hops(path)?
.into_iter()
.map(|hop| {
Ok(SymlinkRouteObservation {
entry: hop.entry,
parent: hop.parent,
target: observe_raw_route(&hop.target)?,
projected: observe_raw_route(&hop.projected)?,
})
})
.collect::<io::Result<Vec<_>>>()?;
Ok(RouteObservationSnapshot { raw, symlink_hops })
}
fn observe_raw_route(route: &Path) -> io::Result<RawRouteObservation> {
let base = route
.ancestors()
.last()
.ok_or_else(|| invalid_data("authority path has no root"))?;
let normalized = AbsolutePathBuf::resolve_path_against_base(route, base).into_path_buf();
let normalized_prefixes = route
.ancestors()
.map(|prefix| AbsolutePathBuf::resolve_path_against_base(prefix, base).into_path_buf())
.collect();
let projected = project_through_longest_existing_ancestor(route)?;
Ok(RawRouteObservation {
spelling: route.to_path_buf(),
normalized_prefixes,
normalized,
projected,
})
}
fn append_raw_observation_paths(paths: &mut Vec<PathBuf>, observation: &RawRouteObservation) {
for prefix in &observation.normalized_prefixes {
push_unique_spelling(paths, prefix.clone());
}
push_unique_spelling(paths, observation.projected.clone());
}
fn push_unique_spelling(paths: &mut Vec<PathBuf>, path: PathBuf) {
if !paths.iter().any(|existing| existing == &path) {
paths.push(path);
}
}
fn symlink_route_hops(path: &Path) -> io::Result<Vec<SymlinkRouteHop>> {
let mut hops = Vec::new();
let mut seen = BTreeSet::new();
collect_symlink_route_hops(path, 0, &mut seen, &mut hops)?;
Ok(hops)
}
fn collect_symlink_route_hops(
path: &Path,
depth: usize,
seen: &mut BTreeSet<PathBuf>,
hops: &mut Vec<SymlinkRouteHop>,
) -> io::Result<()> {
if depth > 40 {
return Err(invalid_data("authority path symlink cycle"));
}
if !seen.insert(path.to_path_buf()) {
return Ok(());
}
for ancestor in path.ancestors() {
match std::fs::symlink_metadata(ancestor) {
Ok(_) => match std::fs::read_link(ancestor) {
Ok(target) => {
let parent = ancestor
.parent()
.ok_or_else(|| invalid_data("authority symlink has no parent"))?;
let target = resolve_literal_path(target, parent);
#[cfg(windows)]
if target.to_str().is_none_or(windows_path_is_ambiguous) {
return Err(invalid_data("ambiguous Windows authority symlink"));
}
let suffix = path
.strip_prefix(ancestor)
.map_err(|_| invalid_data("failed to project authority symlink path"))?;
let projected = resolve_literal_path(suffix, &target);
hops.push(SymlinkRouteHop {
entry: ancestor.to_path_buf(),
parent: parent.to_path_buf(),
target,
projected: projected.clone(),
});
collect_symlink_route_hops(&projected, depth + 1, seen, hops)?;
}
Err(error) if error.kind() == io::ErrorKind::InvalidInput => {}
Err(error) => return Err(error),
},
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) => {}
Err(error) => return Err(error),
}
}
Ok(())
}
fn project_through_longest_existing_ancestor(path: &Path) -> io::Result<PathBuf> {
project_path(path, 0)
}
fn project_path(path: &Path, symlink_depth: usize) -> io::Result<PathBuf> {
if symlink_depth > 40 {
return Err(invalid_data("too many authority path symlinks"));
}
if let Ok(canonical) = std::fs::canonicalize(path) {
return Ok(canonical);
}
for ancestor in path.ancestors() {
if std::fs::symlink_metadata(ancestor).is_ok()
&& let Ok(target) = std::fs::read_link(ancestor)
{
let parent = ancestor
.parent()
.ok_or_else(|| invalid_data("authority symlink has no parent"))?;
let target = resolve_literal_path(target, parent);
#[cfg(windows)]
if target.to_str().is_none_or(windows_path_is_ambiguous) {
return Err(invalid_data("ambiguous Windows authority symlink"));
}
let suffix = path
.strip_prefix(ancestor)
.map_err(|_| invalid_data("failed to project authority path"))?;
return project_path(&resolve_literal_path(suffix, &target), symlink_depth + 1);
}
}
for ancestor in path.ancestors() {
match std::fs::canonicalize(ancestor) {
Ok(canonical) => {
let suffix = path
.strip_prefix(ancestor)
.map_err(|_| invalid_data("failed to project authority path"))?;
return Ok(
AbsolutePathBuf::resolve_path_against_base(suffix, canonical).into_path_buf(),
);
}
Err(error)
if matches!(
error.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) => {}
Err(error) => return Err(error),
}
}
Err(invalid_data("authority path has no existing ancestor"))
}
fn resolve_literal_path(path: impl AsRef<Path>, base: &Path) -> PathBuf {
let path = path.as_ref();
if path.is_absolute() {
path.to_path_buf()
} else {
base.join(path)
}
}
pub(super) fn invalid_data(message: &str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, message)
}
@@ -0,0 +1,69 @@
pub(crate) fn windows_path_is_ambiguous(path: &str) -> bool {
let path = path.replace('/', "\\");
if path.starts_with(r"\??\") {
return true;
}
let path = if path
.get(..8)
.is_some_and(|prefix| prefix.eq_ignore_ascii_case(r"\\?\UNC\"))
|| path
.get(..8)
.is_some_and(|prefix| prefix.eq_ignore_ascii_case(r"\\.\UNC\"))
{
format!(r"\\{}", &path[8..])
} else if path.starts_with(r"\\?\") || path.starts_with(r"\\.\") {
let path = &path[4..];
if !matches!(path.as_bytes(), [drive, b':', b'\\', ..] if drive.is_ascii_alphabetic()) {
return true;
}
path.to_string()
} else {
path
};
let bytes = path.as_bytes();
let remainder = if matches!(bytes, [drive, b':', separator, ..]
if drive.is_ascii_alphabetic() && *separator == b'\\')
{
&path[3..]
} else {
path.as_str()
};
if remainder.contains(':') {
return true;
}
remainder.split('\\').any(|component| {
!matches!(component, "." | "..")
&& (component.ends_with(['.', ' ']) || windows_reserved_component(component))
})
}
pub(crate) fn windows_authority_path_is_ambiguous(path: &str) -> bool {
windows_path_is_ambiguous(path)
|| path
.replace('/', "\\")
.split('\\')
.any(|component| component == "..")
}
fn windows_reserved_component(component: &str) -> bool {
let stem = component
.trim_end_matches(['.', ' '])
.split_once('.')
.map_or(component, |(stem, _)| stem);
stem.eq_ignore_ascii_case("AUX")
|| stem.eq_ignore_ascii_case("CON")
|| stem.eq_ignore_ascii_case("CONIN$")
|| stem.eq_ignore_ascii_case("CONOUT$")
|| stem.eq_ignore_ascii_case("NUL")
|| stem.eq_ignore_ascii_case("PRN")
|| ["COM", "LPT"].iter().any(|prefix| {
let Some(rest) = stem.get(3..) else {
return false;
};
let mut chars = rest.chars();
stem.get(..3)
.is_some_and(|candidate| candidate.eq_ignore_ascii_case(prefix))
&& matches!(chars.next(), Some('1'..='9' | '¹' | '²' | '³'))
&& chars.as_str().is_empty()
})
}