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.


Prevention & Best Practices

1. Validate URLs at the sink, not just at the source

Even if you add server-side validation or sanitize inputs earlier in the flow, always validate again immediately before passing a URL to a navigation API. Defense in depth means multiple independent checks.

2. Consider a domain allowlist for stricter control

The regex fix blocks all non-HTTP(S) schemes, which is the minimum viable protection. For higher-assurance applications, extend the check to validate against a list of known-good domains:

const ALLOWED_DOMAINS = ['app.example.com', 'cdn.example.com'];

function isSafeUrl(url) {
  if (!/^https?:\/\/.+/.test(url)) return false;
  try {
    const parsed = new URL(url);
    return ALLOWED_DOMAINS.includes(parsed.hostname);
  } catch {
    return false;
  }
}

3. Never trust attr.value from user-controlled elements

In Weex (and in web development generally), this.$el('input').attr.value is fully attacker-controlled. Treat it the same way you would treat an HTTP query parameter — untrusted until validated.

4. Apply Content Security Policy (CSP)

In WebView-based apps, a strict CSP can limit the damage from javascript: URI injection even if a URL sneaks through:

Content-Security-Policy: default-src 'self'; navigate-to https://app.example.com

5. Use static analysis to catch taint flows early

Tools such as Semgrep can be configured to trace data from $el(...).attr.value (source) to navigator.push() (sink) and flag the missing validation automatically. See the Semgrep open-redirect rules for ready-made patterns.

Relevant standards

  • OWASP Input Validation Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
  • OWASP Unvalidated Redirects and Forwards: https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/11-Client-side_Testing/04-Testing_for_Client-side_URL_Redirect
  • CWE-601: https://cwe.mitre.org/data/definitions/601.html

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.


References

Frequently Asked Questions

What is an open redirect vulnerability?

An open redirect occurs when an application accepts a user-controlled URL and redirects the browser to it without validating that the destination is trusted or safe, enabling phishing and credential-harvesting attacks.

How do you prevent open redirect vulnerabilities in Weex?

Validate every URL before passing it to `navigator.push()`. At minimum, enforce an `https?://` protocol check with a regex; ideally also validate against an allowlist of trusted domains.

What CWE is open redirect?

Open redirect is classified as CWE-601: URL Redirection to Untrusted Site ('Open Redirect').

Is escaping user input enough to prevent open redirect?

No. Escaping prevents injection into markup but does not stop a fully-formed malicious URL (e.g., `https://evil.com` or `javascript:alert(1)`) from being used as a redirect target. Allowlist validation is required.

Can static analysis detect open redirect vulnerabilities?

Yes. Static analysis tools like Semgrep and CodeQL can trace tainted data from input fields to navigation sinks such as `navigator.push()` and flag the missing validation, exactly as Orbis AppSec did here.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #9

Related Articles

critical

How Cross-Site Scripting happens in fast-xml-parser and how to fix it

CVE-2026-25896 is a critical Cross-Site Scripting vulnerability in fast-xml-parser caused by improper handling of DOCTYPE entity declarations, allowing attackers to inject malicious scripts through crafted XML input. The fix upgrades the library from vulnerable versions (4.5.3 and 5.2.3) to patched releases (4.5.7 and 5.10.1), closing the attack vector in production code. This matters because fast-xml-parser is widely used to process user-supplied XML in Node.js applications, making any XSS flaw

critical

How Reflected XSS happens in Astro and how to fix it

CVE-2026-50146 is a reflected cross-site scripting (XSS) vulnerability in Astro versions prior to 6.3.3, where unescaped slot names could be injected into rendered HTML. The fix upgrades Astro from 5.18.1 to 6.3.3 (along with related packages `@astrojs/starlight` and `starlight-blog`), closing a code path that allowed attacker-controlled input to reach the browser without sanitization. Any Astro-based site that renders dynamic slot names from untrusted sources was potentially exposed to session

high

How Unsafe eval() in JavaScript Happens in React Components and How to Fix It

A high-severity code injection vulnerability was discovered in `TurnPlanner.tsx`, where the `parseInputExpr` function used JavaScript's `Function` constructor — effectively `eval()` — to evaluate user-provided mathematical expressions. The regex guard in place only checked for the presence of arithmetic operators, not whether the input was safe to execute, leaving the door open for arbitrary JavaScript injection. A targeted whitelist fix was applied to reject any input containing characters outs

critical

How Unsanitized IPC Data Injection happens in Electron/HTML and how to fix it

A content injection vulnerability in `src/NankaiTrough.html` allowed attacker-controlled IPC message data to flow directly into DOM properties without type coercion or validation. The fix explicitly converts all `request.data` fields to strings using `String()` with fallback defaults before assigning them to `document.title` and `innerText` properties, eliminating the risk of prototype pollution and unexpected object-to-string coercion attacks.

critical

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

A stored Cross-Site Scripting (XSS) vulnerability in `hasheous/wwwroot/pages/dataobjectdetail.js` allowed attackers with Moderator or Admin privileges to inject malicious HTML into DataObject attribute fields, executing arbitrary JavaScript in every visitor's browser. The fix replaces unsafe `innerHTML` assignments with `textContent` for plain text and a sanitized markdown renderer for AI-generated descriptions, eliminating the injection vector entirely.

high

How Stored XSS via Unsanitized GitHub README HTML Happens in JavaScript and How to Fix It

A high-severity stored Cross-Site Scripting (XSS) vulnerability was discovered in `custom_components/hacs_vision/frontend/panel.js`, where the backend fetched GitHub's pre-rendered README HTML and the frontend injected it directly into the DOM without sanitization. An attacker who controls a GitHub repository could embed malicious JavaScript in their README that executes automatically when any HACS Vision user views that repository's details, potentially exfiltrating credentials or hijacking the