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 - [Foundation.Process Pipe Deadlock]
**Vulnerability:** Execution frameworks using `Foundation.Process` to run shell commands or scripts were vulnerable to a pipe deadlock and Denial of Service (DoS) if the child process output exceeded the OS pipe buffer (~64KB).
**Learning:** Calling `process.waitUntilExit()` before fully reading the child process's output pipe causes the parent to block waiting for the child, while the child blocks waiting for the parent to drain the pipe buffer.
**Prevention:** Always read data from the pipe (e.g., `pipe.fileHandleForReading.readDataToEndOfFile()`) *before* calling `process.waitUntilExit()` to ensure the pipe is drained as the child writes.
5 changes: 3 additions & 2 deletions TriggerKit/Sources/TriggerKitRuntime/AutomationExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,10 @@ public final class AutomationExecutor {
} catch {
return .failure("\(name) launch failed: \(error.localizedDescription)")
}
process.waitUntilExit()

let data = pipe.fileHandleForReading.readDataToEndOfFile()
process.waitUntilExit()

let output = String(data: data, encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""

Expand Down Expand Up @@ -731,11 +732,11 @@ private final class ShellCommandRunner: @unchecked Sendable {
pgrep.standardError = FileHandle.nullDevice
do {
try pgrep.run()
pgrep.waitUntilExit()
} catch {
return []
}
let data = pipe.fileHandleForReading.readDataToEndOfFile()
pgrep.waitUntilExit()
let output = String(data: data, encoding: .utf8) ?? ""
return output
.split(whereSeparator: \.isNewline)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1405,10 +1405,9 @@ final class UniversalControlMouseRelay: @unchecked Sendable {
NSLog("[UCMouseRelay] Could not run tailscale status: %@", String(describing: error))
return []
}
let data = pipe.fileHandleForReading.readDataToEndOfFile()
process.waitUntilExit()
guard process.terminationStatus == 0 else { return [] }

let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let peers = object["Peer"] as? [String: Any] else {
return []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,9 @@ private enum OBSMediaMTXManager {
which.standardOutput = outPipe
which.standardError = Pipe()
try? which.run()
let data = outPipe.fileHandleForReading.readDataToEndOfFile()
which.waitUntilExit()
if which.terminationStatus == 0 {
let data = outPipe.fileHandleForReading.readDataToEndOfFile()
if let path = String(data: data, encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines),
!path.isEmpty,
Expand Down Expand Up @@ -246,6 +246,12 @@ private enum OBSMediaMTXManager {
} catch {
return false
}
if let outPipe = p.standardOutput as? Pipe {
_ = outPipe.fileHandleForReading.readDataToEndOfFile()
}
if let errPipe = p.standardError as? Pipe {
_ = errPipe.fileHandleForReading.readDataToEndOfFile()
}
p.waitUntilExit()
return p.terminationStatus == 0
}
Expand Down
Loading