{"record":{"id":"844a484b783cba11","repo":"BoundaryML/baml","slug":"archive-contains-unsafe-path-path","errorCode":null,"errorMessage":"archive contains unsafe path {path}","messagePattern":"archive contains unsafe path (.+?)","errorType":"exception","errorClass":"FetchError","httpStatus":null,"severity":"critical","filePath":"baml_language/crates/baml_release/src/lib.rs","lineNumber":99,"sourceCode":"    HttpStatus {\n        url: String,\n        status: reqwest::StatusCode,\n    },\n    #[error(\"manifest 404 for version {version} (not released yet?)\")]\n    ManifestNotFound { version: String },\n    #[error(\"manifest schema {got} not supported (max {max}); run `baml self-update`\")]\n    ManifestSchemaTooNew { got: u32, max: u32 },\n    #[error(\"target {target} not built for version {version}\")]\n    TargetNotInManifest { target: String, version: String },\n    #[error(\"sha256 mismatch for {url}: expected {expected}, got {got}\")]\n    ChecksumMismatch {\n        url: String,\n        expected: String,\n        got: String,\n    },\n    #[error(\"archive missing expected binary {name}\")]\n    BinaryNotInArchive { name: String },\n    #[error(\"archive contains unsafe path {path}\")]\n    UnsafeArchivePath { path: String },\n    #[error(\"disk error: {0}\")]\n    Io(#[from] std::io::Error),\n    #[error(\"zip archive error: {0}\")]\n    Zip(#[from] zip::result::ZipError),\n}\n\n#[derive(Debug, Clone)]\npub struct Fetcher {\n    pub spec: ReleaseSpec,\n    pub product: Product,\n    pub manifest_base_url: String,\n    pub release_repo: String,\n    artifact: Option<Artifact>,\n}\n\nimpl Fetcher {\n    pub fn default_for(spec: ReleaseSpec, product: Product) -> Self {","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/baml_language/crates/baml_release/src/lib.rs#L81-L117","documentation":"FetchError::UnsafeArchivePath is thrown by baml_release when an entry inside the downloaded zip archive has an unsafe path (e.g. absolute paths, `..` traversal components, or paths escaping the extraction directory). This is a zip-slip protection: extracting such entries could write files outside the intended install directory.","triggerScenarios":"Extracting a release archive that contains an entry name like `/etc/passwd`, `../../.bashrc`, or a Windows drive path — the extractor refuses before writing anything.","commonSituations":"A tampered or malicious artifact (which checksum validation should also catch), hand-crafted archives used with local manifest overrides in testing, or a packaging bug generating entry names with parent components.","solutions":["Do not attempt to bypass this check — it indicates a dangerous archive","Verify the artifact's checksum against the official manifest; if it also fails, the download is tampered","If you control packaging, fix the packager to emit plain relative entry names","Report the artifact immediately as potentially compromised"],"exampleFix":"// before (naive extraction, zip-slip risk)\nfor f in zip.entries() { f.extract(&dest)?; }\n// after (library already rejects; reject at your layer too)\nfor f in zip.entries() {\n    let name = f.name();\n    if name.contains(\"..\") || Path::new(name).is_absolute() {\n        bail!(\"unsafe archive path {name}\");\n    }\n    f.extract(&dest)?;\n}","handlingStrategy":"validation","validationCode":"fn archive_entries_safe(names: &[&str]) -> bool {\n    names.iter().all(|n| {\n        let p = std::path::Path::new(n);\n        !p.is_absolute() && !n.split('/').any(|c| c == \"..\")\n    })\n}","typeGuard":"fn is_unsafe_path(e: &FetchError) -> bool {\n    matches!(e, FetchError::UnsafeArchivePath { .. })\n}","tryCatchPattern":"match install(v) {\n    Err(FetchError::UnsafeArchivePath { path }) => {\n        error!(\"archive contains unsafe path {path}; refusing install — possible tampering\");\n        std::process::exit(1);\n    }\n    other => other,\n}","preventionTips":["Never disable zip-slip/path checks in the extractor","Verify checksums before extraction so tampering is caught earlier","Reject archives with absolute or `..` entry names at ingestion","Treat this error as a security signal, not a transient failure"],"tags":["security","zip","path-traversal"],"backgroundTag":"path-traversal-blocked","analyzedSha":"bd85ce9dee1463ff04d27efd20531013a4ff46c1","analyzedAt":"2026-09-12T03:38:25.718Z","contentChangedAt":"2026-09-12T03:38:25.718Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}