diff --git a/hive-agent/src/todo_server.rs b/hive-agent/src/todo_server.rs index e5b845dc..39ff323c 100644 --- a/hive-agent/src/todo_server.rs +++ b/hive-agent/src/todo_server.rs @@ -22,6 +22,7 @@ //! existing best-effort JSON-line clients (they just change which socket //! they dial, not the payload). +use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -125,6 +126,15 @@ pub async fn run( /// Bind a `UnixListener` at `path`, creating the parent dir and unlinking a /// stale socket left by a prior boot (which would otherwise block `bind` /// with `EADDRINUSE`). +/// +/// The mode is set **here, at creation** — not corrected afterwards by +/// whoever notices. `connect(2)` on a unix socket requires *write* +/// permission, and `bind` leaves `0777 & ~umask` (typically `0755`), which +/// silently excludes every uid but the harness's own. `hive-c0re` pushes +/// todos in over this socket from the host, so that default locks it out. +/// Same two lines the sibling `web.sock` has carried all along +/// (`web_ui::serve`); access control is the containing directory's job, not +/// the socket's. fn bind(path: &Path) -> Result { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent) @@ -133,7 +143,11 @@ fn bind(path: &Path) -> Result { if path.exists() { let _ = std::fs::remove_file(path); } - UnixListener::bind(path).with_context(|| format!("bind in-agent socket {}", path.display())) + let listener = UnixListener::bind(path) + .with_context(|| format!("bind in-agent socket {}", path.display()))?; + std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o666)) + .with_context(|| format!("set perms on in-agent socket {}", path.display()))?; + Ok(listener) } /// Handle one connection: read a single JSON request line, apply it to the