BoundaryML/baml · critical · FetchError

archive contains unsafe path

Error message

archive contains unsafe path {path}

What it means

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.

Solutions

  1. Do not attempt to bypass this check — it indicates a dangerous archive
  2. Verify the artifact's checksum against the official manifest; if it also fails, the download is tampered
  3. If you control packaging, fix the packager to emit plain relative entry names
  4. Report the artifact immediately as potentially compromised

Example fix

// before (naive extraction, zip-slip risk)
for f in zip.entries() { f.extract(&dest)?; }
// after (library already rejects; reject at your layer too)
for f in zip.entries() {
    let name = f.name();
    if name.contains("..") || Path::new(name).is_absolute() {
        bail!("unsafe archive path {name}");
    }
    f.extract(&dest)?;
}
Defensive patterns

Strategy: validation

Validate before calling

fn archive_entries_safe(names: &[&str]) -> bool {
    names.iter().all(|n| {
        let p = std::path::Path::new(n);
        !p.is_absolute() && !n.split('/').any(|c| c == "..")
    })
}

Type guard

fn is_unsafe_path(e: &FetchError) -> bool {
    matches!(e, FetchError::UnsafeArchivePath { .. })
}

Try / catch

match install(v) {
    Err(FetchError::UnsafeArchivePath { path }) => {
        error!("archive contains unsafe path {path}; refusing install — possible tampering");
        std::process::exit(1);
    }
    other => other,
}

Prevention

When it happens

Trigger: Extracting a release archive that contains an entry name like `/etc/passwd`, `../../.bashrc`, or a Windows drive path — the extractor refuses before writing anything.

Common situations: 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.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/844a484b783cba11. Report an issue: GitHub.

Appendix: source

Thrown at baml_language/crates/baml_release/src/lib.rs:99

    HttpStatus {
        url: String,
        status: reqwest::StatusCode,
    },
    #[error("manifest 404 for version {version} (not released yet?)")]
    ManifestNotFound { version: String },
    #[error("manifest schema {got} not supported (max {max}); run `baml self-update`")]
    ManifestSchemaTooNew { got: u32, max: u32 },
    #[error("target {target} not built for version {version}")]
    TargetNotInManifest { target: String, version: String },
    #[error("sha256 mismatch for {url}: expected {expected}, got {got}")]
    ChecksumMismatch {
        url: String,
        expected: String,
        got: String,
    },
    #[error("archive missing expected binary {name}")]
    BinaryNotInArchive { name: String },
    #[error("archive contains unsafe path {path}")]
    UnsafeArchivePath { path: String },
    #[error("disk error: {0}")]
    Io(#[from] std::io::Error),
    #[error("zip archive error: {0}")]
    Zip(#[from] zip::result::ZipError),
}

#[derive(Debug, Clone)]
pub struct Fetcher {
    pub spec: ReleaseSpec,
    pub product: Product,
    pub manifest_base_url: String,
    pub release_repo: String,
    artifact: Option<Artifact>,
}

impl Fetcher {
    pub fn default_for(spec: ReleaseSpec, product: Product) -> Self {

View on GitHub (pinned to bd85ce9dee)