{"record":{"id":"78ddb9b67a9d19fc","repo":"Hmbown/CodeWhale","slug":"refusing-to-restore-unsafe-path-rel-restore-requires-a","errorCode":null,"errorMessage":"refusing to restore unsafe path '{rel}': restore requires a regular workspace file","messagePattern":"refusing to restore unsafe path '(.+?)': restore requires a regular workspace file","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/snapshot/repo.rs","lineNumber":639,"sourceCode":"                \"git checkout failed: {}\",\n                String::from_utf8_lossy(&checkout.stderr).trim()\n            )));\n        }\n        self.remove_paths_missing_from_target(&current_paths, &target_paths)?;\n        Ok(())\n    }\n\n    /// File restore never traverses symlinks, directories, or Git metadata.\n    /// Validate every existing component before reading, backing up or writing.\n    pub fn validate_restore_file(&self, rel: &Path) -> io::Result<bool> {\n        if !is_safe_relative_path(rel)\n            || rel.components().any(|part| {\n                part.as_os_str()\n                    .as_encoded_bytes()\n                    .eq_ignore_ascii_case(b\".git\")\n            })\n        {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                format!(\n                    \"refusing to restore unsafe path '{}': restore requires a regular workspace file\",\n                    rel.display()\n                ),\n            ));\n        }\n        let mut path = self.work_tree.clone();\n        for part in rel.components() {\n            path.push(part);\n            match std::fs::symlink_metadata(&path) {\n                Ok(meta)\n                    if meta.file_type().is_symlink()\n                        || (path == self.work_tree.join(rel) && !meta.is_file())\n                        || (path != self.work_tree.join(rel) && !meta.is_dir()) =>\n                {\n                    return Err(io::Error::new(\n                        io::ErrorKind::InvalidInput,","sourceCodeStart":621,"sourceCodeEnd":657,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/snapshot/repo.rs#L621-L657","documentation":"Path-validation guard in validate_restore_file, the entry check for snapshot restore (also used by path_differs_from_snapshot, restore_file_if_unchanged, restore_paths_checked). It fires when the relative restore path fails is_safe_relative_path — traversal components, absolute paths, or other unsafe shapes. File restore never traverses symlinks, directories, or Git metadata, so any path that is not a plain relative workspace file path is rejected before any read, backup, or write happens.","triggerScenarios":"Thrown at crates/tui/src/snapshot/repo.rs:639 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Restore only plain relative file paths inside the workspace","Remove '..' or absolute components from the restore path","Restore directories or links through git tooling directly, not the file-restore path"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}