Skip to content

VNX-ANDROID-002: Android WebView runs JavaScript from untrusted content

Detects a JavaScript-enabled Android WebView (Java/Kotlin) that loads a URL or HTML from an Intent, deep link, Fragment arguments, external storage or a cleartext http:// address, an evaluateJavascript call built from such a value, a JavaScript bridge added in such a file, and file:// pages given universal or file access.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-ANDROID-002
Severity High
SARIF level warning
Kind sast
Languages Java, Kotlin
CWE CWE-79
CAPEC CAPEC-86
ATT&CK T1059.007
CWSS vector CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H
Tags android webview xss mobile-security
Source internal/sast/rules/vnx-android-002.rego

Overview

This rule reports Android WebViews (Java and Kotlin) that run script an attacker controls. Enabling JavaScript is not a finding on its own. The rule reports a loadUrl, loadData, loadDataWithBaseURL or postUrl call in a file that enables JavaScript when the URL or HTML comes from the launching Intent or a deep link (getIntent(), getStringExtra, getData, getQueryParameter, Fragment arguments), from external storage, or is a cleartext http:// address, either directly or through a variable assigned from such a source up to 15 lines above. It also reports addJavascriptInterface in a file with such a load, and setAllowUniversalAccessFromFileURLs(true) / setAllowFileAccessFromFileURLs(true). A URL checked between the read and the load (startsWith, host or scheme comparison, an allow-list or validator) is not reported, nor are https:// and file:///android_asset/ literals, http://localhost, comments and strings. Flows through other methods or files are not followed. These patterns are captured by CWE-79 (Improper Neutralization of Input During Web Page Generation).

Why This Matters

A WebView with JavaScript enabled and a registered JavascriptInterface gives any JavaScript running in that view direct access to the annotated Java methods. If the WebView ever loads attacker-controlled content, a redirected URL, an injected ad, a compromised CDN response, the attacker can call those Java methods and execute arbitrary native code on the device. On Android < 4.2, all public methods of any registered object were callable; from 4.2 onward only methods annotated with @JavascriptInterface are exposed, but the risk remains high.

Enabling universal file access (setAllowUniversalAccessFromFileURLs) allows a file:// URL to read any file the app has access to, enabling data theft across origins. Several banking apps have been attacked via this vector, where malicious deep-link payloads loaded a crafted file:// URL that exfiltrated the app’s database. CAPEC-86 documents this class of DOM-based XSS attack directly.

What Gets Flagged

// FLAGGED: a URL from the launching Intent loaded into a JavaScript-enabled WebView
webView.getSettings().setJavaScriptEnabled(true);
String target = getIntent().getStringExtra("url");
webView.loadUrl(target);

// FLAGGED: a statement file on external storage
webView.loadUrl("file://" + Environment.getExternalStorageDirectory() + "/statement.html");

// FLAGGED: file:// pages may read any origin
webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
// FLAGGED: a deep-link parameter
settings.javaScriptEnabled = true
val page = intent.data?.getQueryParameter("page")
webView.loadUrl(page!!)

Not flagged: setJavaScriptEnabled(true) with only https:// or file:///android_asset/ loads, or an intent URL checked with Uri.parse(u).host == "help.example.com" before the load.

Remediation

  1. Disable JavaScript unless you own every page the WebView loads. If you are building a hybrid app and must enable JavaScript, use a strict Content Security Policy delivered via HTTP headers or injected via loadDataWithBaseURL.

    // SAFE: JavaScript disabled for content you do not control
    webView.getSettings().setJavaScriptEnabled(false);
  2. Remove addJavascriptInterface from production code. If a JS-to-native bridge is required, use WebMessageListener (API 23+) which scopes the bridge to specific allowed origins, or build the bridge using a safe messaging protocol verified in shouldOverrideUrlLoading.

    // SAFE: origin-scoped message listener instead of addJavascriptInterface
    webView.addWebMessageListener(
        "AndroidBridge",
        new HashSet<>(Arrays.asList("https://app.example.com")),
        (view, message, sourceOrigin, isMainFrame, replyProxy) -> {
            // handle message from trusted origin only
        }
    );
  3. Restrict file access. Call setAllowFileAccess(false), setAllowFileAccessFromFileURLs(false), and setAllowUniversalAccessFromFileURLs(false) explicitly. These settings default to disabled on modern API levels but should be set explicitly to prevent regressions.

    // SAFE: all file access disabled
    WebSettings settings = webView.getSettings();
    settings.setAllowFileAccess(false);
    settings.setAllowFileAccessFromFileURLs(false);
    settings.setAllowUniversalAccessFromFileURLs(false);
  4. Validate URLs before loading. Override shouldOverrideUrlLoading and shouldInterceptRequest to enforce an allowlist of permitted origins. Reject file://, javascript:, data:, and intent:// schemes unless explicitly required.

  5. Enable Safe Browsing. Call WebView.startSafeBrowsing() to enable Google Safe Browsing malicious URL detection in the WebView.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-android-002
// nosec vnx-android-002

Or record an ignore rule: vulnetix ignore add --rule vnx-android-002 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-android-002.rego, cli/internal/sast/docs/vnx-android-002.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.