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 @@ -18,3 +18,7 @@
**Vulnerability:** The application was falling back to storing OBS passwords as plaintext strings inside exported/saved JSON models (`Macro.swift` and `SystemCommand.swift`) if saving to the macOS Keychain failed.
**Learning:** Saving secrets to unencrypted formats simply because secure storage fails is a critical anti-pattern known as "failing open" that results in data exposure.
**Prevention:** Always fail securely. If secure storage operations fail, discard the sensitive credential in memory rather than writing it insecurely to disk, even if it requires the user to re-authenticate later.
## 2026-06-26 - [Denial of Service via Subprocess Deadlock]
**Vulnerability:** The application was vulnerable to an execution deadlock when a spawned shell process emitted output larger than the OS pipe buffer limit (~64KB), causing the `Process` execution framework to hang indefinitely because it called `waitUntilExit()` before reading the pipe.
**Learning:** If a parent process calls `waitUntilExit()` before fully draining the stdout/stderr pipe, the child process will block indefinitely once the buffer fills up, while the parent is blocked waiting for the child to exit. This is a classic deadlock pattern.
**Prevention:** When using `Foundation.Process` (or `NSTask`) with `Pipe`, always read the pipe data (e.g., using `readDataToEndOfFile()`) *before* calling `waitUntilExit()`. Alternatively, use asynchronous readability handlers to continuously drain the buffer without blocking the main execution thread.
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,11 @@ public final class AutomationExecutor {
} catch {
return .failure("\(name) launch failed: \(error.localizedDescription)")
}
process.waitUntilExit()

// Read pipe data BEFORE waitUntilExit to avoid deadlock when
// process output exceeds the pipe buffer size (~64KB).
let data = pipe.fileHandleForReading.readDataToEndOfFile()
process.waitUntilExit()
let output = String(data: data, encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""

Expand Down
Loading