mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
* fix(mobile): let the shell's page paint a file preview A file preview has one shape on the wire: the desktop answers a base64 body and `normalizeMobileFilePreviewResult` composes `data:<mime>;base64,<content>` for React Native Web's `Image`. Under `img-src 'self'` the browser refuses to load it, so every image preview in the page paints nothing — reproduced in the render check, which logged the refusal naming `img-src 'self'` before this. `data:` is granted to images and to nothing else, so what it admits is what the page itself composed out of a reply it already holds; `script-src 'self'` and `connect-src 'self'` are untouched, and `blob:` is not added because nothing in the closure needs one. Both platform pins narrow from "the header contains no `data:`" to "`data:` appears on `img-src` and nowhere else", which is the check that still fails if a later directive grows one. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): say what the data: image case actually loads through Round 1 is right on both counts. In react-native-web 0.21.2 the hidden <img> the Image component renders carries `alt`, `style`, `draggable`, `ref` and `src` and no load handlers at all — it is there for the browser's image context menu and for `getBackgroundSize()`. The load signal comes from `ImageLoader.load`, which is `new window.Image()` with `onload`/`onerror` on it, so the `new Image()` in this case is the same mechanism the screen's own load runs through rather than a stand-in for it. And the screen maps `onImageError` to "Unable to load preview" (`MobileFilePreviewScreen.tsx:282`); "Binary preview unavailable" is the normalizer's `binary_file` branch, which a CSP refusal never reaches. Comment only. No assertion moves. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): state the img-src data: bound as the destination, not provenance "admits only what the page itself built" read as a provenance guarantee, and CSP has none to give: `data:` is matched as a scheme, so the directive admits any `data:` image URL and the browser cannot tell one the page composed from one it was handed. Nor is the content the page's own — the mime type and the base64 body both come from the host, and `normalizeImagePreviewResult` only checks the mime type is a non-empty string. The true bound is where the URL goes: it is never fetched as anything but an image, `img-src` is the only directive admitting it, an image fetch executes nothing (an SVG inside an `<img>` runs no script), and `script-src 'self'`, `connect-src 'self'` and `object-src 'none'` are untouched. Both copies reworded identically, since they are kept in step by the render check's own policy comparison. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
38 lines
2.0 KiB
Swift
38 lines
2.0 KiB
Swift
enum MobileWebShellCsp {
|
|
/// Sent as a response header on the document and nowhere else: a served document must never
|
|
/// carry its own policy, so there is no meta tag to find and no bundle change that can relax it.
|
|
static let header = [
|
|
"default-src 'none'",
|
|
"script-src 'self'",
|
|
// React Native Web 0.21.2 injects its stylesheet at runtime with no nonce support, so the
|
|
// Phase C page cannot paint under 'self' alone (measured: the render check under this exact
|
|
// header). This relaxes styling only; script-src 'self' is untouched.
|
|
"style-src 'self' 'unsafe-inline'",
|
|
// `data:` because a file preview has no other shape: the desktop answers a base64 body and the
|
|
// page composes `data:<mime>;base64,<content>` for React Native Web's Image.
|
|
//
|
|
// The bound is the destination, not the provenance. CSP matches `data:` as a scheme, so this
|
|
// admits any `data:` image URL and cannot tell one the page composed from one it was handed;
|
|
// the mime type and the body are both the host's, and the page only checks the mime type is a
|
|
// non-empty string. What holds is that the URL is never fetched as anything but an image:
|
|
// img-src is the only directive admitting it, an image fetch executes nothing (an SVG inside
|
|
// an <img> runs no script), and script-src 'self', connect-src 'self' and object-src 'none'
|
|
// are untouched.
|
|
"img-src 'self' data:",
|
|
"font-src 'none'",
|
|
// The origin is one read-only directory behind the manifest map, so 'self' reaches nothing the
|
|
// page cannot already read, and the bootstrap page reads ./manifest.json through it. This is
|
|
// the fence for fetch and XMLHttpRequest; the document-start script covers only the two things
|
|
// the native layer cannot see.
|
|
"connect-src 'self'",
|
|
"media-src 'none'",
|
|
"object-src 'none'",
|
|
"frame-src 'none'",
|
|
"child-src 'none'",
|
|
"worker-src 'none'",
|
|
"base-uri 'none'",
|
|
"form-action 'none'",
|
|
"frame-ancestors 'none'"
|
|
].joined(separator: "; ")
|
|
}
|