{"record":{"id":"8471c7ec03a6ba5f","repo":"zeroclaw-labs/zeroclaw","slug":"local-ipc-endpoint-lock-directory-is-owned-by-u","errorCode":null,"errorMessage":"local IPC endpoint lock directory {} is owned by uid {}; it must belong to the daemon user or root","messagePattern":"local IPC endpoint lock directory (.+?) is owned by uid (.+?); it must belong to the daemon user or root","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/rpc/local.rs","lineNumber":297,"sourceCode":"    /// A directory is trustworthy when it is owned by the current user or\n    /// root and is not group/other-writable — unless the sticky bit is set,\n    /// which restricts unlink and rename to the entry owner and keeps\n    /// `/tmp`-style shared socket directories usable.\n    fn require_trusted_lock_dir(lock_path: &Path) -> Result<()> {\n        let parent = match lock_path.parent() {\n            Some(parent) if !parent.as_os_str().is_empty() => parent,\n            _ => Path::new(\".\"),\n        };\n        let metadata = std::fs::metadata(parent).with_context(|| {\n            format!(\n                \"inspecting local IPC endpoint lock directory {}\",\n                parent.display()\n            )\n        })?;\n        let euid = unsafe { libc::geteuid() };\n        let mode = metadata.mode();\n        if metadata.uid() != euid && metadata.uid() != 0 {\n            anyhow::bail!(\n                \"local IPC endpoint lock directory {} is owned by uid {}; \\\n                 it must belong to the daemon user or root\",\n                parent.display(),\n                metadata.uid()\n            );\n        }\n        if mode & 0o022 != 0 && mode & 0o1000 == 0 {\n            anyhow::bail!(\n                \"local IPC endpoint lock directory {} is writable by other \\\n                 users without the sticky bit; its entries could be replaced. \\\n                 Restrict it (chmod go-w or +t) or point ZEROCLAW_SOCKET at a \\\n                 private directory\",\n                parent.display()\n            );\n        }\n        Ok(())\n    }\n","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/rpc/local.rs#L279-L315","documentation":"The parent directory of the socket lifecycle lock (`<ZEROCLAW_SOCKET>.lock`) is owned by a uid that is neither the daemon's effective uid nor root. ZeroClaw fails closed because in such a directory another local user could seed or replace the lock entry, blocking startup or handing two daemons different lock inodes.","triggerScenarios":"ZEROCLAW_SOCKET points into a directory created by a different user (e.g. a /var/run subdirectory created by a packaging script while the daemon runs as the zeroclaw user); daemon started under a different uid than the one that created the directory (systemd User= changed, running via sudo vs. not).","commonSituations":"Switching the daemon between root and non-root service accounts; sharing a socket directory between components run as different users; copying a config with an absolute ZEROCLAW_SOCKET from another machine/install.","solutions":["Point ZEROCLAW_SOCKET at a directory owned by the daemon user (e.g. ~/.local/state/zeroclaw or XDG_RUNTIME_DIR/zeroclaw).","chown the existing directory to the daemon user (or root): sudo chown zeroclaw:zeroclaw /path/to/dir.","Run the daemon as the same uid that owns the directory.","Do not share the socket directory between users; give each daemon user its own."],"exampleFix":"# before: dir created by another user\nexport ZEROCLAW_SOCKET=/run/shared/zeroclaw.sock   # /run/shared owned by uid 1001, daemon runs as 1002\n\n# after: private, user-owned socket dir\nsudo chown zeroclaw:zeroclaw /run/shared\n# or\nmkdir -p \"$HOME/.local/state/zeroclaw\"\nexport ZEROCLAW_SOCKET=\"$HOME/.local/state/zeroclaw/zeroclaw.sock\"","handlingStrategy":"validation","validationCode":"use std::os::unix::fs::MetadataExt;\nfn lock_dir_trusted(sock: &std::path::Path) -> bool {\n    let dir = sock.parent().unwrap_or(std::path::Path::new(\".\"));\n    let Ok(md) = std::fs::metadata(dir) else { return false };\n    let euid = unsafe { libc::geteuid() };\n    (md.uid() == euid || md.uid() == 0) && (md.mode() & 0o022 == 0 || md.mode() & 0o1000 != 0)\n}","typeGuard":null,"tryCatchPattern":"if !lock_dir_trusted(&sock_path) {\n    return Err(anyhow::anyhow!(\"socket dir not trusted; fix ownership before start\"));\n}\nlet listener = local::serve(&sock_path).await?;","preventionTips":["Give each daemon user a private runtime dir (XDG_RUNTIME_DIR or a state dir) and point ZEROCLAW_SOCKET there.","Never change the service user without chown'ing the socket directory.","Add a pre-start check (ExecStartPre) validating directory ownership."],"tags":["unix","permissions","ownership","ipc","security","filesystem","startup"],"backgroundTag":"unsafe-file-ownership","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}