Skip to content

Harden dashboard WebView (disable file/content access, mixed content, JS popups)#54

Merged
hawkff merged 1 commit into
mainfrom
security/webview-hardening
Jun 21, 2026
Merged

Harden dashboard WebView (disable file/content access, mixed content, JS popups)#54
hawkff merged 1 commit into
mainfrom
security/webview-hardening

Conversation

@hawkff

@hawkff hawkff commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Summary

Hardens the Clash/yacd dashboard WebView — the app's highest-risk web surface, since it
loads a user-editable URL (DataStore.yacdURL) with JavaScript + DOM storage enabled.

The yacd dashboard genuinely needs JS + DOM storage + localhost network, so those stay on.
Everything else is now explicitly disabled so the loaded page can never:

  • read the device filesystem (allowFileAccess = false)
  • read content providers (allowContentAccess = false)
  • escalate from file:// origins (allowFileAccessFromFileURLs = false,
    allowUniversalAccessFromFileURLs = false)
  • downgrade to cleartext resources on an https page (mixedContentMode = MIXED_CONTENT_NEVER_ALLOW)
  • spawn JS popups (javaScriptCanOpenWindowsAutomatically = false, setSupportMultipleWindows(false))

Why

Previously only javaScriptEnabled/domStorageEnabled were set; all other WebView settings
relied on platform defaults (and allowFileAccess defaults to true below API 30). A
user-supplied dashboard URL with JS enabled + filesystem access is a real exfiltration surface.

Testing

  • CodeRabbit CLI: No findings.
  • Verified on-device: with Clash API enabled and a VPN connected, the yacd dashboard
    (MetaCubeX/yacd 0.3.6) loads and runs normally from http://127.0.0.1:9090/ui — no
    net::ERR_ACCESS_DENIED, no mixed-content blocks. The lockdown doesn't break the dashboard.

Notes

  • Kept dependency-free (core WebSettings); did not add androidx.webkit. SafeBrowsing/
    WebSettingsCompat could be a later enhancement but isn't needed to close the file/content
    access surface.

Greptile Summary

This PR hardens the Clash/yacd dashboard WebView by explicitly disabling a set of insecure-by-default settings — file-system access, content-provider access, file-URL origin escalation, mixed content, and JS-initiated popups — while leaving javaScriptEnabled and domStorageEnabled on because the dashboard SPA requires them.

  • Adds allowFileAccess = false, allowContentAccess = false, allowFileAccessFromFileURLs = false, and allowUniversalAccessFromFileURLs = false to prevent the user-supplied dashboard URL from reading device files or content providers (especially important below API 30, where these default to true).
  • Sets mixedContentMode = MIXED_CONTENT_NEVER_ALLOW and disables javaScriptCanOpenWindowsAutomatically / setSupportMultipleWindows to close the remaining popup and downgrade surfaces.

Confidence Score: 5/5

Safe to merge — the change is additive security hardening with no functional regressions on the normal dashboard path.

Each new setting is applied correctly inside the WebSettings.apply block (including setSupportMultipleWindows, which is a WebSettings method, not a WebView method). The deprecated allowFileAccessFromFileURLs/allowUniversalAccessFromFileURLs flags are suppressed appropriately and still matter for pre-API-30 devices. The mixedContentMode change only affects pages served over HTTPS that try to load HTTP subresources; the default dashboard on http://127.0.0.1 is unaffected. No pre-existing logic (URL loading, menu handling, error callbacks) is altered.

No files require special attention.

Important Files Changed

Filename Overview
app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt WebView settings block refactored into an apply scope to add explicit security hardening: file/content access disabled, deprecated file-URL cross-origin escalation flags suppressed, mixed-content blocked, and JS popups disabled. No logic changes to URL loading or menu handling.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[WebviewFragment.onViewCreated] --> B[WebView.setWebContentsDebuggingEnabled\nDEBUG builds only]
    B --> C[mWebView.settings.apply]
    C --> D[javaScriptEnabled = true\ndomStorageEnabled = true\nREQUIRED for Clash SPA]
    C --> E[allowFileAccess = false\nallowContentAccess = false\nPREVENTS filesystem/provider reads]
    C --> F[allowFileAccessFromFileURLs = false\nallowUniversalAccessFromFileURLs = false\nPREVENTS file:// origin escalation]
    C --> G[mixedContentMode = NEVER_ALLOW\nPREVENTS http downgrade on https pages]
    C --> H[javaScriptCanOpenWindowsAutomatically = false\nsetSupportMultipleWindows = false\nPREVENTS JS popup windows]
    D & E & F & G & H --> I[mWebView.loadUrl\nDataStore.yacdURL]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[WebviewFragment.onViewCreated] --> B[WebView.setWebContentsDebuggingEnabled\nDEBUG builds only]
    B --> C[mWebView.settings.apply]
    C --> D[javaScriptEnabled = true\ndomStorageEnabled = true\nREQUIRED for Clash SPA]
    C --> E[allowFileAccess = false\nallowContentAccess = false\nPREVENTS filesystem/provider reads]
    C --> F[allowFileAccessFromFileURLs = false\nallowUniversalAccessFromFileURLs = false\nPREVENTS file:// origin escalation]
    C --> G[mixedContentMode = NEVER_ALLOW\nPREVENTS http downgrade on https pages]
    C --> H[javaScriptCanOpenWindowsAutomatically = false\nsetSupportMultipleWindows = false\nPREVENTS JS popup windows]
    D & E & F & G & H --> I[mWebView.loadUrl\nDataStore.yacdURL]
Loading

Reviews (1): Last reviewed commit: "security(webview): lock down the dashboa..." | Re-trigger Greptile

The Clash/yacd dashboard WebView loads a user-editable URL (DataStore.yacdURL) with
JavaScript + DOM storage enabled (the yacd SPA requires both). Everything else is now
explicitly locked down so that page can never read the device filesystem or content
providers, escalate from file:// origins, downgrade to cleartext on an https page, or
spawn JS popups:

- allowFileAccess = false
- allowContentAccess = false
- allowFileAccessFromFileURLs = false
- allowUniversalAccessFromFileURLs = false
- mixedContentMode = MIXED_CONTENT_NEVER_ALLOW
- javaScriptCanOpenWindowsAutomatically = false; setSupportMultipleWindows(false)

Verified on-device: the yacd dashboard (MetaCubeX/yacd 0.3.6) still loads and runs from
http://127.0.0.1:9090/ui — no access-denied / mixed-content errors.
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

WebviewFragment.onViewCreated replaces two individual mWebView.settings assignments with a consolidated apply block that retains JavaScript and DOM storage enablement while adding explicit security restrictions: file/content access disabled, universal file URL access blocked, mixed content prevented, and JS-initiated window opening disallowed.

Changes

WebView Security Configuration

Layer / File(s) Summary
WebView settings consolidation and hardening
app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt
onViewCreated wraps all mWebView.settings assignments in an apply block, keeping javaScriptEnabled and domStorageEnabled true while adding allowFileAccess, allowContentAccess, allowFileAccessFromFileURLs, allowUniversalAccessFromFileURLs set to false, mixedContentMode set to MIXED_CONTENT_NEVER_ALLOW, and setSupportMultipleWindows(false) + javaScriptCanOpenWindowsAutomatically = false.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

Poem

🐇 A rabbit checks the web's open door,
No files sneaking in from the floor,
Mixed content? No way!
Popups kept at bay,
The WebView is safer than before! 🔒

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main security hardening changes to the WebView configuration (disabling file/content access, mixed content, and JS popups).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description is directly related to the changeset, detailing specific security hardening measures applied to the WebView settings in WebviewFragment.kt.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@hawkff hawkff merged commit c89b57f into main Jun 21, 2026
5 checks passed
@hawkff hawkff deleted the security/webview-hardening branch June 21, 2026 17:44
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