Cameron Mcloughlinagent: Re-enable sandbox (#61711)12a19dc3 months agoHistory
125 lines · 4.9 KB · Rust
RawDownload
use std::path::{Path, PathBuf}; /// A sandbox writable-path grant, paired with the canonical target it was/// resolved to when it was approved.////// Persisting the resolved canonical path (rather than re-resolving the/// requested path by string at enforcement time) closes a symlink TOCTOU hole:/// the path that is actually enforced is the one that was vetted at approval/// time.#[derive(Clone, Debug, PartialEq, Eq)]pub struct GrantedWritePath {    /// The path exactly as the user/model requested it (untrusted; display/provenance).    pub requested: PathBuf,    /// The canonical, symlink-resolved target established when the grant was    /// approved. `None` for a legacy/hand-authored bare-string settings entry,    /// which is resolved fresh at enforcement time.    pub resolved: Option<PathBuf>,    /// Windows/WSL only: whether the canonical target lives on a Windows-hosted    /// (DrvFs) filesystem, whose sandbox-integrity guarantees are weaker than a    /// distro-native filesystem. Recorded at approval time (after the user was    /// shown the weaker-guarantee warning) so later commands can reuse the grant    /// without re-classifying it. Always `false` on other platforms and for    /// legacy/bare-string entries.    pub on_windows_fs: bool,} impl GrantedWritePath {    /// A grant from a bare requested path with no resolved canonical target    /// (resolved fresh at enforcement time).    pub fn from_requested(requested: PathBuf) -> Self {        Self {            requested,            resolved: None,            on_windows_fs: false,        }    }     /// A grant whose canonical target was resolved when it was approved. Assumes    /// a native (non-Windows-hosted) filesystem; use [`Self::resolved_on_fs`] to    /// record the Windows/WSL DrvFs classification.    pub fn resolved(requested: PathBuf, resolved: PathBuf) -> Self {        Self::resolved_on_fs(requested, resolved, false)    }     /// A grant whose canonical target was resolved when it was approved,    /// recording whether that target lives on a Windows-hosted (DrvFs)    /// filesystem.    pub fn resolved_on_fs(requested: PathBuf, resolved: PathBuf, on_windows_fs: bool) -> Self {        Self {            requested,            resolved: Some(resolved),            on_windows_fs,        }    }     /// The path used for lexical subtree/coverage/dedup logic: the resolved    /// canonical target when known (the real grant), otherwise the requested    /// path.    pub fn canonical_or_requested(&self) -> &Path {        self.resolved.as_deref().unwrap_or(&self.requested)    }} // Hand-written serde so grants round-trip with legacy persisted data: a bare// JSON string deserializes to `{ requested, resolved: None }`, and a `None`// resolved serializes back as a bare string. This mirrors the string-or-object// approach used by `GrantedWritePathContent` in `settings_content`.impl serde::Serialize for GrantedWritePath {    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {        match &self.resolved {            None => self.requested.serialize(serializer),            Some(resolved) => {                use serde::ser::SerializeStruct as _;                // Emit `on_windows_fs` only when set, so native grants (the                // common case, and every grant on non-Windows platforms) keep                // the compact two-field form and legacy rows round-trip.                let field_count = if self.on_windows_fs { 3 } else { 2 };                let mut state = serializer.serialize_struct("GrantedWritePath", field_count)?;                state.serialize_field("requested", &self.requested)?;                state.serialize_field("resolved", resolved)?;                if self.on_windows_fs {                    state.serialize_field("on_windows_fs", &self.on_windows_fs)?;                }                state.end()            }        }    }} impl<'de> serde::Deserialize<'de> for GrantedWritePath {    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {        #[derive(serde::Deserialize)]        struct Object {            requested: PathBuf,            #[serde(default)]            resolved: Option<PathBuf>,            #[serde(default)]            on_windows_fs: bool,        }         #[derive(serde::Deserialize)]        #[serde(untagged)]        enum StringOrObject {            String(PathBuf),            Object(Object),        }         Ok(match StringOrObject::deserialize(deserializer)? {            StringOrObject::String(requested) => Self {                requested,                resolved: None,                on_windows_fs: false,            },            StringOrObject::Object(Object {                requested,                resolved,                on_windows_fs,            }) => Self {                requested,                resolved,                on_windows_fs,            },        })    }}