GuardVibe
News
· 6 min read

Localhost Isn't Private: DBHub, Cline and Rsdoctor Advisories

DBHub, Cline and Rsdoctor ran local servers any website could reach: SQL execution, agent takeover, source leaks. Plus AWS language server and Elysia fixes.

Three developer tools published this week turned out to run local servers that other websites could reach. A malicious page could run SQL through DBHub, take over a Cline dashboard session, or read a project's source from Rsdoctor. Amazon's language servers and the Elysia framework also shipped high-severity fixes.

What shipped

DBHub: DNS rebinding reaches the database, and read-only mode wasn't read-only

Package: @bytebase/dbhub — DNS rebinding <= 0.22.4 (fixed 0.22.5), read-only bypass < 0.22.6 (fixed 0.22.6) Severity: critical (GHSA-fm8p-53ww-hf6w, CVE-2026-61742) and high (GHSA-mwwr-p57h-56pf, CVE-2026-61788)

DBHub is a database MCP server. Started with --transport http, it serves an unauthenticated MCP endpoint. Its only browser guard checked that the Origin hostname matched the Host hostname, and DNS rebinding gets past that check. The result: a web page the developer visits can call DBHub's tools and run SQL against whatever database it's connected to. Separately, readonly = true on execute_sql never reached the database connectors. That left only a check on each statement's first keyword, and a SELECT that calls a function with side effects gets past it. With a privileged role, that means reading and writing files on the host and running commands. The default stdio transport isn't affected by the browser attack.

npm install @bytebase/dbhub@latest   # 0.22.6 or later fixes both

Cline: any website could drive the Cline Hub dashboard

Package: cline < 3.0.30, fixed in 3.0.30 Severity: high, CVSS 8.8 (GHSA-3cj3-hqcr-g934, CVE-2026-59723)

cline dashboard starts a server on 127.0.0.1:8787 whose /browser WebSocket never checked the Origin header. With ROOM_SECRET unset, which is the default on a local bind, every connection was accepted. Any site open in the developer's browser could read session state, change MCP and provider settings, and trigger command execution, because dashboard sessions auto-approve tools by default. The researchers confirmed it by injecting a malicious stdio MCP server into Cline's settings.

npm install -g cline@latest   # 3.0.30 or later

Rsdoctor: build analyzer serves your source code to the network

Package: @rsdoctor/rspack-plugin <= 1.5.15, fixed in 1.5.16 Severity: high, CVSS 7.5 (GHSA-jmg2-rcxh-w8q3, CVE-2026-61782)

The report server is on by default outside CI. It bound to 0.0.0.0, sent Access-Control-Allow-Origin: *, and served POST /api/data/key with no authentication. Anyone on the same network, and any web page via the wildcard CORS header, could download the source of every compiled module plus the serialized build config.

npm install -D @rsdoctor/rspack-plugin@latest

Language Servers for AWS (Amazon Q Developer): workspace trust boundary

Package: @aws/lsp-codewhisperer — code execution < 0.0.113, file write < 0.0.117 (AWS lists them as Language Servers for AWS 1.65.0 and 1.69.0) Severity: high (GHSA-xhcr-j4j9-3gh7, CVE-2026-12957; GHSA-6v3r-4p5c-mrp5, CVE-2026-12958)

These language servers power Amazon Q Developer's agentic chat. After a user trusts a malicious workspace, commands in its project config files can run automatically. A symlink inside the workspace can also let the agent write files outside it without asking. AWS lists no workaround. Update the Amazon Q Developer plugin so it picks up the patched language server, and be careful about trusting repositories you just cloned.

Elysia: multipart bodies cost quadratic CPU

Package: elysia < 1.4.29, fixed in 1.4.29 Severity: high, CVSS 7.5 (GHSA-9643-4qgh-g8mx, CVE-2026-56669)

Elysia's form-data normalizer calls getAll() once per unique key, and each call scans every field. Doubling the field count quadruples the work. One unauthenticated multipart/form-data request with many fields can tie up the server. The advisory offers no workaround other than upgrading.

npm install elysia@latest

DNS rebinding, explained

"It only listens on localhost" feels like a security boundary. It isn't one, because the developer's browser is also on localhost, and any tab can send requests there.

The same-origin policy stops a page on evil.example from reading responses from http://127.0.0.1:8080. DNS rebinding gets around it. The attacker serves a page from rebind.evil.example with a very short DNS TTL. Once the page has loaded, the attacker changes the DNS record to point at 127.0.0.1. The page's scripts keep making requests to rebind.evil.example. The browser treats them as same-origin, so it sends them and lets the page read the responses, but they now land on the developer's local server. The Host header in each request still says rebind.evil.example.

That is why DBHub's check failed: after rebinding, Origin and Host both contain the attacker's hostname, so they match. WebSockets are an even easier target. Browsers don't apply CORS to the WebSocket handshake at all, so a server that ignores Origin (as Cline Hub did) is open to any page, with no rebinding needed.

This keeps showing up in AI-generated code because the shortest working answer to "add an HTTP transport" or "serve a local dashboard" is the insecure one. That answer calls app.listen(port) with no host, which binds every interface. It adds cors() with no options, which allows every origin. And it skips auth "because it's local". MCP servers, agent dashboards and build analyzers all get scaffolded this way.

Vulnerable:

import express from "express";
import cors from "cors";

const app = express();
app.use(cors());                 // Access-Control-Allow-Origin: *
app.post("/mcp", handleMcp);     // no auth: "it's local"
app.listen(8080);                // binds 0.0.0.0, not just loopback

Fixed:

const PORT = 8080;
const ALLOWED_HOSTS = new Set([`127.0.0.1:${PORT}`, `localhost:${PORT}`]);

app.use((req, res, next) => {
  // Rebinding sends the attacker's hostname in Host, so reject it
  if (!ALLOWED_HOSTS.has(req.headers.host ?? "")) return res.status(403).end();
  // A per-session token that a web page cannot know
  if (req.headers.authorization !== `Bearer ${process.env.LOCAL_TOKEN}`) {
    return res.status(401).end();
  }
  next();
});
app.post("/mcp", handleMcp);
app.listen(PORT, "127.0.0.1");

For a WebSocket server, check Origin against an explicit allowlist during the upgrade, and reject connections that carry no token.

The review check: for every server a tool starts, ask what a random web page could do with it. Look at three lines. Which interface does listen bind? Is the Host header checked against an allowlist? Is there a token a browser tab can't guess? If any of the three answers is "nothing", the server is reachable from the internet through the developer's browser.

Check your own repo

npm ls @bytebase/dbhub @rsdoctor/rspack-plugin elysia   # in your project tree?
npm ls -g cline @bytebase/dbhub                         # CLIs are often installed globally
lsof -iTCP -sTCP:LISTEN -n -P | grep '\*:'              # what is listening on every interface right now
npx guardvibe@3.36.0 audit .   # 3.36.0 flags the affected dbhub and elysia versions (VG1124, VG1126)

Sources

Get the next one in your feed reader

Follow GuardVibe in your feed reader. No account, no email.