{"record":{"id":"b2a814b93e9ce040","repo":"astrid-runtime/astrid","slug":"private-file-has-links-durable-media-must-have","errorCode":null,"errorMessage":"private file has {} links; durable media must have exactly one","messagePattern":"private file has (.+?) links; durable media must have exactly one","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/platform_fs.rs","lineNumber":563,"sourceCode":"        ));\n    }\n    if metadata.st_uid != nix::unistd::getuid().as_raw() {\n        return Err(io::Error::new(\n            io::ErrorKind::PermissionDenied,\n            format!(\n                \"private file is not owned by the current user: {}\",\n                path.display()\n            ),\n        ));\n    }\n    if metadata.st_mode & 0o777 != 0o600 {\n        return Err(io::Error::new(\n            io::ErrorKind::PermissionDenied,\n            format!(\"private file is not owner-only: {}\", path.display()),\n        ));\n    }\n    if metadata.st_nlink != 1 {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\n                \"private file has {} links; durable media must have exactly one\",\n                metadata.st_nlink\n            ),\n        ));\n    }\n    validate_no_extended_acl(path)?;\n    Ok(())\n}\n\n#[cfg(target_os = \"macos\")]\nfn remove_extended_acl_macos(path: &Path) -> io::Result<()> {\n    let path = absolute_command_path(path)?;\n    let status = std::process::Command::new(\"/bin/chmod\")\n        .arg(\"-N\")\n        .arg(path)\n        .status()?;","sourceCodeStart":545,"sourceCodeEnd":581,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/platform_fs.rs#L545-L581","documentation":"The library requires private files to have exactly one hard link (st_nlink == 1) because durable security-sensitive media must not be reachable through a second directory entry, which would let edits bypass the enforced permissions. It throws this when validation counts more than one link.","triggerScenarios":"validate_private_file finds st_nlink > 1: someone hard-linked the file elsewhere (`ln`), or a backup/dedup tool created additional links to the inode.","commonSituations":"Backup tools using hard links (e.g. rsync --link-dest, rsnapshot); developer manually hard-linking the credential file into another project; deduplicating filesystems linking shared inodes.","solutions":["Remove the extra hard links (`ls -li <path>` to find inodes, then delete the other directory entries) until the link count is 1.","Copy the file to a fresh inode instead of hard-linking: `cp <src> <dst>` then `rm <src>` and `mv <dst> <src>`, keeping mode 0600.","Disable hard-link-based backup/dedup for the private directory and re-create the file via the library.","Recreate the file with atomic_write_private_file, which writes a fresh single-link inode."],"exampleFix":"// before (nlink=2 due to backup hard link)\nvalidate_private_file(Path::new(\"/home/me/.astrid/credentials\"))?;\n// after (replace with fresh single-link file)\n// $ cp /home/me/.astrid/credentials /tmp/cred && chmod 600 /tmp/cred\n// $ rm /home/me/.astrid/credentials && mv /tmp/cred /home/me/.astrid/credentials\nvalidate_private_file(Path::new(\"/home/me/.astrid/credentials\"))?;","handlingStrategy":"validation","validationCode":"use std::os::unix::fs::MetadataExt;\nfn has_single_link(path: &std::path::Path) -> bool {\n    std::fs::metadata(path).map(|m| m.nlink() == 1).unwrap_or(false)\n}","typeGuard":"fn is_unlinked_alias_free(path: &std::path::Path) -> bool {\n    std::fs::metadata(path).map(|m| m.nlink() == 1).unwrap_or(false)\n}","tryCatchPattern":"match validate_private_file(path) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains(\"links\") => {\n        // replace with a fresh single-link copy\n    },\n    other => other?,\n}","preventionTips":["Never `ln` (hard link) private files elsewhere","Exclude private directories from hard-link-based backups (rsync --link-dest, rsnapshot)","Prefer `cp` over hard links when duplicating sensitive files"],"tags":["filesystem","unix","hardlink","security"],"backgroundTag":"invalid-state-transition","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"}