{"record":{"id":"45700f1c547ebaea","repo":"astrid-runtime/astrid","slug":"workspace-state-directory-escapes-or-redirects-out","errorCode":null,"errorMessage":"workspace state directory escapes or redirects outside its selected path: {}","messagePattern":"workspace state directory escapes or redirects outside its selected path: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/workspace_security.rs","lineNumber":318,"sourceCode":"    crate::platform_fs::verify_no_redirects(state_dir)?;\n    let metadata = match std::fs::symlink_metadata(state_dir) {\n        Ok(metadata) => metadata,\n        Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),\n        Err(error) => return Err(error),\n    };\n    if metadata.file_type().is_symlink() || !metadata.is_dir() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            format!(\n                \"workspace state path must be a real directory, not a redirect or file: {}\",\n                state_dir.display()\n            ),\n        ));\n    }\n\n    let canonical = std::fs::canonicalize(state_dir)?;\n    if canonical != state_dir || canonical.parent() != Some(project_root) {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            format!(\n                \"workspace state directory escapes or redirects outside its selected path: {}\",\n                state_dir.display()\n            ),\n        ));\n    }\n    Ok(())\n}\n\n/// Stable identity for one project root and workspace layout selection.\n///\n/// The identity is suitable for detecting whether a CLI and an already-running\n/// daemon selected the same project. It does not expose the project path.\n#[must_use]\npub fn workspace_selection_fingerprint(\n    project_root: &Path,\n    workspace_layout: &WorkspaceLayout,","sourceCodeStart":300,"sourceCodeEnd":336,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/workspace_security.rs#L300-L336","documentation":"After canonicalizing the state directory, the library verifies it resolves to exactly the literal path requested and that its parent is the project root. Any symlink component, aliased path, or a directory whose parent is not `project_root` triggers this error, blocking state from escaping its designated location.","triggerScenarios":"Calling `resolve` when `std::fs::canonicalize(state_dir)` differs from `state_dir` (symlinks, non-normalized path) or when the canonical path's parent is not `project_root` (path placed outside the project).","commonSituations":"Config uses an absolute path or `/../` traversal pointing elsewhere; the state dir is a symlink target (resolves differently than spelled); user moved the state dir outside the project tree.","solutions":["Set the state path to a plain relative path directly under the project root, with no symlinks or `..` components.","Move the directory back inside the project root.","Clear environment overrides (e.g. TMPDIR-style env vars) that make the canonical path differ from the literal one."],"exampleFix":"// before\nstate_dir = \"/var/lib/astrid/project-state\"\n// after\nstate_dir = \".astrid/state\"","handlingStrategy":"validation","validationCode":"fn inside_project_root(state_dir: &std::path::Path, root: &std::path::Path) -> bool {\n    let canonical = std::fs::canonicalize(state_dir).ok();\n    canonical.as_deref() == Some(state_dir)\n        && canonical.and_then(|c| c.parent()).map(|p| p == root).unwrap_or(false)\n}","typeGuard":"fn resolves_to_configured_path(p: &std::path::Path) -> bool {\n    std::fs::canonicalize(p).map(|c| c == p).unwrap_or(false)\n}","tryCatchPattern":"match resolve(...) {\n    Err(e) if e.to_string().contains(\"escapes or redirects\") => {\n        // fall back to default relative state path under project root\n        resolve(...)\n    }\n    other => other,\n}","preventionTips":["Use plain relative paths without `..` or symlinked components for state dirs.","Keep state directories directly under the project root.","Avoid absolute or platform-dependent state paths in shared config."],"tags":["filesystem","path-traversal","validation"],"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"}