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:
- Protocol allowlist — the URL must begin with
http://orhttps://. This immediately blocksjavascript:,data:,file:///,vbscript:, and every other non-HTTP scheme that could be abused. - Non-empty path — the
.+after the protocol separator ensures there is at least one character following://, preventing bare-protocol strings likehttps://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.valuein Weex is fully attacker-controlled — never pass it tonavigator.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 unguardednavigator.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.wedemonstrates 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 viathis.$el('input').attr.valueinside theonclick()handler inweex/src/index.we:24. - Sink:
navigator.push({ url: path, ... })at line 26 of the same file, where the unvalidatedpathvalue 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 thenavigator.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
- CWE-601: URL Redirection to Untrusted Site ('Open Redirect')
- CWE-20: Improper Input Validation
- OWASP Input Validation Cheat Sheet
- OWASP Testing for Client-side URL Redirect
- Semgrep open-redirect rules
- Weex Navigator Module Documentation
- fix: the weex component reads a url directly from a ... in index.we