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
Published August 26, 2026Reviewed 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

How stored XSS happens in TinyMCE plugins and how to fix it

The snippets plugin's `Main.ts` inserted raw, unsanitized snippet content directly into the TinyMCE editor via `editor.insertContent(snippet.content)`, allowing stored JavaScript payloads saved by any snippet editor to execute in every user's browser. The fix routes snippet content through TinyMCE's own parser and serializer before insertion, stripping dangerous markup while preserving legitimate formatting.

critical

How CSS Injection via Weak Pattern Validation happens in Vue.js and how to fix it

A critical CSS injection vulnerability in `testpage/App.vue` allowed attackers to bypass weak HTML5 pattern validation and load malicious stylesheets. The fix replaces direct variable assignment with a hardened `setCustomStylesheetHref()` method using strict regex validation.

high

How SQL-injection-style template literal injection happens in JavaScript DOM rendering and how to fix it

A Semgrep rule (`utils.custom.sql-injection-template-literal`) flagged `src/export/SheetMusicView.js` for building a query/markup string out of a JavaScript template literal with untrusted values interpolated directly into it. In this case the sink was an `<option value="${s.id}">${s.name}</option>` string used to build the snippet picker, meaning any snippet name containing `"` or `<` could break out of the attribute and inject arbitrary HTML. The fix introduces an `_escapeHtml()` helper and ro

critical

How Unvalidated External Data Fetch happens in React and how to fix it

The Datasets.jsx component fetched a remote manifest from snapshots.qdrant.io and rendered its contents directly into React state without validating response status, JSON shape, or field types. A compromised or spoofed endpoint could have injected malicious payloads straight into the UI; the fix adds strict validation and type coercion before the data ever reaches the render tree.

medium

How Cross-Site Scripting Happens in JavaScript Parsers and How to Fix It

A cross-site scripting vulnerability in JSXGraph's JessieCode parser allowed attackers to inject JavaScript through maliciously crafted input that appeared in error messages. The fix ensures proper output encoding when user-controlled data is included in parser error reporting.

critical

How Cross-Site Scripting (XSS) happens in JavaScript sanitization functions and how to fix it

A critical XSS vulnerability was discovered in the `sanitizeInput()` function in script.js, where only angle brackets were being escaped while quotes, ampersands, and backticks remained unprotected. This incomplete sanitization allowed attackers to craft payloads using event handlers and template literals that bypassed the security controls entirely. The fix implements comprehensive HTML entity encoding for all XSS-relevant characters.