{"record":{"id":"8baef2189f22291c","repo":"astrid-runtime/astrid","slug":"layout-staging-path-is-redirected","errorCode":null,"errorMessage":"layout staging path is redirected: {}","messagePattern":"layout staging path is redirected: (.+?)","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/dirs_layout.rs","lineNumber":534,"sourceCode":"\n        let parent = path.parent().ok_or_else(|| {\n            io::Error::new(io::ErrorKind::InvalidInput, \"layout record has no parent\")\n        })?;\n        std::fs::create_dir_all(parent)?;\n        let name = path\n            .file_name()\n            .and_then(|name| name.to_str())\n            .ok_or_else(|| {\n                io::Error::new(\n                    io::ErrorKind::InvalidInput,\n                    \"layout record has no file name\",\n                )\n            })?;\n        let staged = parent.join(format!(\".{name}.next\"));\n        match std::fs::symlink_metadata(&staged) {\n            Ok(metadata) if metadata.file_type().is_file() => std::fs::remove_file(&staged)?,\n            Ok(_) => {\n                return Err(io::Error::new(\n                    io::ErrorKind::InvalidData,\n                    format!(\"layout staging path is redirected: {}\", staged.display()),\n                ));\n            },\n            Err(error) if error.kind() == io::ErrorKind::NotFound => {},\n            Err(error) => return Err(error),\n        }\n        let mut file = OpenOptions::new()\n            .write(true)\n            .create_new(true)\n            .mode(0o600)\n            .open(&staged)?;\n        file.write_all(bytes)?;\n        file.sync_all()?;\n        crate::platform_fs::rename_with_write_through(&staged, path)?;\n        File::open(parent)?.sync_all()\n    }\n","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/dirs_layout.rs#L516-L552","documentation":"Before writing, atomic_write stages to parent/.{name}.next. If that staging path exists but is NOT a regular file (symlink, directory, etc.), the library refuses to touch it and raises InvalidData \"layout staging path is redirected: {path}\". This prevents an attacker (or a misconfiguration) from redirecting the atomic write through a symlink to an arbitrary location.","triggerScenarios":"A file or symlink named .{name}.next exists next to the target layout record and symlink_metadata shows it is not a plain file, so the Ok(_) arm returns the formatted InvalidData error.","commonSituations":"A leftover symlink .layout-v2.json.next planted by an attacker in a world-writable directory; a previous crashed run left a directory at the staging name; security tooling replaced the temp file with a symlink.","solutions":["Inspect the reported staged path; remove or replace the non-file entry (e.g. rm the symlink) so it is either absent or a regular file.","Investigate how the symlink appeared — treat it as a possible tampering attempt and audit directory permissions (avoid world-writable layout dirs).","Re-run the layout write after cleaning the staging path; the library will then remove a plain stale file itself."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"let staged = parent.join(format!(\".{}.next\", name));\nif let Ok(md) = std::fs::symlink_metadata(&staged) {\n    assert!(md.file_type().is_file(), \"staging path {} is not a plain file\", staged.display());\n}","typeGuard":"fn staging_path_is_clean(parent: &Path, name: &str) -> bool {\n    let staged = parent.join(format!(\".{name}.next\"));\n    match std::fs::symlink_metadata(&staged) {\n        Ok(md) => md.file_type().is_file(),\n        Err(_) => true, // NotFound is fine\n    }\n}","tryCatchPattern":"match write_layout_version(path, record) {\n    Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().contains(\"staging path is redirected\") => {\n        eprintln!(\"possible symlink tampering: audit directory permissions and remove the staged entry\");\n    },\n    other => other?,\n}","preventionTips":["Keep layout directories writable only by the service user (avoid world-writable dirs)","Clean up .next staging files from crashed runs during startup","Alert on symlinks appearing next to layout records — treat as a security signal"],"tags":["filesystem","security","symlink","atomic-write"],"backgroundTag":"file-already-exists","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}