feat: Workspace filesystem cleanup #391
3 issues
code-review: Found 3 issues (2 medium, 1 low)
Medium
Stale socket no longer removed when registry metadata is missing - `src/cli/daemon-control.ts:90-95`
The previous implementation always called removeStaleSocket(socketPath) at the end of forceStopDaemon, ensuring an orphaned socket file would be cleaned up even when no registry entry existed. The new implementation throws early when findDaemonRegistryEntryBySocketPath returns null, so stale sockets without a matching registry entry are never cleaned up by this path. This can leave orphaned socket files on disk that prevent subsequent daemon startup from binding to the expected path.
Also found at:
src/cli/daemon-control.ts:91-95
Daemon socket directory placed in shared tmpdir with predictable name enables pre-creation DoS - `src/daemon/socket-path.ts:21-36`
daemonRunDir() defaults to os.tmpdir() (typically /tmp, shared/world-writable with sticky bit), and daemonDirForWorkspaceKey produces a deterministic name xcodebuildmcp-<12-hex> derived from the workspace path hash. A local unprivileged attacker who can predict or enumerate workspace hashes can pre-create that directory under their own UID before the daemon starts. ensureSocketDir will then skip mkdirSync (because existsSync is true), and validateSocketDir will throw on the uid mismatch, preventing the daemon from ever starting for that workspace. The validation correctly defends against symlink/uid hijacking of an existing dir, but the predictable name in a shared directory still allows trivial denial-of-service against any user on a multi-tenant host.
Also found at:
src/daemon/socket-path.ts:89-95
Low
chmod-on-detect repairs perms after potential exposure window - `src/daemon/socket-path.ts:84-86`
validateSocketDir lazily chmods the directory to 0o700 only when it detects loose perms (stat.mode & 0o077 !== 0). If a previous run (or external actor with same UID) left the directory world/group-readable, any socket, log, or daemon.json that was created during that window may already have been observable. The fix corrects future state but does not audit or roll prior contents. Consider failing closed (refusing to start) when perms are unexpectedly loose, instead of silently repairing.
Duration: 15m 8s · Tokens: 1.2M in / 14.3k out · Cost: $7.21 (+merge: $0.00)
Annotations
Check warning on line 95 in src/cli/daemon-control.ts
github-actions / warden: code-review
Stale socket no longer removed when registry metadata is missing
The previous implementation always called removeStaleSocket(socketPath) at the end of forceStopDaemon, ensuring an orphaned socket file would be cleaned up even when no registry entry existed. The new implementation throws early when findDaemonRegistryEntryBySocketPath returns null, so stale sockets without a matching registry entry are never cleaned up by this path. This can leave orphaned socket files on disk that prevent subsequent daemon startup from binding to the expected path.
Check warning on line 95 in src/cli/daemon-control.ts
github-actions / warden: code-review
[YSU-WDD] Stale socket no longer removed when registry metadata is missing (additional location)
The previous implementation always called removeStaleSocket(socketPath) at the end of forceStopDaemon, ensuring an orphaned socket file would be cleaned up even when no registry entry existed. The new implementation throws early when findDaemonRegistryEntryBySocketPath returns null, so stale sockets without a matching registry entry are never cleaned up by this path. This can leave orphaned socket files on disk that prevent subsequent daemon startup from binding to the expected path.
Check warning on line 36 in src/daemon/socket-path.ts
github-actions / warden: code-review
Daemon socket directory placed in shared tmpdir with predictable name enables pre-creation DoS
`daemonRunDir()` defaults to `os.tmpdir()` (typically `/tmp`, shared/world-writable with sticky bit), and `daemonDirForWorkspaceKey` produces a deterministic name `xcodebuildmcp-<12-hex>` derived from the workspace path hash. A local unprivileged attacker who can predict or enumerate workspace hashes can pre-create that directory under their own UID before the daemon starts. `ensureSocketDir` will then skip `mkdirSync` (because `existsSync` is true), and `validateSocketDir` will throw on the uid mismatch, preventing the daemon from ever starting for that workspace. The validation correctly defends against symlink/uid hijacking of an existing dir, but the predictable name in a shared directory still allows trivial denial-of-service against any user on a multi-tenant host.
Check warning on line 95 in src/daemon/socket-path.ts
github-actions / warden: code-review
[55B-9B7] Daemon socket directory placed in shared tmpdir with predictable name enables pre-creation DoS (additional location)
`daemonRunDir()` defaults to `os.tmpdir()` (typically `/tmp`, shared/world-writable with sticky bit), and `daemonDirForWorkspaceKey` produces a deterministic name `xcodebuildmcp-<12-hex>` derived from the workspace path hash. A local unprivileged attacker who can predict or enumerate workspace hashes can pre-create that directory under their own UID before the daemon starts. `ensureSocketDir` will then skip `mkdirSync` (because `existsSync` is true), and `validateSocketDir` will throw on the uid mismatch, preventing the daemon from ever starting for that workspace. The validation correctly defends against symlink/uid hijacking of an existing dir, but the predictable name in a shared directory still allows trivial denial-of-service against any user on a multi-tenant host.