diff --git a/CHANGELOG.md b/CHANGELOG.md index 285ed78..3119e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,6 +106,8 @@ Cutting that release is tracked in ### Fixed +- The macOS app no longer disables App Transport Security process-wide. Its + HTTP compatibility exception is limited to browser web content. - WebKit and Chromium now agree on empty-history `back` failures and enforce the same bounded key input before dispatch. - Portable name characters, local-development hosts, scroll bounds, and diff --git a/SECURITY.md b/SECURITY.md index 1214ff6..4419d95 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ # Security policy -Headless exists to give an AI agent a browser it *cannot* misuse. The safety +Headless exists to give an AI agent a browser it _cannot_ misuse. The safety rules are enforced by the host process, not by prompting, so a vulnerability here is a vulnerability in the product's core promise. We take reports seriously. @@ -28,18 +28,18 @@ we will agree a timeline with you. These are host-enforced contracts. Anything that defeats one is in scope: -| Boundary | Expected behaviour | -| --- | --- | -| **No arbitrary code execution** | There is no JavaScript-evaluation verb and no shell verb. Reaching arbitrary in-page or host execution through the protocol is a vulnerability. | -| **Navigation** | HTTP/HTTPS only. `file:`, `javascript:`, `data:`, credential-bearing URLs, and external application schemes must be refused at every layer. | -| **Downloads** | Page-initiated downloads are denied. Executables, installers, scripts, libraries, and disk images are blocked by extension. | -| **Control plane** | A `0600` Unix socket inside a `0700` per-user directory, with a peer-UID check. There is no TCP listener and no Chromium debug port. Any remote reachability is a vulnerability. | -| **Artifacts** | Bare validated names, `O_EXCL` creation at `0600` inside a `0700` root, never overwritten. Path traversal or reading outside the store is a vulnerability. | -| **Secrets** | Cookie and storage *values* require both `--values` and `HEADLESS_ALLOW_SENSITIVE_DIAGNOSTICS=1`. Authorization, cookie, token, and secret headers, plus URL credentials, are always redacted. Flow recordings never contain typed values. | -| **Untrusted content** | Everything derived from a page is marked `untrustedContent` and is never executed as a command. A page that induces the host to act on its own text is a vulnerability. | -| **Sandbox** | The Linux host refuses to run as root and never passes `--no-sandbox`. Snap Chromium is rejected before launch. | - -Prompt injection that merely *persuades an agent* to do something within these +| Boundary | Expected behaviour | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **No arbitrary code execution** | There is no JavaScript-evaluation verb and no shell verb. Reaching arbitrary in-page or host execution through the protocol is a vulnerability. | +| **Navigation** | HTTP/HTTPS only. `file:`, `javascript:`, `data:`, credential-bearing URLs, and external application schemes must be refused at every layer. | +| **Downloads** | Page-initiated downloads are denied. Executables, installers, scripts, libraries, and disk images are blocked by extension. | +| **Control plane** | A `0600` Unix socket inside a `0700` per-user directory, with a peer-UID check. There is no TCP listener and no Chromium debug port. Any remote reachability is a vulnerability. | +| **Artifacts** | Bare validated names, `O_EXCL` creation at `0600` inside a `0700` root, never overwritten. Path traversal or reading outside the store is a vulnerability. | +| **Secrets** | Cookie and storage _values_ require both `--values` and `HEADLESS_ALLOW_SENSITIVE_DIAGNOSTICS=1`. Authorization, cookie, token, and secret headers, plus URL credentials, are always redacted. Flow recordings never contain typed values. | +| **Untrusted content** | Everything derived from a page is marked `untrustedContent` and is never executed as a command. A page that induces the host to act on its own text is a vulnerability. | +| **Sandbox** | The Linux host refuses to run as root and never passes `--no-sandbox`. Snap Chromium is rejected before launch. | + +Prompt injection that merely _persuades an agent_ to do something within these boundaries is not a host vulnerability — but if page content can escape the `untrustedContent` marking or reach a privileged path, that is. @@ -61,6 +61,9 @@ These are documented design boundaries, not defects: chrome, other applications, or audio. - **Network mocking is Linux-only.** macOS returns `UNSUPPORTED_CAPABILITY` rather than partially emulating traffic control. +- **HTTP on macOS.** The ATS exception is limited to `WKWebView` so browser + pages can use HTTP when required. Native application networking retains the + default ATS protections. ## Supported versions diff --git a/apps/headless/Tests/macos-bundle-security.sh b/apps/headless/Tests/macos-bundle-security.sh new file mode 100755 index 0000000..cd4751c --- /dev/null +++ b/apps/headless/Tests/macos-bundle-security.sh @@ -0,0 +1,23 @@ +#!/bin/sh +set -eu + +PLIST="${1:?usage: macos-bundle-security.sh /path/to/Info.plist}" + +fail() { + echo "macOS bundle security: $1" >&2 + exit 1 +} + +command -v plutil >/dev/null 2>&1 || fail "plutil is required" +plutil -lint "$PLIST" >/dev/null || fail "Info.plist is invalid" + +if plutil -extract NSAppTransportSecurity.NSAllowsArbitraryLoads raw "$PLIST" >/dev/null 2>&1; then + fail "NSAllowsArbitraryLoads must not disable ATS for the entire app" +fi + +WEB_CONTENT_EXCEPTION="$( + plutil -extract NSAppTransportSecurity.NSAllowsArbitraryLoadsInWebContent raw "$PLIST" 2>/dev/null +)" || fail "the WKWebView-scoped ATS exception is missing" +test "$WEB_CONTENT_EXCEPTION" = "true" || fail "the WKWebView-scoped ATS exception must be true" + +echo "macOS bundle ATS configuration passed" diff --git a/apps/headless/build.sh b/apps/headless/build.sh index 8bb47b0..5b21f6f 100755 --- a/apps/headless/build.sh +++ b/apps/headless/build.sh @@ -94,13 +94,13 @@ cat > "$APP/Contents/Info.plist" <NSSupportsAutomaticGraphicsSwitching NSAppTransportSecurity - NSAllowsArbitraryLoads NSAllowsArbitraryLoadsInWebContent NSHumanReadableCopyrightheadless — the browser that isn’t there PLIST +Tests/macos-bundle-security.sh "$APP/Contents/Info.plist" # Passkeys require Apple's restricted web-browser.public-key-credential # entitlement backed by a provisioning profile; macOS SIGKILLs ad-hoc builds diff --git a/docs/roadmap/improvements-backlog.md b/docs/roadmap/improvements-backlog.md index 6147d62..5e307f9 100644 --- a/docs/roadmap/improvements-backlog.md +++ b/docs/roadmap/improvements-backlog.md @@ -380,8 +380,10 @@ Owner-decided scope: package managers, no hosted service. default `HEADLESS_VERSION` is 1.0.0. - **E7.** ([#45](https://github.com/LockInTime/headless/issues/45)) Cut a release: everything since v1.0.2 (capture formats, context pruning) is unreleased. -- **E8.** ([#46](https://github.com/LockInTime/headless/issues/46)) `NSAllowsArbitraryLoads` is blanket-true (`build.sh:86-90`); scope - it (localhost exception) if WKWebView allows. +- **E8.** [x] ([#46](https://github.com/LockInTime/headless/issues/46)) ~~`NSAllowsArbitraryLoads` is blanket-true + (`build.sh:86-90`); scope it (localhost exception) if WKWebView allows.~~ + **Done:** native app networking retains ATS while the HTTP compatibility + exception is limited to `WKWebView`; the bundle build asserts this boundary. ## §F — Website & docs (Phase 5)