Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@
**Vulnerability:** A process can block (deadlock) when its stdout/stderr pipe fills before the parent reads it, because the child blocks on `write()` while the parent blocks on `waitUntilExit()`.
**Learning:** `pipe.fileHandleForReading.readDataToEndOfFile()` after `process.waitUntilExit()` is the deadlock pattern. Default macOS pipe buffer is ~64KB.
**Prevention:** Read the pipe before/concurrently-with waiting for exit. The simplest pattern is to perform the read inside the same background queue that calls `waitUntilExit()`, capturing the bytes for the caller to use after the dispatch group resolves.

## 2024-05-12 - Insecure Webhook Transmission via HTTP
**Vulnerability:** Autopilot configuration allowed webhooks to use the unencrypted `http` scheme.
**Learning:** Permitting `http` for webhook endpoints exposes potentially sensitive daemon alerts and metrics to interception (unencrypted transmission).
**Prevention:** Always restrict external webhook URLs to strictly use `https` during both configuration validation and parsing.
4 changes: 2 additions & 2 deletions Sources/Cacheout/Headless/StatusSocket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -708,8 +708,8 @@ public enum AutopilotConfigValidator {
if let urlStr = webhook["url"] as? String {
if let url = URL(string: urlStr) {
let scheme = url.scheme?.lowercased() ?? ""
if scheme != "http" && scheme != "https" {
errors.append("webhook: url must use http or https scheme, got '\(scheme)'")
if scheme != "https" {
errors.append("webhook: url must use https scheme, got '\(scheme)'")
}
if url.host == nil || url.host?.isEmpty == true {
errors.append("webhook: url must be an absolute URL with a host")
Expand Down
3 changes: 2 additions & 1 deletion Sources/Cacheout/Headless/WebhookAlerter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,8 @@ extension WebhookAlerter.WebhookConfig {
public static func parse(from json: [String: Any]) -> WebhookAlerter.WebhookConfig? {
guard let webhook = json["webhook"] as? [String: Any],
let urlStr = webhook["url"] as? String,
let url = URL(string: urlStr) else {
let url = URL(string: urlStr),
url.scheme?.lowercased() == "https" else {
return nil
}
let format = webhook["format"] as? String ?? "generic"
Expand Down
12 changes: 12 additions & 0 deletions Tests/CacheoutTests/HeadlessTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,18 @@ final class AutopilotConfigValidatorTests: XCTestCase {
XCTAssertTrue(errors.contains { $0.contains("unsupported format") })
}

func testWebhookInsecureScheme() {
let json = """
{
"version": 1,
"enabled": true,
"webhook": {"url": "http://x.com", "format": "generic", "timeout_s": 5}
}
""".data(using: .utf8)!
let errors = AutopilotConfigValidator.validate(data: json)
XCTAssertTrue(errors.contains { $0.contains("use https scheme") })
}

func testWebhookTimeoutOutOfRange() {
let json = """
{
Expand Down