jdx/mise · error
path must not escape the task directory
Error message
path must not escape the task directory
What it means
ensure_safe_relative rejects output paths containing '..', a root component, or a Windows prefix. Such paths can resolve outside the task directory when the archive is packed or restored, so mise refuses them outright as a path-traversal guard.
Source
Thrown at src/task/task_cache.rs:1262
)
})?;
}
build_output_matcher(root, &patterns)
.wrap_err_with(|| format!("task {} has an invalid cache output pattern", task.name))?;
Ok(())
}
fn ensure_safe_relative(path: &Path) -> Result<()> {
if path.as_os_str().is_empty() || path.is_absolute() {
bail!("path must be a non-empty relative path");
}
if path.components().any(|c| {
matches!(
c,
Component::ParentDir | Component::RootDir | Component::Prefix(_)
)
}) {
bail!("path must not escape the task directory");
}
if !path.components().any(|c| matches!(c, Component::Normal(_))) {
bail!("path must identify an output beneath the task directory");
}
Ok(())
}
fn resolve_output_roots(task: &Task, root: &Path, require_matches: bool) -> Result<Vec<PathBuf>> {
let mut resolved = BTreeSet::new();
let patterns = task.outputs.patterns();
let matcher = build_output_matcher(root, &patterns)?;
for output in output_glob_patterns(&patterns) {
ensure_safe_relative(Path::new(&output))?;
if crate::task::task_source_checker::is_glob_pattern(&output) {
let mut glob_matched = false;
for expanded in expand_enumeration_patterns(&output)? {
ensure_safe_relative(Path::new(&expanded))?;
for entry in glob(root.join(expanded).to_str().unwrap_or_default())? {View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Point outputs at a path inside the project root, e.g. outputs = ['dist/']
- Produce artifacts inside the root and add a separate copy task for consumers elsewhere
- Remove '..' segments even where the filesystem would tolerate them
Example fix
# before [tasks.build] outputs = ['../shared/dist/app'] # after [tasks.build] outputs = ['dist/app']
Defensive patterns
Strategy: validation
Validate before calling
# CI / pre-commit: outputs never traverse upward
python3 - <<'PY'
import tomllib, pathlib
cfg = tomllib.loads(pathlib.Path('mise.toml').read_text())
for name, t in cfg.get('tasks', {}).items():
for o in t.get('outputs', []):
body = o.lstrip('!')
assert '..' not in pathlib.PurePosixPath(body).parts, f'{name}: output escapes root: {o}'
PY Prevention
- Never reference sibling or parent dirs from outputs; copy artifacts inside the root instead
- The check fires on any '..' component, even where the filesystem would resolve it
- Generate outputs in conventionally-created subdirectories
When it happens
Trigger: An outputs pattern resolves to a path whose components include ParentDir/RootDir/Prefix — e.g. outputs = ['../shared/dist'] or a glob that expands through a path containing '..'.
Common situations: Monorepos where outputs land in a sibling package's directory; escaping the project to a shared build dir; '../' segments left in a copied pattern.
Related errors
- remote cache path escapes its output root
- content-level SLSA verification rejected unsafe archive path
- brew-cask: invalid {kind} '{value}'
- output path {} traverses symlink ancestor {}
- remote cache symlink target must be relative
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/641245a542f670b1.
Report an issue: GitHub.