Skip to content

shell: Enforce script network policy for TextView images - #3230

Merged
huacnlee merged 5 commits into
longbridge:mainfrom
lurenjia534:fix/shell-textview-image-policy
Sep 25, 2026
Merged

huacnlee merged 5 commits into
longbridge:mainfrom
lurenjia534:fix/shell-textview-image-policy

Conversation

@lurenjia534

@lurenjia534 lurenjia534 commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Description

Script-created HTML and Markdown TextViews currently fetch document images with the host's HTTP client without checking the creating script's network capabilities. This includes requests made to measure intrinsic image sizes and table columns.

Capture the creating Policy in the TextView specification and return a GPUI ImageSource::Custom for every document image. A thin element wrapper initializes the document owner during request_layout, so constructing a TextView during CLI check needs no active drawing context. A private Asset implementation uses GPUI's existing asynchronous loading, cache and redraw notifications; its key includes the document owner, isolating different views and policies. Authorize GET requests with Capabilities::may_request, disable automatic redirects and re-authorize each hop. Decode the fetched bytes without passing the original URL back to GPUI. Loads run in the background with a 30-second timeout, an 8 MiB body limit and at most 10 redirects; HTTPS downgrades are refused. Dropping the view cancels pending loads and releases decoded images.

Base gains a narrow image-source override covering inline/block images and measurement, including embedded data URLs. Shell routes every document image through that asset loader and rejects non-HTTP(S), relative and credential-bearing URLs. Failure or pending loads cannot fall back to the host loader. Native TextViews without an override keep their existing behavior. This PR only addresses document image loading.

Public API

gpui-base

pub fn TextView::image_source<F>(self, resolver: F) -> Self
where
    F: Fn(&gpui::SharedUri) -> gpui::ImageSource + Send + Sync + 'static;

Override every document image source for rendering and intrinsic-size measurement; the returned source is authoritative, including when loading fails or remains pending.

Breaking Changes

Shell TextView.html(id, text) and TextView.markdown(id, text) retain their signatures, but remote images now require the creating script's GET grant. Non-HTTP(S) document images (including data:) are refused. For an image at https://images.example/image.png, embedders must explicitly grant it:

- Policy::new()
+ Policy::new().with_capabilities(Capabilities::new().http_requests([
+     HttpRequestGrant::new("images.example", ["GET"], ["/image.png"], [] as [&str; 0]),
+ ]))

Every redirect target also needs authorization. The application-asset image(path) API is unaffected.

How to Test

The image tests intercept the actual HTTP transport used by this implementation and assert RedirectPolicy::NoFollow. They cover positive PNG loading and decoding, denied initial requests, scheme/host/path/port/method grants, authorized and denied redirects, downgrade refusal, response limits, concurrent policies, HTML/Markdown, inline/block/table images, intrinsic measurement, repeated frames and embedded data rejection. A separate regression test calls ShellRuntime::check with plain HTML and Markdown TextViews outside drawing; it reproduced the lifecycle panic before the fix.

  • cargo test -p gpui-shell --lib --locked -- --skip tests::standard_runtime --skip tests::network --skip tests::fs --skip tests::process --skip tests::benchmark: 663 passed (same exclusions as the runtime-core CI job).

  • cargo test -p gpui-base --lib text:: --locked: 250 passed.

  • cargo clippy -p gpui-shell --all-targets --locked -- -D warnings: passed.

  • cargo fmt --all -- --check and git diff --check: passed.

  • cargo run -p gpui-base-examples --bin components --locked -- text-view: built and launched on Linux (startup smoke check only). No manual interaction, screenshot validation or cross-platform performance profiling was completed. Rendering and measurement coverage comes from the GPUI visual tests above.

  • npm --prefix crates/component-shell/tests/types ci and npm --prefix crates/component-shell/tests/types test: passed, including the CLI check that previously panicked.

Checklist

  • I have read the contributing guide and kept this PR focused on one problem.
  • Reviewed the changes and the AI-assisted code.
  • Passed cargo run for story tests related to the changes.
  • Tested macOS, Windows and Linux platforms performance (not a platform-specific change).

lurenjia534 and others added 4 commits September 25, 2026 17:17
GPUI renders an SVG <image> whose href is not a data URL from the local
file it names, so an SVG fetched under a GET grant could draw files the
script has no grant to read. Parse the SVG with a resolver that records
such an href before rendering, and refuse the image when it has one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
huacnlee
huacnlee previously approved these changes Sep 25, 2026

@huacnlee huacnlee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Two notes:

  1. I pushed fe5ad9a to close a local-file read through SVG images. GPUI's SvgRenderer uses usvg's default image resolver, which reads an <image href> that is not a data URL from disk (absolute paths as written, relative ones from the working directory). An SVG served from a granted host could therefore draw local files the script has no grant to read. Decoding now parses the SVG first with a resolver that records such hrefs, and refuses the image when it has one. A test covers it.

  2. Since the image cache is now scoped to the view's keyed state, a TextView that is not drawn for a frame (scrolled out of a list, conditionally hidden) fetches its images again when it reappears. That is the right trade for isolating grants, and the requests go through the host's HttpClient, so a host client that honors HTTP Cache-Control avoids the network round trip; no change needed here.

# Conflicts:
#	crates/base/src/text/text_view.rs
@huacnlee
huacnlee merged commit 23e259f into longbridge:main Sep 25, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants