jdx/mise · error

remote cache symlink target must be relative

Error message

remote cache symlink target must be relative

What it means

validate_cache_symlink_target rejects absolute symlink targets in remote cache entries. Remote entries must store only relative symlinks so they can be resolved safely within the materialized output tree; absolute targets could point anywhere on the reading machine.

Solutions

  1. Change the task so it creates relative symlinks (e.g. ln -s ../lib/foo lib/foo) and republish the cache entry.
  2. Re-run the task with cache disabled/forced to produce a fresh, valid artifact.
  3. If symlinks to absolute locations are required, store them as regular files or relative links resolved at runtime instead.
  4. Purge the offending entry from the remote cache.

Example fix

// before (in task script)
ln -s /opt/tools/bin/helper dist/helper
// after
cd dist && ln -s ../tools/bin/helper helper   # or copy the binary instead of linking
Defensive patterns

Strategy: validation

Validate before calling

fn relative_link(t: &std::path::Path) -> bool {
    !t.is_absolute()
}

Type guard

fn is_relative_symlink_target(t: &std::path::Path) -> bool {
    !t.is_absolute()
}

Try / catch

match validate_cache_symlink_target(&link_path, &target) {
    Err(e) if e.to_string().contains("must be relative") => {
        log::warn!("skipping entry with absolute symlink {link_path:?} -> {target:?}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: archive_to_cas() or materialize_remote_tree() encounters a symlink whose target is absolute (starts with '/' or a Windows drive/prefix) (task_cache_store.rs:543).

Common situations: Build outputs containing absolute symlinks (e.g. created by ln -s /usr/lib/... during the task); archives created from system trees with absolute links; malicious cache entries.

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/6d0246d2a66bea5a. Report an issue: GitHub.

Appendix: source

Thrown at src/task/task_cache_store.rs:543

}

fn validate_cache_name(name: &str) -> Result<()> {
    let path = Path::new(name);
    if name.is_empty()
        || name == "."
        || name == ".."
        || name.contains(['/', '\\', '\0'])
        || path.components().count() != 1
        || !matches!(path.components().next(), Some(Component::Normal(_)))
    {
        bail!("invalid remote cache path component");
    }
    Ok(())
}

fn validate_cache_symlink_target(path: &Path, target: &Path) -> Result<()> {
    if target.is_absolute() {
        bail!("remote cache symlink target must be relative");
    }
    let resolved = path.parent().unwrap_or(Path::new("")).join(target);
    let mut depth = 0_i64;
    for component in resolved.components() {
        match component {
            Component::Normal(_) => depth += 1,
            Component::ParentDir => depth -= 1,
            Component::CurDir => {}
            _ => bail!("remote cache symlink target is unsafe"),
        }
        if depth < 0 {
            bail!("remote cache symlink target escapes its output root");
        }
    }
    Ok(())
}

fn archive_to_cas(path: &Path, staging_dir: &Path) -> Result<(CacheDigest, Vec<BlobUpload>)> {

View on GitHub (pinned to 533346cc37)