diff --git a/README.md b/README.md index a69ab35..a66fa6c 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ It's a single Dock app with five panels: It began local-only — hence the name — and 1.0 added the hosted providers behind the same interface, so "local" is now the default rather than the only option. On macOS 26 only Apple's on-device model runs; open-weight (MLX) models and the -hosted providers need macOS 27. **Private Cloud Compute** is wired up but needs -an Apple entitlement that is still pending, so it is currently inert. +hosted providers need macOS 27. **Private Cloud Compute** works too: an app that +uses it needs Apple's Private Cloud Compute entitlement. **Product page:** [thisbrain.ai/locallm](https://thisbrain.ai/locallm) — or get the app directly: diff --git a/docs/sdk-guide.md b/docs/sdk-guide.md index 13f2cab..c87c466 100644 --- a/docs/sdk-guide.md +++ b/docs/sdk-guide.md @@ -706,6 +706,36 @@ policy allows them. Shared context reaches the model with the next `respond` / ` turn — a turn run on `languageModelSession` doesn't send it ([Running a turn](#running-a-turn-and-when-to-use-languagemodelsession)). `ConfirmingToolAuthorizer` remembers the user's answer per view and tool, so a polling view asks once (destructive calls ask each time). +**The sandbox, layer by layer.** A view is HTML and script from someone else's server, so +`MCPAppViewController` contains it in layers, each of which holds even if the one above it failed: + +1. **CSP, declared ∩ allowed.** A server's `_meta.ui.csp` is a request, not a grant: the CSP + actually served on the view's document is each declared origin list intersected with your + `MCPAppsSandboxPolicy` (`allowedConnectOrigins`, `allowedResourceOrigins`, + `allowedFrameOrigins`), so a server update can never widen what your app lets a view reach. + With the default `.closed` policy that is `default-src 'none'`, `connect-src 'none'`, + `frame-src 'none'`, plus `base-uri`, `form-action` and `object-src 'none'`; inline script and + style run, nothing loads from the network. Server-supplied origins are validated strictly + (`https://` / `wss://`, a DNS host, optional port) before they go into the header, and + `baseUriDomains` is never granted. Read `controller.effectiveCSP` for what a view really got. +2. **A network block under the CSP.** A `WKContentRuleList` on the whole web view blocks every + http(s), ws(s) and ftp load outside the effective origins — so even a mis-served CSP lets + nothing out. +3. **A sandboxed iframe.** The view runs in an `