mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +00:00
* Let in-page links switch sections inside a chat visual * Make in-page links work in chat visuals on mobile and in WebKit The first fix (an about:srcdoc base in the visual) only reached desktop Chromium. On mobile, the page hosting the visual carried a policy with base-uri 'none', and a srcdoc visual inherits its host's policy before its own, so the base was refused and an in-page link still took the visual off its page and showed it as failed. The mobile web shell sends the same rule as a header. Separately, WebKit (iOS, and the web client in Safari) fires the frame's load event for an in-page link inside a sandboxed frame, and both hosts treated any second load as the visual navigating away and retired it. - The mobile host page and the mobile web shell header now allow only an about: base, so the shell's own base applies; the visual's own policy still refuses any base it declares. - Instead of retiring a visual on its second load, the host now asks the visual's shell after every load. A later load must be answered by the same run of the shell within the plugin panel watchdog's 5 s deadline; no answer, or an answer from a new run (a reload), still retires it. - Tests: unit tests for the policy, the answer reader and the frame's keep/retire rules, and a Chromium + WebKit test of both mobile surfaces that runs in the mobile web app CI job. * Keep any answered later load, and say plainly what the check is for The previous commit tagged each run of a visual's wrapper with a random id so a reload would still retire it. The visual shares a window with its wrapper, so it could read or set that id itself, and a reload only reruns the same Orca-written document, which is not an escape. Drop it: after any load past the first, the host asks once, and any answer on this visual's channel within the wait keeps it; silence retires it. - Comments now say the check is a tripwire for a frame gone blank or to another page; the frame guard (desktop), the host page's policy plus the app's load filter (mobile) and the sandbox are the boundaries. - Visuals get their own 5 s wait instead of borrowing the plugin panel watchdog's, because a 1 s wait retired a busy visual in WebKit. - Tests: a reload is now kept; going blank, or dropping the base and following a link, still retires. The CI routing for the shared wrapper is now pinned in the path-classification test. * Load the visual links check's mobile modules only where they can build The unit shards run every config/scripts test with only the root dependencies installed. This check imported a mobile/ module at load, and transforming it reads mobile/tsconfig.json, which extends a package those shards do not have. The transform error could not be sent back to the test worker under Bun, so the whole shard stopped with "DataCloneError: The object can not be cloned". Import the visual shell and the mobile host page inside the dependency gate, as the session-dictation check already does, so the shards skip the file without loading them. The mobile web app job, which installs mobile dependencies, still runs it in full.
56 lines
3.4 KiB
Swift
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: 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: "; ")
|
|
}
|