jdx/mise · critical

unsafe source repository path

Error message

unsafe source repository path

What it means

install_at walks every path in the repository tree (`git ls-tree -r`) and rejects any entry containing non-Normal path components (absolute paths, `..`, `.`, drive/root prefixes) or any path segment named `.git` (case-insensitive). This blocks path-traversal and git-directory smuggling from a malicious or misconfigured source repository.

Solutions

  1. Find offending entries: `git ls-tree -r --name-only <rev> | grep -iE '(^|/)\.git(/|$)|\.\.|^/'` and remove them from the repository.
  2. Remove vendored nested .git directories before committing (use a subtree export or copy files without VCS metadata).
  3. Push a cleaned commit, then re-run onboarding with the new revision.

Example fix

// before (repo tree)
.git/hooks/pre-commit
../shared/config.toml
// after — clean and republish
$ git rm -r --cached .git/hooks  # and delete nested .git dirs, '..' files
$ git commit -m "remove unsafe paths"
$ git rev-parse HEAD  # use the new SHA for onboarding
Defensive patterns

Strategy: validation

Validate before calling

function hasUnsafePath(entry: string): boolean {
  const segs = entry.split("/");
  return segs.some(s => s === ".." || s === "." || s.length === 0 || s.toLowerCase() === ".git")
    || entry.startsWith("/");
}
// audit the repo: git ls-tree -r --name-only <rev> | xargs -I{} check hasUnsafePath

Prevention

When it happens

Trigger: Installing from a source repository whose tree contains entries like `../../etc/passwd`, `/absolute/path`, `.git/hooks/x`, or a directory literally named `GIT` or `.Git`.

Common situations: Repos crafted (or accidently committed) with `..` filenames, submodule artifacts, or nested `.git` directories from vendored checkouts; security scanning of untrusted onboarding sources.

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 jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/87fd0ba3bb6364bd. Report an issue: GitHub.

Appendix: source

Thrown at src/system/remote_repository.rs:233

        .args(["clone", "--no-checkout", "--"])
        .arg(bundle)
        .arg(&checkout)
        .output()?;
    if !output.status.success() {
        bail!("invalid transferred repository bundle");
    }
    if git(&checkout, &["rev-parse", "HEAD"])? != revision {
        bail!("transferred revision mismatch");
    }
    let entries = git(&checkout, &["ls-tree", "-r", "-z", "--name-only", revision])?;
    for entry in entries.split('\0').filter(|s| !s.is_empty()) {
        let path = Path::new(entry);
        if path
            .components()
            .any(|c| !matches!(c, std::path::Component::Normal(_)))
            || entry.split('/').any(|p| p.eq_ignore_ascii_case(".git"))
        {
            bail!("unsafe source repository path");
        }
        if entry.to_ascii_lowercase().ends_with(".local.toml") {
            bail!(
                "source contains machine-local configuration ({entry}); remove it from the repository before onboarding"
            );
        }
    }
    git(&checkout, &["remote", "set-url", "origin", origin])?;
    let branch = git(&checkout, &["symbolic-ref", "--short", "HEAD"])?;
    git(
        &checkout,
        &["-c", "core.hooksPath=/dev/null", "checkout", &branch],
    )?;
    if destination.join(".git").exists() {
        if git(destination, &["remote", "get-url", "origin"])? != origin {
            bail!("global configuration origin does not match");
        }
        if !git(

View on GitHub (pinned to 533346cc37)