Back to Blog
critical SEVERITY7 min read

How Open Redirect via Unvalidated URL Input happens in Weex and how to fix it

A critical open redirect vulnerability was discovered in `weex/src/index.we` where the `onclick()` handler passed raw user input directly to `navigator.push()` without any URL validation. An attacker could supply a `javascript:` URI or a phishing URL, causing the app to navigate to arbitrary destinations. The fix adds a strict `https?://` protocol check before any navigation occurs.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 26, 2026•Reviewed August 26, 2026

Answer Summary

This is an Open Redirect vulnerability (CWE-601) in a Weex component (`weex/src/index.we`, line 26), where the `onclick()` handler read a URL from a user-controlled input field and passed it directly to `navigator.push()` without validation. An attacker could inject a `javascript:` URI or a phishing URL as the navigation target. The fix adds a regular-expression guard (`/^https?:\/\/.+/`) that rejects any input that does not begin with `http://` or `https://`, halting navigation and displaying an "Invalid URL" toast to the user.

Vulnerability at a Glance

cweCWE-601
fixAdded a regex check (`/^https?:\/\/.+/`) before calling `navigator.push()`, rejecting non-HTTP(S) URLs and showing an error toast
riskAttacker-controlled navigation to phishing sites or execution of javascript: URIs
languageWeex (JavaScript / Vue-like DSL)
root causeUser-supplied input from an `<input>` element was passed directly to `navigator.push()` with no protocol or domain validation
vulnerabilityOpen Redirect / Unvalidated URL Redirect

The Weex Component That Would Navigate Anywhere You Asked

The weex/src/index.we file is responsible for handling user-driven navigation inside a Weex mobile application. Its onclick() handler reads a path from an <input> element and immediately hands it to navigator.push() — the Weex API that drives in-app page transitions. At first glance this looks like a convenience feature. In practice, it was an open invitation for attackers to redirect users wherever they wanted.

This post breaks down exactly how the vulnerability works, what an attacker could do with it, and how a five-line regex guard closes the hole entirely.


The Vulnerability Explained

What the code did before the fix

Inside weex/src/index.we, starting at line 24, the onclick() handler looked like this:

onclick() {
  const path = this.$el('input').attr.value;

  navigator.push({
    url: path,
    animation: 'true',
  });
}

The variable path is populated directly from whatever the user typed into the input field — this.$el('input').attr.value. That value is then passed verbatim as the url property to navigator.push(). There is no length check, no protocol check, no domain allowlist, and no sanitization of any kind.

Why this is dangerous

navigator.push() in Weex accepts a URL and navigates the WebView to it. Because the value comes straight from user input with zero filtering, an attacker (or a malicious link that pre-fills the field) can supply:

Payload Effect
javascript:alert(document.cookie) Executes arbitrary JavaScript in the WebView context, potentially stealing session tokens
https://evil-phishing-site.com/login Silently redirects the user to a convincing fake login page
file:///etc/passwd On some Weex/WebView configurations, reads local files
data:text/html,<script>... Injects and runs an inline HTML page

The most realistic attack scenario for a mobile app is a phishing redirect: an attacker sends a deep-link or in-app message that pre-populates the input with https://accounts.example-fake.com/signin. The user sees the familiar "navigate" button, taps it, and lands on a credential-harvesting page that mirrors the real app's login screen.

Because this is production code (not a test helper), every user of the application is exposed.

CWE Classification

This vulnerability maps to CWE-601: URL Redirection to Untrusted Site ('Open Redirect'). It also has characteristics of CWE-20: Improper Input Validation since the root cause is the complete absence of input checks before a sensitive API call.


The Fix

What changed

The fix inserts a validation block between reading path and calling navigator.push(). Here is the complete before/after comparison:

Before (vulnerable):

onclick() {
  const path = this.$el('input').attr.value;

  navigator.push({
    url: path,
    animation: 'true',
  });
}

After (fixed):

onclick() {
  const path = this.$el('input').attr.value;

  if (!/^https?:\/\/.+/.test(path)) {
    modal.toast({ 'message': 'Invalid URL', 'duration': 1 });
    return;
  }

  navigator.push({
    url: path,
    animation: 'true',
  });
}

How the fix works

The regular expression /^https?:\/\/.+/ enforces two things:

  1. Protocol allowlist — the URL must begin with http:// or https://. This immediately blocks javascript:, data:, file:///, vbscript:, and every other non-HTTP scheme that could be abused.
  2. Non-empty path — the .+ after the protocol separator ensures there is at least one character following ://, preventing bare-protocol strings like https:// from slipping through.

If the check fails, modal.toast() shows the user a one-second "Invalid URL" message and the function returns early — navigator.push() is never reached. Valid http:// and https:// URLs pass through unmodified, so legitimate navigation behaviour is completely preserved.

Why this specific guard matters

The key security property introduced here is protocol enforcement at the call site. By checking the URL at the exact point where it enters navigator.push(), the fix ensures that no code path — regardless of how path is assembled — can bypass the check. This is the correct place to validate: as close to the dangerous sink as possible.


Key Takeaways

  • this.$el('input').attr.value in Weex is fully attacker-controlled — never pass it to navigator.push() without validation, just as you would never pass a raw query string to a database query.
  • javascript: URIs are the silent killer — a single unguarded navigator.push({ url: userInput }) call is enough to execute arbitrary JavaScript inside a WebView.
  • Protocol allowlisting (^https?://) is the minimum bar — it costs five lines of code and eliminates an entire class of URI-based attacks.
  • The fix in weex/src/index.we demonstrates correct placement — validate at the call site, not somewhere upstream where the check can be bypassed.
  • A user-visible error toast is the right UX response — silently dropping the navigation would confuse legitimate users; the modal.toast({ 'message': 'Invalid URL' }) call communicates the rejection clearly.

How Orbis AppSec Detected This

  • Source: User-controlled text entered into the <input> element, read via this.$el('input').attr.value inside the onclick() handler in weex/src/index.we:24.
  • Sink: navigator.push({ url: path, ... }) at line 26 of the same file, where the unvalidated path value is used as the navigation target.
  • Missing control: No protocol check, no domain validation, and no sanitization of any kind between reading the input value and passing it to the navigation API.
  • CWE: CWE-601 — URL Redirection to Untrusted Site ('Open Redirect').
  • Fix: A regex guard (/^https?:\/\/.+/) was inserted before the navigator.push() call; inputs that fail the check trigger an error toast and an early return, preventing navigation entirely.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

A single missing validation check in weex/src/index.we was enough to turn a navigation feature into an open redirect vector. The onclick() handler trusted this.$el('input').attr.value completely, handing whatever the user typed straight to navigator.push() — no questions asked. The five-line fix that enforces ^https?:// is a textbook example of how a small, targeted change can eliminate a high-severity vulnerability without touching any other behaviour.

The broader lesson is that navigation APIs are security sinks. Any time your code takes a URL from user input and acts on it — whether through navigator.push(), window.location, <a href>, or a server-side redirect — that URL must be validated against a known-safe pattern before use. The cost of adding that check is trivial. The cost of skipping it can be the trust of every user in your application.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #9

Related Articles

critical

fast-xml-parser 4.5.0 XSS: DOCTYPE Entity Injection in XML.parse()

fast-xml-parser versions 4.5.0 and earlier fail to sanitize DOCTYPE entity declarations during XML parsing, allowing attackers to inject JavaScript through crafted XML payloads. The vulnerability permits reflected XSS in applications that process untrusted XML input. Upgrading to version 5.7.0 eliminates the unsafe entity expansion path.

high

sanitizeBangumiHtml() XSS Fix: insertAdjacentHTML RCE via JSON

A high-severity cross-site scripting vulnerability existed where untrusted JSON data from bangumis.json was rendered directly into the DOM using insertAdjacentHTML without sanitization. An attacker who could modify this external data source could execute arbitrary JavaScript in visitors' browsers. The fix introduces a dedicated sanitizeBangumiHtml() function that strips script tags, event handlers, and javascript: URLs before insertion.

high

form.js jQuery Selector Construction: CWE-79 Quote-Breaking Fix

A defense-in-depth fix in the form.js initialization routine eliminates a jQuery selector injection vector where user-controlled category data was concatenated directly into a string literal. The vulnerable pattern at lines 47-48 of the form handler could allow malicious content to break out of the selector's quoted context and execute arbitrary jQuery methods.

high

ItemPicker `_commitTraitInput` XSS via Unescaped Trait Chip Rendering

The `_commitTraitInput` function in ItemPicker accepted arbitrary user input for trait values without sanitization, then rendered those values directly into HTML chip elements through `_updateList`. An attacker could inject malicious JavaScript payloads that executed when trait chips were displayed. The fix applies a strict whitelist filter removing all non-alphanumeric characters except spaces and hyphens.

high

formatObject() Leaves localStorage Keys Unescaped Before innerHTML

The `formatObject()` pretty-printer used to build the key-configuration storage viewer interpolated object keys and array key/value pairs directly into an HTML string that is later assigned with `innerHTML`. Only string *values* were passed through `escapeHtml()`, so a crafted key name persisted in `localStorage` could execute script every time a user opened the storage viewer. The fix wraps the index-pair branch and the object-key branch in `escapeHtml()`, keeping the raw path only for non-colo

high

Voice Assistant Widget XSS: Unsanitized Bot Messages Execute in

The voice assistant widget's `appendMessage` function had a critical cross-site scripting (XSS) vulnerability where bot messages were inserted directly into the DOM without sanitization, while user messages were escaped. An attacker controlling bot responses could inject and execute arbitrary JavaScript in the user's browser context. The fix applies HTML escaping to all message types uniformly.