jdx/mise · error · eyre::Report

[dotfiles]."{}": target is not a safe OCI path

Error message

[dotfiles]."{}": target is not a safe OCI path

What it means

oci_target_path normalizes a [dotfiles] target (~ -> root, ~/x -> root/x, absolute /x -> x, backslashes to slashes) and then rejects the result if it is empty or contains a '..' component. This is a safety gate: '..' in the image path would escape the intended location inside the container filesystem, and an empty path has no tar meaning.

Source

Thrown at src/oci/builder.rs:1031

        (files, dirs)
    }
}

fn oci_target_path(req: &FileRequest) -> Result<String> {
    let raw = req.target_raw.as_str();
    let path = if raw == "~" {
        "root".to_string()
    } else if let Some(rest) = raw.strip_prefix("~/") {
        format!("root/{rest}")
    } else {
        req.target
            .strip_prefix("/")
            .map_err(|_| eyre::eyre!("dotfile target must be absolute: {}", req.target_raw))?
            .to_string_lossy()
            .replace('\\', "/")
    };
    if path.is_empty() || path.split('/').any(|p| p == "..") {
        bail!(
            "[dotfiles].\"{}\": target is not a safe OCI path",
            req.target_raw
        );
    }
    Ok(path)
}

fn source_mode(path: &std::path::Path) -> Result<u32> {
    let md = path.metadata()?;
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        Ok(md.permissions().mode() & 0o7777)
    }
    #[cfg(not(unix))]
    {
        let _ = md;
        Ok(0o644)

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Rewrite the target as an absolute path without any '..' segment, e.g. "/etc/mise/config.toml" or "~/.config/mise/config.toml".
  2. Replace a "/" target with the concrete destination file name under a real directory.
  3. If you need path joining, build it from components and reject '..' programmatically instead of concatenating strings.

Example fix

# before
[dotfiles."~/secrets"]
target = "/../etc/passwd"

# after
[dotfiles."~/secrets"]
target = "/etc/mise/secrets"
Defensive patterns

Strategy: validation

Validate before calling

def safe_oci_target(t: str) -> bool:
    if t == "~":
        return True
    if t.startswith("~/"):
        p = "root/" + t[2:]
    elif t.startswith("/"):
        p = t[1:]
    else:
        return False  # relative -> different error, still reject up front
    p = p.replace("\\", "/")
    return bool(p) and all(part != ".." for part in p.split("/"))

assert all(safe_oci_target(t) for t in targets)

Prevention

When it happens

Trigger: A [dotfiles] target like "/../etc/passwd", "~/../..", "a/../../b", or a bare "/" (which strips to the empty string). Windows-style separators are converted before the check, so "~\..\x" is caught too.

Common situations: Copy-pasted relative targets from another tool, targets built by string concatenation that accidentally include '..', or a target of just "/" expecting the image root.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/5718d76a30f6f007. Report an issue: GitHub.