Skip to content

fix: Linux desktop app freeze — layered CSP with runtime port pinning#181

Open
mempko wants to merge 9 commits intowesm:mainfrom
mempko:fix/linux-desktop-webkitgtk-freeze
Open

fix: Linux desktop app freeze — layered CSP with runtime port pinning#181
mempko wants to merge 9 commits intowesm:mainfrom
mempko:fix/linux-desktop-webkitgtk-freeze

Conversation

@mempko
Copy link

@mempko mempko commented Mar 17, 2026

Summary

  • Fixes Linux desktop (WebKitGTK) freeze caused by restrictive Tauri CSP blocking resources after webview navigates from tauri://localhost to the Go backend at http://127.0.0.1:{port}
  • Adds a Go-side cspMiddleware that sets a port-pinned Content-Security-Policy header on all non-API responses, narrowing Tauri's compile-time wildcard to the exact runtime port
  • Tightens Tauri CSP: explicit script-src 'self' (no wildcard port) so scripts can only load from the app's own origin, addressing the security review concern about arbitrary local port trust

How the two CSP layers interact

Directive Tauri (compile-time) Go server (runtime) Effective (intersection)
default-src 'self' http://127.0.0.1:* 'self' http://127.0.0.1:8081 'self' http://127.0.0.1:8081
script-src 'self' 'self' http://127.0.0.1:8081 'self'
connect-src 'self' http://127.0.0.1:* ws://127.0.0.1:* 'self' http://127.0.0.1:8081 ws://127.0.0.1:8081 'self' http://127.0.0.1:8081 ws://127.0.0.1:8081

Test plan

  • make test — Go tests pass including new CSP middleware tests
  • Build Linux AppImage, launch — UI loads and is fully interactive
  • Inspect response headers — CSP header present with exact port
  • macOS/Windows desktop builds still work

🤖 Generated with Claude Code

@roborev-ci
Copy link

roborev-ci bot commented Mar 17, 2026

roborev: Combined Review (9716c95)

Summary Verdict: This PR introduces significant security regressions by dangerously relaxing the Tauri desktop app's Content
Security Policy (CSP), weakening script-execution protections, and expanding trust to arbitrary localhost origins.

High Severity

  • File: desktop/src-tauri/tauri.conf.json:23
    Description: The new script-src permits both 'unsafe-inline' and 'unsafe-eval', and the frame-ancestors 'none' directive has been completely removed. This significantly weakens XSS and clickjacking protections, making any DOM/XSS bug in the renderer directly exploitable since renderer JS execution in Tauri can often reach privileged native functionality.
    Suggested Fix
    :
    Remove 'unsafe-inline' and 'unsafe-eval' from script-src. If a framework/runtime needs them, fix that specific requirement with nonces or hashes for inline code. Restore the frame-ancestors 'none' directive to maintain previous security boundaries.

  • File:
    desktop/src-tauri/tauri.conf.json:23
    Description: The CSP now trusts http://127.0.0.1:* across multiple directives (default-src, script-src, img-src, style-src, and
    font-src). This is much broader than the app’s threat model requires and expands the attack surface, allowing any arbitrary process listening on any localhost port to become a trusted script or resource origin for the desktop app.
    Suggested Fix: Limit localhost allowances to the minimum required directives (e.g., only
    connect-src for the sidecar, ideally restricted to specific ports if possible). Do not allow localhost in script-src or default-src unless there is a hard, reviewed requirement. Prefer explicit per-directive allowlists over broad fallback trust.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@wesm
Copy link
Owner

wesm commented Mar 17, 2026

Thanks for looking at this. I'll have a closer look today on a Linux box and get this hardened and merged.

@roborev-ci
Copy link

roborev-ci bot commented Mar 17, 2026

roborev: Combined Review (bac6139)

Summary Verdict: The CSP modifications functionally resolve the desktop rendering issue but introduce a security regression by inadvertently allowing script execution from any loopback port.

Medium

  • [desktop/src-tauri/tauri.conf.json:23](/home/roborev/.robore
    v/clones/wesm/agentsview/desktop/src-tauri/tauri.conf.json#L23)

    Adding http://127.0.0.1:* to default-src broadens the Content Security Policy more than intended. Because there
    is no explicit script-src directive, default-src acts as the fallback for scripts, workers, and frames. This allows the desktop webview to load and execute JavaScript from any local port, weakening the application's containment layers and creating a potential execution vector if an attacker controls a local service.

Suggested fix: Keep default-src 'self' and add http://127.0.0.1:* only to the specific directives that actually require it (e.g., connect-src, img-src, style-src, font-src). Additionally, define an explicit
script-src 'self' (and optionally script-src-elem 'self') to ensure scripts can only be loaded from the Tauri origin.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@mempko
Copy link
Author

mempko commented Mar 17, 2026

Trying a different approach, static CSP here is a problem.

@mempko mempko changed the title fix: Linux desktop app freeze caused by restrictive CSP fix: Linux desktop app freeze — layered CSP with runtime port pinning Mar 17, 2026
@roborev-ci
Copy link

roborev-ci bot commented Mar 17, 2026

roborev: Combined Review (496e1db)

The CSP tightening and middleware implementation improve security, but introduce a functional regression regarding origin construction for non-loopback access modes.

Medium

  • [internal/server/server.go:304](/home/roborev/.roborev/clones/wesm/
    agentsview/internal/server/server.go#L304)

    cspMiddleware builds origin and wsOrigin from s.cfg.Host and hard-codes http:// / ws://. That is unsafe for supported non-loopback access modes
    :

    • remote_access / bind-all can use 0.0.0.0 or :: as the listen host, which is not the browser-visible origin.
    • Managed proxy / TLS proxy access will be reached via a different host and often https /
      wss.
    • IPv6 hosts like ::1 need brackets, so formatting the origin directly produces syntactically invalid URIs like http://::1:8080 which browsers will reject.

    The practical regression is that the page may load, but connect-src can
    block websocket/live-update traffic under proxy, remote-access, or IPv6 setups.

    Suggested fix: Derive the CSP source from the actual request origin (r.Host plus request scheme, with trusted proxy handling where appropriate), or stop pinning explicit ws/http origins on
    browser-served pages and rely on 'self' where possible. To properly format addresses, use net.JoinHostPort(host, strconv.Itoa(port)) before prepending the schemes.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link

roborev-ci bot commented Mar 17, 2026

roborev: Combined Review (d9ac59b)

Summary verdict: The CSP implementation has medium-severity issues regarding incomplete interface IP coverage for bind-all mode and overly permissive script sources.

Medium

  • [internal/server/server.go#L320](/home/roborev/.roborev/
    clones/wesm/agentsview/internal/server/server.go#L320)

    Runtime CSP now permits script execution from non-self origins. buildCSPPolicy() builds httpSrcs from loopback aliases plus every configured publicOrigins, then reuses that list
    for both default-src and script-src. This weakens the CSP's XSS containment value, as any HTML/script injection elsewhere in the app becomes easier to weaponize.
    Suggested fix: Keep script-src 'self' unless cross-origin script loading is strictly required. Restrict
    publicOrigins and loopback variants to connect-src only.

  • internal/server/server.go#L323
    buildCSPPolicy fails to add the actual interface IPs when the server is in bind-all mode (0.0.0.0 / ::), only adding loopback aliases. If the UI is opened via a LAN address (e.g., http://192.168 .1.20:8080), the document CSP will not explicitly allow ws://192.168.1.20:8080, which can block websocket connections on some engines/webviews.
    Suggested fix: Pass the same concrete bind-
    all IP list used by the host/CORS logic into buildCSPPolicy, or reuse the existing allowed-origin builder directly so connect-src includes every real reachable origin.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@roborev-ci
Copy link

roborev-ci bot commented Mar 17, 2026

roborev: Combined Review (e5210ad)

Summary Verdict: All agents agree the code is clean; no medium, high, or critical issues were
found.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@mempko
Copy link
Author

mempko commented Mar 17, 2026

Hmm, still not ready. Retesting

@roborev-ci
Copy link

roborev-ci bot commented Mar 18, 2026

roborev: Combined Review (0d6d493)

The PR tightens desktop CSP handling and adds runtime CSP middleware, but introduces a security concern regarding wildcard loopback trust.

Medium

Wildcard loop
back trust in the desktop CSP broadens script execution to any local service

  • Files: [desktop/src-tauri/tauri.conf.json:23](/home/roborev/.roborev/clones/wesm/agentsview/desktop/src-tauri/ta
    uri.conf.json#L23), internal/server/server.go:304
  • Description: The Tauri CSP
    now allows default-src 'self' http://127.0.0.1:* and script-src 'self' http://127.0.0.1:*. That means any process listening on loopback can become a trusted script origin for the desktop webview. The
    new runtime CSP narrows agentsview-served pages to the active port, but that protection only exists when the response comes from this server via cspMiddleware. If the webview is ever redirected or navigated to another localhost page, or another localhost origin is loaded before the runtime header applies, code from an unrelated local service can
    run inside the desktop app context. For a desktop wrapper, trusting all loopback ports is a meaningful expansion of the execution boundary.
  • Suggested Remediation: Keep the compile-time Tauri CSP as narrow as possible. Prefer pinning the exact backend origin at launch time rather than using 127.0 .0.1:*, or confine wildcard loopback access to connect-src only and load executable resources from a fixed trusted origin. In parallel, enforce a strict navigation allowlist in the Tauri layer so the webview cannot be redirected to arbitrary loopback origins.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@mempko
Copy link
Author

mempko commented Mar 18, 2026

Now an issue between native dialogs and the webview on linux. Working on a fix.

mempko and others added 2 commits March 17, 2026 20:37
The Tauri CSP only allowed 'self' for script-src/default-src. After
redirecting from tauri://localhost to the Go backend at
http://127.0.0.1, the CSP blocked resources from the backend origin,
causing the WebKitGTK webview to freeze on Linux.

Add http://127.0.0.1:* to all CSP directives and include
'unsafe-inline'/'unsafe-eval' for scripts so the SPA can load and
run after navigation to the backend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e frame-ancestors

Remove 'unsafe-inline' and 'unsafe-eval' from script-src (not needed
by Svelte/Vite production builds) and restore the frame-ancestors
'none' directive that was dropped in the previous commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@roborev-ci
Copy link

roborev-ci bot commented Mar 18, 2026

roborev: Combined Review (f1a901b)

Verdict: All agents agree the code is clean and no issues were found.


Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

mempko and others added 6 commits March 17, 2026 20:37
The Tauri compile-time CSP must use http://127.0.0.1:* in default-src
because the Go server port is unknown at build time. To prevent this
wildcard from allowing script execution from arbitrary local ports,
the Tauri CSP now sets an explicit `script-src 'self'` (no wildcard).

A new Go-side cspMiddleware sets a port-pinned Content-Security-Policy
header on all non-API responses. Since CSPs are additive (most
restrictive wins), the runtime policy intersects with Tauri's broad
default-src to narrow connect-src/default-src to the exact port.

- Tauri CSP: explicit script-src 'self' (no wildcard port for scripts)
- Tauri CSP: wildcard only in default-src and connect-src (needed for
  cross-origin page load and API/SSE access)
- Go server: cspMiddleware as outermost handler wrapper pins exact port
- Tests: verify CSP present on SPA routes, absent on API routes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address review feedback: cspMiddleware was using fmt.Sprintf to build
origins, which produced invalid URIs for IPv6 (http://::1:8081) and
wrong origins for bind-all mode (http://0.0.0.0:8081).

- Use httpOrigin()/net.JoinHostPort for correct IPv6 bracketing
- Mirror buildAllowedOrigins loopback logic for 0.0.0.0/:: hosts
- Include publicOrigins with https->wss mapping for proxy/TLS setups
- Add test cases for IPv6, bind-all, and public origin scenarios

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous commit removed the wildcard from script-src, img-src,
style-src, and font-src in the Tauri CSP, causing the UI to freeze.

Root cause: 'self' in the Tauri CSP resolves to tauri://localhost
(the Tauri origin), NOT the Go server origin. After the webview
navigates to http://127.0.0.1:{port}, resources served by the Go
server are cross-origin from Tauri's perspective. Without
http://127.0.0.1:* in each directive, WebKitGTK blocks scripts,
styles, images, and fonts from the Go server.

The wildcard port is unavoidable in the Tauri compile-time CSP.
The Go server's runtime CSP (script-src 'self', where 'self' =
http://127.0.0.1:8081) provides the port-pinned tightening via
CSP intersection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WebKitGTK in a Tauri webview does not resolve 'self' to the Go
server origin after navigating from tauri://localhost. Using only
'self' in script-src/default-src causes the UI to freeze because
WebKitGTK blocks all resources from http://127.0.0.1:{port}.

The pinned server origin (exact host:port, not a wildcard) is now
included in all resource directives. Public origins and LAN IPs
remain restricted to connect-src only, so the script execution
surface is limited to the server's own origin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On Linux/WebKitGTK, dismissing a native GTK dialog (e.g. the auto-update
confirmation) can leave the webview frozen — it renders but does not
process input events. Fix by calling set_focus() on the main webview
window in every dialog callback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Google Fonts CDN to style-src and font-src directives so the
frontend can load web fonts. Remove unnecessary frame-ancestors
directive. Set Tauri csp to null since the Go server handles CSP
at runtime, avoiding double-policy conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mempko mempko force-pushed the fix/linux-desktop-webkitgtk-freeze branch from f1a901b to ac090d4 Compare March 18, 2026 04:03
@roborev-ci
Copy link

roborev-ci bot commented Mar 18, 2026

roborev: Combined Review (ac090d4)

Verdict: The
PR introduces security regressions by disabling Tauri's compile-time CSP and omitting clickjacking protections in the new runtime CSP middleware.

High Severity

  • Location: [desktop/src-tauri/tauri.conf.json:23](/home/roborev/.roborev/cl
    ones/wesm/agentsview/desktop/src-tauri/tauri.conf.json#L23)

    • Problem: Setting Tauri security.csp to null removes the desktop shell's compile-time CSP entirely. The new Go-side CSP only covers backend
      -served HTTP pages; it does not protect Tauri-controlled startup/fallback/error surfaces or unexpected tauri:// content. This weakens the desktop app's last line of defense and violates project guidelines.
    • Fix: Restore a restrictive compile-time CSP in tauri.conf.json (preserving at minimum script-src, object-src 'none', base-uri 'none', and frame-ancestors 'none') and use the Go runtime middleware to further narrow it.
  • Location: [internal/server/server.go:408](/home/rob
    orev/.roborev/clones/wesm/agentsview/internal/server/server.go#L408)

    • Problem: buildCSPPolicy() no longer emits a frame-ancestors 'none' directive. This allows the browser-served UI to be embedded in arbitrary
      iframes, reintroducing clickjacking exposure for proxied deployments, remote-access setups, and local loopback binds.
    • Fix: Add frame-ancestors 'none' to the runtime CSP policy for all HTML responses and update the corresponding middleware tests (like TestCSPMiddlewareSetsHeaderOnNon APIRoutes) to expect this directive. Consider also sending X-Frame-Options: DENY as defense in depth.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

… focus restore

- Restore restrictive compile-time CSP in tauri.conf.json with localhost
  and Google Fonts entries needed for WebKitGTK compatibility
- Add frame-ancestors 'none' to runtime CSP middleware and X-Frame-Options:
  DENY as defense in depth against clickjacking
- Delay webview focus restoration by 100ms after GTK dialog dismissal so
  the dialog fully releases focus before set_focus() fires

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@roborev-ci
Copy link

roborev-ci bot commented Mar 18, 2026

roborev: Combined Review (eec9d61)

Verdict: The PR is mostly secure and functional, but requires a CSP update for full IPv6 loopback compatibility.

Medium

  • desktop/src-tauri/tauri.conf.json:23
    *
    Problem: The restored Tauri compile-time CSP only allows 127.0.0.1 and localhost, but this change series explicitly adds IPv6 loopback support on the server side. If the desktop app loads http://[::1]:<port>, the compile-time CSP will
    still block scripts, connections, and other assets before the runtime CSP header can help, making the IPv6 path broken.
    • Fix: Add http://[::1]:* and ws://[::1]:* to the relevant CSP directives, or guarantee that the desktop app always navig
      ates to a whitelisted loopback hostname.

Synthesized from 3 reviews (agents: codex, gemini | types: default, security)

@mempko
Copy link
Author

mempko commented Mar 18, 2026

Does the app, especially the desktop app use IPv6 anywhere between client and server? I can't find it binding to any ipv6 address. Seems like a false positive for the reviewing agent?

@wesm
Copy link
Owner

wesm commented Mar 18, 2026

The security reviewer is susceptible to false positives. You can halt here and let me take a look at this to get it merged, thanks for working on it

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