Files
orca/mobile/modules/orca-mobile-web-shell/ios/MobileWebShellCsp.swift
T
NeilandArthur Chang 0cba9f2e08 fix(mobile): render offline Nerd Font symbols on first terminal display (#31446)
Fixes #17168

Supersedes community PR #17220. Original contributor credit is preserved below.

Co-authored-by: Arthur Chang <arthurc0102@gmail.com>
2026-10-21 01:17:08 -07:00

57 lines
3.5 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. `https:` for the
// remote images the page already tries to render and cannot: an agent's favicon, which is a
// fixed `google.com/s2/favicons` URL; a project's icon, which is a favicon, an avatar or an
// upload the host names; and whatever an artifact names inside the sealed HTML preview frame,
// which inherits this policy because a `srcdoc` frame has no URL of its own.
//
// Not markdown, not the rich editor and not a review comment's avatar, whatever a later reader
// assumes from ruling 26: markdown paints `![](...)` as a tappable link, the editor is still a
// plain source field, and the avatar is skipped under `Platform.OS !== 'web'` by a card that
// pins the skip in a test. All three are anticipated surfaces, and C7.10 is where they become
// real ones.
//
// The bound is the destination, not the provenance. CSP matches both as schemes, so this admits
// any image URL of either and cannot tell one the page composed from one it was handed; for
// `data:` 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 them, 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. What `https:` adds is a request whose target the rendered content
// chose, so a document can learn it was opened; the shell's Referrer-Policy header keeps the
// session id out.
//
// `http:` stays out: it reaches no image TLS cannot serve, and a cleartext image is readable
// and replaceable in flight by anything on the path.
"img-src 'self' data: blob: https:",
// Terminal fonts use bundled same-origin assets or embedded data URLs.
"font-src 'self' data:",
// 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 previews play bytes downloaded through authenticated RPC, never a network URL.
"media-src blob:",
"object-src 'none'",
"frame-src 'none'",
"child-src 'none'",
"worker-src 'none'",
// `about:` only, so a chat visual's srcdoc frame (which inherits this) keeps its own
// `about:srcdoc` base and in-page links stay in it; no network base can apply.
"base-uri about:",
"form-action 'none'",
"frame-ancestors 'none'"
].joined(separator: "; ")
}