Skip to content

feat: Workspace filesystem cleanup

87c486d
Select commit
Loading
Failed to load commit list.
Merged

feat: Workspace filesystem cleanup #391

feat: Workspace filesystem cleanup
87c486d
Select commit
Loading
Failed to load commit list.
@sentry/warden / warden: find-bugs completed May 4, 2026 in 15m 57s

3 issues

find-bugs: Found 3 issues (2 medium, 1 low)

Medium

Daemon socket directory in shared tmpdir is predictable and vulnerable to local pre-creation/symlink attacks - `src/daemon/socket-path.ts:29-36`

daemonDirForWorkspaceKey now constructs <tmpdir>/xcodebuildmcp-<12-hex-of-sha256(workspaceRoot)>. On Linux/macOS tmpdir() is typically /tmp, a world-writable directory shared across local users. The 12-hex suffix is fully deterministic from the (often guessable) workspace root path, so a co-resident local user can pre-create the directory (or a symlink at that name) before the victim's daemon starts. ensureSocketDir calls mkdirSync(dir, { recursive: true, mode: 0o700 }), but Node's mkdirSync does not modify mode on an existing directory, so the 0o700 protection is silently bypassed when the path already exists. Combined with removeStaleSocket blindly unlinkSync-ing inside that directory, a local attacker can squat on the path, intercept/disrupt the daemon's IPC socket, or force the daemon to bind into an attacker-controlled location. The previous implementation placed these under ~/.xcodebuildmcp/daemons/<key>, which is inside the user's home directory and not exposed to other local users.

isPidAlive treats EPERM as alive but ignores invalid pid 0/negative which targets process group - `src/utils/process-liveness.ts:1-8`

process.kill(pid, 0) with pid <= 0 has special semantics in POSIX: pid 0 signals every process in the caller's process group, and negative pids signal a process group. Passing such values to isPidAlive will not check liveness of a specific process and may return true based on group membership rather than the intended target. Callers using this for live-owner detection during cleanup could incorrectly conclude that a stale/invalid recorded pid (e.g., 0) is still alive and skip cleanup of artifacts indefinitely.

Low

removeDaemonRegistryEntry and cleanupWorkspaceDaemonFiles silently swallow lock-acquisition failures - `src/daemon/daemon-registry.ts:286-345`

withDaemonRegistryMutationLock returns null when the bounded busy-wait for the daemon registry lock times out (DAEMON_REGISTRY_LOCK_WAIT_MS = 1s). writeDaemonRegistryEntry checks for this null and throws, but removeDaemonRegistryEntry (lines 286-288) and cleanupWorkspaceDaemonFiles (lines 331-345) discard the return value. Under contention from another MCP server, daemon, or CLI, cleanup will silently no-op, leaving stale registry/socket files behind that can mislead subsequent daemon startup or liveness checks.


Duration: 15m 52s · Tokens: 1.3M in / 18.5k out · Cost: $4.48 (+extraction: $0.00, +merge: $0.00)

Annotations

Check warning on line 36 in src/daemon/socket-path.ts

See this annotation in the file changed.

@sentry-warden sentry-warden / warden: find-bugs

Daemon socket directory in shared tmpdir is predictable and vulnerable to local pre-creation/symlink attacks

`daemonDirForWorkspaceKey` now constructs `<tmpdir>/xcodebuildmcp-<12-hex-of-sha256(workspaceRoot)>`. On Linux/macOS `tmpdir()` is typically `/tmp`, a world-writable directory shared across local users. The 12-hex suffix is fully deterministic from the (often guessable) workspace root path, so a co-resident local user can pre-create the directory (or a symlink at that name) before the victim's daemon starts. `ensureSocketDir` calls `mkdirSync(dir, { recursive: true, mode: 0o700 })`, but Node's `mkdirSync` does not modify mode on an existing directory, so the 0o700 protection is silently bypassed when the path already exists. Combined with `removeStaleSocket` blindly `unlinkSync`-ing inside that directory, a local attacker can squat on the path, intercept/disrupt the daemon's IPC socket, or force the daemon to bind into an attacker-controlled location. The previous implementation placed these under `~/.xcodebuildmcp/daemons/<key>`, which is inside the user's home directory and not exposed to other local users.

Check warning on line 8 in src/utils/process-liveness.ts

See this annotation in the file changed.

@sentry-warden sentry-warden / warden: find-bugs

isPidAlive treats EPERM as alive but ignores invalid pid 0/negative which targets process group

process.kill(pid, 0) with pid &lt;= 0 has special semantics in POSIX: pid 0 signals every process in the caller's process group, and negative pids signal a process group. Passing such values to isPidAlive will not check liveness of a specific process and may return true based on group membership rather than the intended target. Callers using this for live-owner detection during cleanup could incorrectly conclude that a stale/invalid recorded pid (e.g., 0) is still alive and skip cleanup of artifacts indefinitely.