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.
## 2026-05-25 - TOCTOU Symlink Vulnerability in Config Permission Enforcement
**Vulnerability:** Found `chmod(path, 0o600)` being used directly on a path, which follows symlinks and allows a Time-of-Check to Time-of-Use (TOCTOU) vulnerability where an attacker could replace the config file with a symlink to an arbitrary file, altering its permissions.
**Learning:** System calls that follow symlinks like `chmod` are unsafe when used on user-controlled or mutable directories.
**Prevention:** Always use `open()` with `O_NOFOLLOW` and `O_CLOEXEC` to safely get a file descriptor without resolving symlinks, then apply permissions using `fchmod()`.
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 securely without following symlinks (TOCTOU prevention)
URL(fileURLWithPath: path).withUnsafeFileSystemRepresentation { cPath in
guard let cPath = cPath else { return }
let fd = open(cPath, 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 without requiring read permission first

Opening the config with O_RDONLY before fchmod introduces a regression when the file is owned by the daemon user but currently lacks read bits (for example mode 000 or 200): open fails with EACCES, the permission fix is skipped, and the subsequent contents(atPath:) read fails, leaving reload in an error state. The previous chmod(path, 0o600) path could recover these files by restoring read permission first, so this change can prevent valid configs from being loaded after restrictive mode changes.

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

if fd >= 0 {
fchmod(fd, 0o600)
close(fd)
}
}

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