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-24 - TOCTOU Vulnerability in File Permissions
**Vulnerability:** Used `chmod()` on a file path directly to enforce 0600 permissions.
**Learning:** `chmod()` is vulnerable to Time-of-Check to Time-of-Use (TOCTOU) attacks, as the file could be swapped with a symlink right before `chmod` executes.
**Prevention:** Always use `open()` with the `O_NOFOLLOW` flag to safely obtain a file descriptor, and then apply permissions using `fchmod()`.
12 changes: 10 additions & 2 deletions Sources/Cacheout/Headless/DaemonMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -966,8 +966,16 @@ public actor DaemonMode: StatusSocket.DataSource {
return
}

// Enforce 0600 permissions
chmod(path, 0o600)
// Enforce 0600 permissions using O_NOFOLLOW to prevent TOCTOU symlink attacks
let url = URL(fileURLWithPath: path)
url.withUnsafeFileSystemRepresentation { cPath in
guard let cPath = cPath else { return }
let fd = open(cPath, O_RDONLY | O_NOFOLLOW)
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 Avoid O_RDONLY when opening file for fchmod

Opening the config file with O_RDONLY | O_NOFOLLOW can fail with EACCES when the file is owner-owned but currently not readable (for example mode 0000/0200), so the permission fix is skipped and the subsequent contents(atPath:) read fails. This is a regression from the previous chmod(path, 0o600) behavior, which could recover such files by tightening permissions first, and it can break startup/reload for mis-permissioned autopilot configs.

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

if fd >= 0 {
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 Reject config when O_NOFOLLOW open fails

The new secure-open block silently ignores open(..., O_NOFOLLOW) failures and proceeds to load the config by path, so a symlinked autopilot.json now bypasses the intended 0600-hardening step entirely while still being consumed. Because O_NOFOLLOW is specifically meant to fail on symlinks, this needs an explicit error path instead of continuing with contents(atPath:) after fd < 0.

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

fchmod(fd, 0o600)
close(fd)
}
}

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