{"record":{"id":"3897b7825fd6c5ce","repo":"jdx/mise","slug":"invalid-cleanup-target","errorCode":null,"errorMessage":"invalid cleanup target {}","messagePattern":"invalid cleanup target (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/oci/packages.rs","lineNumber":557,"sourceCode":"    let cache = rootfs.join(\"var/cache/apk\");\n    let log = rootfs.join(\"var/log/apk.log\");\n    reject_symlink_components(rootfs, &cache)?;\n    reject_symlink_components(rootfs, &log)?;\n    remove_dir_children(&cache)?;\n    remove_path(&log)\n}\n\n/// Reject a cleanup target when it or any path component below `rootfs` is a\n/// symlink. OCI package layers are untrusted, so following one during cleanup\n/// could remove files outside the layer root.\nfn reject_symlink_components(rootfs: &Path, target: &Path) -> Result<()> {\n    let relative = target\n        .strip_prefix(rootfs)\n        .wrap_err_with(|| format!(\"cleanup target {} is outside rootfs\", target.display()))?;\n    let mut current = rootfs.to_path_buf();\n    for component in relative.components() {\n        let std::path::Component::Normal(component) = component else {\n            bail!(\"invalid cleanup target {}\", target.display());\n        };\n        current.push(component);\n        match fs::symlink_metadata(&current) {\n            Ok(metadata) if metadata.file_type().is_symlink() => {\n                bail!(\n                    \"refusing cleanup through symlink component {}\",\n                    current.display()\n                );\n            }\n            Ok(_) => {}\n            Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()),\n            Err(err) => {\n                return Err(err)\n                    .wrap_err_with(|| format!(\"reading metadata for {}\", current.display()));\n            }\n        }\n    }\n    Ok(())","sourceCodeStart":539,"sourceCodeEnd":575,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/oci/packages.rs#L539-L575","documentation":"clean_apk_transients validates every cleanup target via reject_symlink_components: after stripping the rootfs prefix, each remaining path component must be a plain Normal component. Anything else (ParentDir, RootDir, Prefix — including paths that resolve through '..') is rejected as an invalid cleanup target, guarding against deleting outside the rootfs.","triggerScenarios":"clean_apk_transients computes a transient path (e.g. apk cache/db temp files) that normalizes to non-Normal components — typically because the stripped target still contains '..' or is a filesystem prefix — and reject_symlink_components bails.","commonSituations":"Corrupted or unexpected apk database paths inside the rootfs; symlinked or overlapping mount points causing odd relative paths; internal state bugs rather than user configuration errors.","solutions":["Inspect the rootfs for unexpected '..'-containing or absolute transient paths left by apk","Rebuild from a clean base image so apk's transient files are in expected locations","If reproducible, report it — this usually indicates malformed path state, not user configuration"],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"null","typeGuard":null,"tryCatchPattern":"try {\n  buildOciImage(config);\n} catch (e) {\n  if (String(e.message).includes(\"invalid cleanup target\")) {\n    // rebuild from a clean base image; report if reproducible\n  } else throw e;\n}","preventionTips":["Build from clean, standard base images","Avoid images with exotic path layouts in the apk db/cache locations","Report reproducible occurrences upstream"],"tags":["oci","apk","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}