React Native's Open Door Policy: The Anatomy of CVE-2025-11953
Feb 4, 2026·6 min read·4 visits
Executive Summary (TL;DR)
A critical RCE in @react-native-community/cli (Metro Server) allows attackers on the same network to execute arbitrary commands on a developer's machine via the `/open-url` endpoint. The server fails to sanitize input before passing it to the OS shell.
In the world of modern development, 'Developer Experience' (DX) is king. Tools are designed to be frictionless, magical, and zero-config. But sometimes, that magic involves binding a server to 0.0.0.0 and letting unauthenticated network traffic execute shell commands on your workstation. CVE-2025-11953 is a critical Remote Code Execution (RCE) vulnerability in the React Native CLI's Metro server that turns a developer's convenience into a hacker's playground.
The Hook: Convenience at a Cost
Let's set the scene. You're a React Native developer. You're sitting in a coffee shop, sipping an overpriced oat latte, iterating on your new app. Your phone is connected to your laptop, and the Metro Bundler is running, serving JavaScript bundles to your device. To make this seamless, the Metro server (part of @react-native-community/cli) typically binds to 0.0.0.0 (all interfaces) so your phone can reach it over the local network. It listens on port 8081.
Here's the problem: Metro isn't just serving static files. It includes 'middleware'—helper endpoints designed to improve your workflow. Specifically, it has endpoints like /open-url and /open-stack-frame. The idea is brilliant: if your app crashes on your phone, you tap a button, the phone sends a request to your laptop, and your laptop opens the offending file in VS Code or opens a documentation URL in Chrome. It's magical.
But for a security researcher, 'magic' usually translates to 'unvalidated input'. That helper endpoint is effectively a remote control for your operating system. And because the server binds to 0.0.0.0, that remote control isn't just in your hand—it's available to anyone on the same Wi-Fi network. That guy in the corner with the hoodie? He's not debugging his startup; he's scanning for port 8081.
The Flaw: Trusting User Input
The vulnerability (CWE-78) lies in how the Metro server handles requests to these helper endpoints. The logic was deceptively simple: receive a JSON payload containing a URL, and tell the operating system to 'open' that URL. On the surface, this looks like open('http://google.com').
However, under the hood, the library used to perform this action interacts with the underlying OS shell. On Windows specifically, this often translates to spawning a process via cmd.exe. The developers made a classic blunder: they took the input directly from the HTTP request body—data completely controlled by the user—and passed it into a command execution sink without strict sanitization.
It's the digital equivalent of a bank teller blindly accepting a note that says 'Give me all the money' because it was written on official-looking stationary. The code didn't check if the 'URL' was actually a URL. It just checked if it was a string, and then handed it to the shell. This is where the syntax of cmd.exe becomes a weapon. By appending command separators like & or |, an attacker can turn a request to open a website into a request to open a website and run ransomware.
The Code: The Smoking Gun
Let's look at the code. This isn't just a theoretical bug; it's a stark example of why input validation is non-negotiable. Before the patch, the openURLMiddleware looked something like this (simplified for dramatic effect):
// VULNERABLE CODE
const openURLMiddleware = async (req, res, next) => {
const { url } = JSON.parse(req.body);
// "Sure, I'll open that for you!"
await open(url);
res.end('OK');
};The open() function eventually calls child_process.spawn or exec. If url contains shell metacharacters, they get executed. Now, let's look at the fix in commit 15089907d1f1301b22c72d7f68846a2ef20df547. The maintainers realized they couldn't just trust the string. They needed to validate that it was, in fact, a valid URL protocol.
// FIXED CODE (Commit 15089907d1f1)
async function openURLMiddleware(req, res, next) {
const { url } = req.body;
try {
// 1. Force parsing. If it's not a URL, this throws.
const parsedUrl = new URL(url);
// 2. Allowlist protocols. No 'file:', no 'javascript:', and definitely no shell hacks.
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
res.writeHead(400); return res.end('Invalid protocol');
}
} catch (e) {
res.writeHead(400); return res.end('Invalid URL');
}
await open(url);
res.end();
}By enforcing new URL(url), they rely on the WHATWG URL standard to parse the string. If you try to inject http://site.com & calc.exe, the URL parser will either throw an error (invalid character) or URL-encode the malicious parts, rendering them harmless to the shell. It's a robust fix because it validates the structure of the data, not just looking for 'bad characters'.
The Exploit: Popping Calc (and Shells)
So, how do we break it? Imagine you are on a shared network. You run a quick nmap scan and see port 8081 open on a nearby IP. It identifies as the Metro Bundler. It's showtime.
The attack vector is a simple HTTP POST request. You don't need authentication. You don't need cookies. You just need to send JSON.
The Payload:
curl -X POST http://192.168.1.50:8081/open-url \
-H "Content-Type: application/json" \
-d '{"url": "http://google.com & calc.exe"}'When the server processes this on a Windows machine, the constructed command effectively becomes:
cmd /c start http://google.com & calc.exe
The browser opens Google, and moments later, the Calculator app pops up. While calc.exe is the harmless 'Proof of Concept' standard, replace that with a PowerShell one-liner to download a Cobalt Strike beacon, and you own the developer's laptop. The /open-stack-frame endpoint is equally vulnerable, accepting file and lineNumber parameters that are just as susceptible to injection.
The Impact: Why This Hurts
Why is this CVSS 9.8? Because it hits developers where they live. A developer's workstation is the crown jewel of corporate targets. It has SSH keys to production servers. It has AWS credentials in ~/.aws/credentials. It has the source code for the company's proprietary IP. It has VPN access to the internal network.
By compromising one developer via a drive-by Wi-Fi attack or a CSRF attack (if the developer visits a malicious site that fires a request to localhost:8081), an attacker gains a foothold inside the perimeter. From there, lateral movement is trivial. The fact that this requires zero user interaction and zero privileges makes it terrifying. The developer doesn't even see a prompt; the command just executes in the background.
The Fix: Shutting It Down
The immediate fix is to upgrade @react-native-community/cli to version 20.0.0 or later. The React Native team was responsive and the patch is solid. However, relying solely on patching is reactive. We need to talk about defense in depth.
1. Bind to Localhost:
There is rarely a good reason for Metro to listen on 0.0.0.0 in a coffee shop. Configure your metro.config.js to bind to 127.0.0.1 unless you explicitly need to debug on a physical device that can't connect via USB.
// metro.config.js
module.exports = {
server: {
host: '127.0.0.1'
}
};2. Firewall Rules: Developers often disable local firewalls 'to make things work'. Don't. Ensure your OS firewall blocks incoming connections to non-standard ports (like 8081) on public networks. Treat your local dev server like a production server: if it doesn't need to be public, don't make it public.
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
@react-native-community/cli React Native Community | >= 4.8.0 < 20.0.0 | 20.0.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-78 (OS Command Injection) |
| CVSS v3.1 | 9.8 (Critical) |
| Attack Vector | Network (AV:N) |
| EPSS Score | 0.405% (Rising) |
| Vulnerable Component | Metro Server Middleware (openURL) |
| Exploit Status | Proof of Concept Available |
MITRE ATT&CK Mapping
The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.