jdx/mise · error
refusing cleanup through symlink component {}
Error message
refusing cleanup through symlink component {} What it means
During apk transient cleanup, mise walks each path component of the target with symlink_metadata and refuses to continue if any component is itself a symlink. This prevents a planted or unexpected symlink from redirecting deletions outside the rootfs (TOCTOU-style escape).
Source
Thrown at src/oci/packages.rs:562
remove_path(&log)
}
/// Reject a cleanup target when it or any path component below `rootfs` is a
/// symlink. OCI package layers are untrusted, so following one during cleanup
/// could remove files outside the layer root.
fn reject_symlink_components(rootfs: &Path, target: &Path) -> Result<()> {
let relative = target
.strip_prefix(rootfs)
.wrap_err_with(|| format!("cleanup target {} is outside rootfs", target.display()))?;
let mut current = rootfs.to_path_buf();
for component in relative.components() {
let std::path::Component::Normal(component) = component else {
bail!("invalid cleanup target {}", target.display());
};
current.push(component);
match fs::symlink_metadata(¤t) {
Ok(metadata) if metadata.file_type().is_symlink() => {
bail!(
"refusing cleanup through symlink component {}",
current.display()
);
}
Ok(_) => {}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(err) => {
return Err(err)
.wrap_err_with(|| format!("reading metadata for {}", current.display()));
}
}
}
Ok(())
}
/// Remove every regular file under `dir` (recursively), leaving the directory
/// structure and any symlinks intact. A missing `dir` is a no-op.
fn remove_files_recursively(dir: &Path) -> Result<()> {View on GitHub (pinned to afd2eddd3a)
Solutions
- Use a base image without symlinked components on the apk paths, or normalize the layout
- Remove/repoint the offending symlink inside the rootfs before building
- Skip custom transient cleanup if your image layout legitimately relies on symlinks
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
# check for symlinked components on apk paths in the base image docker run --rm --entrypoint sh BASE_IMAGE -c 'for p in /etc/apk /lib/apk /lib/apk/db /var/cache/apk; do [ -L "$p" ] && echo "symlink: $p"; done; true'
Try / catch
try {
buildOciImage(config);
} catch (e) {
if (String(e.message).includes("refusing cleanup through symlink component")) {
// flatten the symlinked layout in the base image or adjust it
} else throw e;
} Prevention
- Choose base images without symlinked apk/db/cache directories
- Audit base image layouts before adopting them
- Treat this as a security stop — do not work around by disabling checks
When it happens
Trigger: A directory component of an apk transient path inside the rootfs is a symlink when clean_apk_transients walks it — e.g. an image where /lib, /var/cache, or the apk db path is symlinked.
Common situations: Base images using symlinked directory layouts (common with merged-/usr or musl layouts); attacker-influenced layers; apk creating cache dirs as symlinks.
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
- invalid cleanup target {}
- [dotfiles]."{}": mode symlink-each requires a directory sour
- [dotfiles]."{}": target is not a safe OCI path
- brew-cask: staged symlink path escaped extraction root: {}
- brew-cask: refusing generic artifact source outside the extr
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/8b61388a2d837ad5.
Report an issue: GitHub.