Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@
**Vulnerability:** External shell command executed in `listLocalSnapshots()` triggered a deadlock when `tmutil` output exceeded 64KB, because stdout and stderr were read synchronously inside the process termination handler.
**Learning:** In Swift, reading from a process pipe synchronously inside a `terminationHandler` can result in a permanent deadlock if the child blocks writing to a full pipe, preventing it from exiting.
**Prevention:** Asynchronously drain pipes continuously while the process is running using background queues.
## 2024-05-21 - TOCTOU Symlink Vulnerability in File Permission Enforcement
**Vulnerability:** Used `chmod(path, 0o600)` directly on the autopilot config file path, which could be exploited via a TOCTOU symlink attack to change permissions on arbitrary files.
**Learning:** Using path-based permission changes (`chmod`, `setAttributes`) on potentially user-controlled paths is vulnerable to symlink races.
**Prevention:** Use `open()` with `O_NOFOLLOW` and `O_CLOEXEC` flags to get a secure file descriptor, then apply permissions using `fchmod()`. Always use `URL(fileURLWithPath:).withUnsafeFileSystemRepresentation` to bridge paths.
11 changes: 9 additions & 2 deletions Sources/Cacheout/Headless/DaemonMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -966,8 +966,15 @@ public actor DaemonMode: StatusSocket.DataSource {
return
}

// Enforce 0600 permissions
chmod(path, 0o600)
// Enforce 0600 permissions safely to prevent TOCTOU vulnerabilities
URL(fileURLWithPath: path).withUnsafeFileSystemRepresentation { pathPtr in
guard let ptr = pathPtr else { return }
let fd = open(ptr, O_RDONLY | O_NOFOLLOW | O_CLOEXEC)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Open config fd without requiring read permission

Opening the file with O_RDONLY makes the permission-fix path fail for owner-unreadable configs (for example mode 0200 or 0000): open returns EACCES, fchmod never runs, and reload later errors on read. Before this commit, chmod(path, 0o600) could still repair those modes first, so this is a functional regression in self-healing permission enforcement. Consider using an access mode that can obtain an fd for fchmod without requiring immediate read permission (or retrying with a write-capable open).

Useful? React with πŸ‘Β / πŸ‘Ž.

if fd >= 0 {
fchmod(fd, 0o600)
close(fd)
}
Comment on lines +973 to +976
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject config when secure permission open fails

When open(..., O_NOFOLLOW | O_CLOEXEC) fails (for example, if autopilot.json is a symlink), this block silently skips fchmod and execution continues to contents(atPath:), which still loads the config. That means the intended 0600 enforcement is bypassed for exactly the untrusted-path case this change targets, leaving a symlinked or overly-permissive config file accepted. Please treat open/fchmod failure as a reload error (or read via the same validated fd) instead of continuing.

Useful? React with πŸ‘Β / πŸ‘Ž.

}

// Read file
guard let data = FileManager.default.contents(atPath: path) else {
Expand Down
Loading