Skip to content

fix(shell): refuse foreign requests at the network layer on Windows; release 0.3.1 - #3

Merged
zknpr merged 4 commits into
mainfrom
fix/webview2-request-pin
Oct 9, 2026
Merged

zknpr merged 4 commits into
mainfrom
fix/webview2-request-pin

Conversation

@zknpr

@zknpr zknpr commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Problem

Candidate QA of the unpublished 0.3.0 draft on Windows 11 (WebView2 154) found that the navigation pin keeps the page in the app but not the data. A compromised page can still exfiltrate:

  • Navigation forms that leak: location.href, location.assign, a link click and a meta refresh.
  • What reaches the network: each sent a real top-level request (sec-fetch-mode: navigate, sec-fetch-dest: document, no prefetch purpose) carrying the URL and query string to a foreign listener, about 10–16 ms after it was issued.
  • What the app saw: the page stayed on http://tauri.localhost, and its stderr logged "refused a navigation away from the app".
  • Why the pin can't stop it: wry cancels NavigationStarting synchronously (args.SetCancel(!allow)), but WebView2 had already sent the request.
  • Other platforms and channels: the same probe recorded zero hits on macOS (WKWebView) and Linux (WebKitGTK). window.open sends nothing.

Precondition: a compromised webview, the same threat model the pin exists for. No exploit path to that is known.

Solution

On Windows, refuse every web request (http, https, ws, wss) to a host other than the app's asset origin (http://tauri.localhost) and IPC origin (http://ipc.localhost) at the network layer. WebView2's WebResourceRequested is raised before a request leaves, and answering there with a local 403 means nothing reaches the network.

Architecture

  • The navigation_pin plugin gains an on_webview_ready hook, so every webview (the configured window and the db-<n> windows) gets the request pin as it becomes ready.
  • request_pin::install (Windows only) adds a * filter. It uses AddWebResourceRequestedFilterWithRequestSourceKinds where available, so iframes and shared workers are covered, as wry does for its own filter. Its handler answers refused requests with a 403 built by the WebView2 environment.
  • wry's custom-protocol handler ignores URIs it doesn't own, so requests for the app's own origins are left untouched for wry to serve.
  • The pure policy, is_app_request, lives in lib.rs and is tested on every platform. Web schemes must target an app origin. Only data:, blob: and about: pass, and every other scheme is refused, file: included. That last part comes from the Codex P1 below: on Windows file://host/share is a UNC/SMB connection that can also leak NTLM credentials. An unparseable URI is refused, and the log line records only the origin.
  • If installation fails, the error goes to stderr instead of closing the window. The navigation pin still applies, and closing would make the app unusable on a WebView2 that lacks an API this needs.

Per-file changes

  • src-tauri/src/request_pin.rs (new): the Windows filter and handler.
  • src-tauri/src/lib.rs: app_request_origins, is_app_request, origin_for_log, install_request_pin and the on_webview_ready hook. Two tests: the policy, and a lockstep requiring every connect-src origin to be admitted.
  • src-tauri/Cargo.toml, Cargo.lock: webview2-com 0.38 and windows 0.61 as Windows dependencies. Both were already locked underneath wry at these versions; only the root package's dependency list changed. The notices drift check still passes.
  • .github/workflows/release.yml: PRs that touch src-tauri/** or viewer-dist/** now run the three-platform build. ci.yml builds on macOS only, so Windows-only code like this would otherwise first compile at release time.
  • Version files: 0.3.1. The 0.3.0 draft is deleted rather than its tag reused, because its attestations name v0.3.0.

Security

This closes the Windows half of the exfiltration channel the navigation pin targets. Subresource requests to foreign hosts were already blocked by CSP; now they are also refused at the network layer on Windows.

Review findings

Finding Verdict Fix
Codex P1, lib.rs:186 on 2eb9d96: non-web schemes pass, so file://host/... (UNC) could slip through CONFIRMED at code level: every scheme other than http/https/ws/wss returned true. Exploitability not measured; Chromium normally refuses web-origin navigations to file: before any request. 1bdbab4: an allowlist of local schemes (data:, blob:, about:). The regression (file://attacker.example/share/x, file:///C:/..., ftp:, chrome-extension:) failed before the fix and passes after it.

Test plan

  • cargo test --lib: 138 passed, including the two new tests. Clippy clean. npm test 27/27. The notices drift check passes.
  • Windows compile: the release workflow's Windows build of this PR passes on 9776023, 2eb9d96 and 1bdbab4.
  • Windows runtime, VM 241, on this PR's own installers (hash-verified in the guest, silent upgrade 0.3.0 → 0.3.1), before and after the P1 fix:
    • Smoke: 6/6, which exercises IPC through http://ipc.localhost.
    • Foreign listener: zero hits for location.href, location.assign, link click and meta refresh; on 0.3.0 each one leaked.
    • App log: "refused a request away from the app" appears exactly for those navigations.
    • CSP: blocks <base> and the form before any request.
    • Control: a same-origin navigation still works, and window.open sends nothing.
    • Evidence is in artifacts/release-0.3.1-candidate/.
  • Linux and macOS: their builds pass, and the module is Windows-only. Linux 0.3.0 had already shown zero hits.

zknpr added 2 commits October 9, 2026 11:05
Candidate QA of 0.3.0 on Windows 11 (WebView2 154) found that the
navigation pin keeps the page in the app but not the data. location.href,
location.assign, a link click and a meta refresh each sent a real
navigation request (sec-fetch-mode: navigate) carrying the URL to a
foreign server, even though NavigationStarting was cancelled and the app
logged the refusal. wry cancels synchronously; WebView2 had already sent
the request. WKWebView and WebKitGTK sent nothing in the same probe.

request_pin (Windows only) adds a "*" WebResourceRequested filter on every
webview as it becomes ready and answers each web request (http, https, ws,
wss) to a host other than the app's asset or IPC origin with a local 403,
before it leaves the machine. data:, blob: and custom schemes are local and
pass; an unparseable URI is refused. wry's custom-protocol handler ignores
URIs it does not own, so the two coexist.

The release workflow now also runs on PRs that touch src-tauri/ or
viewer-dist/: ci.yml builds on macOS only, so Windows- and Linux-only
shell code otherwise first compiled at release time.

webview2-com and windows were already in the lock under wry at the same
versions; only the root package's dependency list changed.
0.3.0 was drafted but never published: Windows candidate QA found the navigation pin did not stop the request carrying a refused navigation's URL. 0.3.1 adds the Windows request pin.
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 1051f076-7141-4709-a198-b7f3eacd2238

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5945617c9e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src-tauri/src/lib.rs Outdated
Comment on lines +184 to +186
match url.scheme() {
"http" | "https" | "ws" | "wss" => is_app_navigation(&url, allowed),
_ => true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject network-backed file URLs

When a compromised page navigates to a URL such as file://secret.attacker.example/share/x, this branch accepts it because every scheme other than HTTP(S)/WS(S) returns true. On Windows, a hosted file:// URI is a UNC network path, and WebView2 raises WebResourceRequested for file schemes, so the handler leaves this outbound request untouched and preserves the navigation-exfiltration channel the new pin is intended to close. Restrict the pass-through list to schemes known to be non-networked, or at least reject file: URLs with a host. See the Microsoft documentation for UNC file URIs and WebView2 request interception.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-09T09:34:42.313360Z 1bdbab4 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

zknpr added 2 commits October 9, 2026 11:21
SECURITY.md and CLAUDE.md describe the WebView2 behaviour and the request pin. Release readiness records what each platform's probe measured, including the 0.3.0 Windows leak that kept that draft unpublished, and the installed-package smoke results of the candidates.
… (UNC)

Codex review (P1): is_app_request passed every scheme other than
http/https/ws/wss. On Windows a file://host/share URI is a UNC path, an SMB
connection to that host that can also hand it the user's NTLM
credentials, so a compromised page could have used it to keep the
exfiltration channel open.

Only data:, blob: and about: pass now; every other scheme is refused. The
app's own custom protocols arrive as http://<name>.localhost on Windows, so
nothing legitimate is lost. Not measured: whether WebView2 would have made
the SMB connection at all (Chromium normally refuses web-origin
navigations to file:). The allowlist closes it either way.

The regression test (file://attacker.example/share/x, file:///C:/...,
ftp:, chrome-extension:) failed before this change and passes after it.
@zknpr
zknpr merged commit d816bea into main Oct 9, 2026
6 checks passed
@zknpr
zknpr deleted the fix/webview2-request-pin branch October 9, 2026 10:02
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.

1 participant