{"record":{"id":"d1d6e360abc9ff17","repo":"jdx/mise","slug":"refusing-to-write-dependency-sidecar-through-symlink","errorCode":null,"errorMessage":"refusing to write dependency sidecar through symlink {}","messagePattern":"refusing to write dependency sidecar through symlink (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lockfile/graph.rs","lineNumber":415,"sourceCode":"                }\n                if dir.join(\"uv.lock\").is_file() || dir.join(\"aube-lock.yaml\").is_file() {\n                    self.remove.push(dir);\n                }\n            }\n        }\n    }\n    pub(super) fn has_changes(&self) -> bool {\n        !self.files.is_empty() || !self.remove.is_empty()\n    }\n    pub(super) fn publish_files(&self) -> Result<()> {\n        use std::io::Write;\n        for (target, text) in &self.files {\n            let parent = target\n                .parent()\n                .ok_or_else(|| eyre!(\"invalid sidecar file path\"))?;\n            for ancestor in parent.ancestors().take_while(|p| p.starts_with(&self.root)) {\n                if ancestor.is_symlink() {\n                    bail!(\n                        \"refusing to write dependency sidecar through symlink {}\",\n                        ancestor.display()\n                    );\n                }\n            }\n            std::fs::create_dir_all(parent)?;\n            let mut tmp = tempfile::NamedTempFile::new_in(parent)?;\n            tmp.write_all(text.as_bytes())?;\n            tmp.as_file().sync_all()?;\n            tmp.persist(target)?;\n        }\n        Ok(())\n    }\n    pub(super) fn prune(&self) -> Result<()> {\n        for dir in &self.remove {\n            if dir.exists() {\n                std::fs::remove_dir_all(dir)?;\n            }","sourceCodeStart":397,"sourceCodeEnd":433,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/lockfile/graph.rs#L397-L433","documentation":"When publishing dependency sidecar files, `publish_files` walks each target's parent directories up to the managed root and refuses to write if any ancestor is a symlink. This prevents a symlink planted inside the tool directory from redirecting writes outside the managed tree (symlink attack / accidental escape).","triggerScenarios":"Publishing sidecar files when a directory between the root and the sidecar location (e.g. `~/.local/share/mise/installs/node`) is a symlink — e.g. the user symlinked their installs directory to another disk, or a malicious/planted symlink exists in the tree.","commonSituations":"Users symlinking parts of `~/.local/share/mise` (dotfile management, moving installs to another volume); tools that symlink their installation directories; compromised or shared multi-user machines.","solutions":["Replace the symlinked ancestor directory with a real directory (e.g. use bind mounts or move data and keep mise's default layout)","Reconfigure mise's data/install directory to a real path instead of symlinking subdirectories","Identify the offending symlink from the error message and remove it","Regenerate/reinstall the affected tool so directories are created normally by mise"],"exampleFix":"# before: installs dir symlinked for dotfile management\nln -s /mnt/data/mise/installs ~/.local/share/mise/installs\n# after: real directory, data moved\nmv ~/.local/share/mise/installs /mnt/data/mise/installs   # configure root path instead of symlinking","handlingStrategy":"validation","validationCode":"use std::path::Path;\nfn ancestors_have_no_symlinks(target: &Path, root: &Path) -> bool {\n    target.parent().unwrap_or(root)\n        .ancestors().take_while(|p| p.starts_with(root))\n        .all(|p| !p.is_symlink())\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"refusing to write dependency sidecar through symlink\") => {\n        // inspect the reported ancestor path, replace symlink with a real directory, retry\n    }\n    other => other?,\n}","preventionTips":["Don't symlink subdirectories of mise's data/installs tree; configure real paths instead","Use bind mounts rather than symlinks when relocating tool storage","Audit tool install directories for unexpected symlinks on shared machines","Run `mise doctor` after restructuring mise's directory layout"],"tags":["lockfile","symlink","security","filesystem"],"backgroundTag":"path-traversal-blocked","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}