From e20077a4fdcb0f788cb9fd695f6530dc857c8d56 Mon Sep 17 00:00:00 2001 From: maplesyzzurp <117tristan@gmail.com> Date: Sat, 30 May 2026 05:09:17 -0400 Subject: [PATCH] harden(daemon): enforce the API socket resolves inside COVEN_HOME (AUTH.md L134) docs/AUTH.md's "Current hardening gap" requires failing closed when the socket path resolves outside COVEN_HOME, or when socket creation/cleanup would cross the trusted state directory boundary. The path is built as `/coven.sock`, so that held only by construction; make it an explicit guard in bind_api_socket so a future change cannot let it escape. --- crates/coven-cli/src/daemon.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/coven-cli/src/daemon.rs b/crates/coven-cli/src/daemon.rs index a2a828a..0655d85 100644 --- a/crates/coven-cli/src/daemon.rs +++ b/crates/coven-cli/src/daemon.rs @@ -970,6 +970,18 @@ pub fn serve_next_tcp_connection( pub fn bind_api_socket(coven_home: &Path) -> Result { ensure_private_coven_home(coven_home)?; let socket_path = daemon_socket_path(coven_home); + // Fail closed if the socket path would resolve outside the trusted state + // directory: socket creation and cleanup must never cross the COVEN_HOME + // boundary. daemon_socket_path() builds `/coven.sock`, so this is + // an explicit guard so a future change can't let it escape. See docs/AUTH.md + // "Current hardening gap". + if socket_path.parent() != Some(coven_home) { + anyhow::bail!( + "refusing to bind Coven API socket {}: resolves outside Coven home {}", + socket_path.display(), + coven_home.display() + ); + } // Only ever replace a genuine, non-symlink socket. Blindly removing // whatever sits at the path would follow an attacker-planted symlink or // delete an unrelated file. See docs/AUTH.md "Current hardening gap". @@ -1810,6 +1822,15 @@ mod tests { Ok(()) } + #[cfg(unix)] + #[test] + fn daemon_socket_path_stays_inside_coven_home() { + // AUTH.md L134: the socket must resolve directly inside COVEN_HOME, so + // bind_api_socket's containment guard always holds for the derived path. + let home = std::path::Path::new("/some/coven/home"); + assert_eq!(daemon_socket_path(home).parent(), Some(home)); + } + #[cfg(unix)] #[test] fn bind_api_socket_hardens_coven_home_permissions() -> Result<()> {