{"record":{"id":"8a207ecf87860239","repo":"astrid-runtime/astrid","slug":"workspace-path-redirects-from-its-selected-target","errorCode":null,"errorMessage":"workspace path redirects from its selected target: {}","messagePattern":"workspace path redirects from its selected target: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/workspace_security.rs","lineNumber":208,"sourceCode":"                Err(error) if error.kind() == io::ErrorKind::NotFound => continue,\n                Err(error) => return Err(error),\n            };\n            let final_component = index == components.len().saturating_sub(1);\n            let expected_file = final_component && kind == DescendantKind::File;\n            if metadata.file_type().is_symlink()\n                || (expected_file && !metadata.is_file())\n                || (!expected_file && !metadata.is_dir())\n            {\n                return Err(io::Error::new(\n                    io::ErrorKind::InvalidInput,\n                    format!(\n                        \"workspace path must not contain redirects or unexpected file types: {}\",\n                        current.display()\n                    ),\n                ));\n            }\n            if std::fs::canonicalize(&current)? != current {\n                return Err(io::Error::new(\n                    io::ErrorKind::InvalidInput,\n                    format!(\n                        \"workspace path redirects from its selected target: {}\",\n                        current.display()\n                    ),\n                ));\n            }\n        }\n        Ok(self.state_dir.join(relative))\n    }\n\n    /// Re-check that the selected state path has not been redirected.\n    ///\n    /// A missing state directory remains valid. This permits a checked\n    /// selection to be created before initialization while still rejecting a\n    /// later symlink or non-directory replacement.\n    ///\n    /// # Errors","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/workspace_security.rs#L190-L226","documentation":"As a final per-component check, resolve_descendant canonicalizes the current path and requires it to equal itself; if canonicalization yields a different path the route 'redirects' from its selected target and InvalidInput is raised. This catches cases the metadata checks miss, such as case-insensitive filesystem aliases or hardlink/bind tricks that make the walked path differ from the kernel's true path.","triggerScenarios":"resolve_directory or resolve_file walking a path where any component's canonical form differs from the walked form — typically due to symlinked parents that passed earlier checks only via TOCTOU, bind mounts, or case-insensitive filesystem mismatches.","commonSituations":"macOS/Windows case-insensitive volumes where the stored path casing differs from the on-disk casing; a parent directory swapped for a symlink between the metadata check and canonicalize; overlay/bind mounts inside the workspace.","solutions":["Use the canonicalized path (dunce::canonicalize / fs::canonicalize output) when constructing descendant paths so walked and canonical forms match.","Fix directory-name casing to exactly match the on-disk names.","Remove bind/overlay mounts or symlinked parents inside the workspace.","Re-run resolution if a concurrent process mutated the tree; serialize workspace mutations to avoid TOCTOU races."],"exampleFix":"// before\nlet f = ws.resolve_file(Path::new(\"Config/Settings.TOML\"))?; // on-disk: config/settings.toml\n// after\nlet canonical = std::fs::canonicalize(root)?;\nlet rel = Path::new(\"config/settings.toml\");\nlet f = ws.resolve_file(rel)?;","handlingStrategy":"validation","validationCode":"let canonical = std::fs::canonicalize(&root)?;\nlet target = canonical.join(rel);\ndebug_assert_eq!(std::fs::canonicalize(&target).unwrap(), target, \"path redirects\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Build descendant paths from an already-canonicalized root with exact on-disk casing","Avoid bind/overlay mounts inside workspace trees","Re-resolve after any external process moves or remounts the workspace"],"tags":["security","symlink","canonicalization","filesystem"],"backgroundTag":"path-traversal-blocked","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}