Files
orca/mobile/modules/orca-mobile-web-shell/ios/MobileWebShellCsp.swift
T
Neil a0c46b405f feat(jira): load attachment images outside issue Markdown (#28631)
Load Jira attachment images separately from issue Markdown so descriptions and comments stay small while screenshots load lazily and open in the existing lightbox. Downloads begin when an image mounts, capture its owning host, require negotiated image support, and use bounded authenticated byte streams and caches. Older clients retain their inline-image handlers.

Keep legacy and cancellable downloads in separate in-flight domains so cancelling a new image reader cannot abort a legacy reader; completed bytes share the bounded cache. Preserve the landed assignee, board, transition/comment queue and request lifetimes. Move unchanged project metadata IPC handlers into their domain module and tighten two inherited focus-check ceilings without changing behavior.

Validation:410 tests/54 suites; all changed-source quality stages, lint, format, routing and contract checks; exact final node/web types; independent source review;48/48 fresh hidden Electron checks with226 stable source hashes and nine inspected screenshots. Synthetic transports exercise production clients and UI. Current CI static checks, mobile bundle, wire compatibility, Linux/Windows packaging and Windows serve pass; five test shards were pending at review.

Extends the existing attachment work credited to Frank (@lifefloating) and Brennan Benson in PR #8938; existing contribution credits and media remain in this PR.
2026-10-14 05:33:30 -07:00

56 lines
3.4 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:",
"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 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: "; ")
}