[BUG] MCP isLoopbackBind() defaults to false when HOSTNAME is unset or bracketed, disabling Host validation and DNS rebinding protection
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 82/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- security
Research direction
Start in src/lib/mcp/origin-policy.ts and compare its isLoopbackBind() with the implementation in bin/lib/launcher-utils.mjs. Check the existing tests for MCP origin policy and add cases for unset HOSTNAME, bracketed [::1], and other 127.0.0.0/8 addresses. Done when loopback requests run Host validation and untrusted Host headers are rejected.
Written by the indexing model from the issue text.
Description
Description
In src/lib/mcp/origin-policy.ts, isLoopbackBind() checks whether the server is bound to a loopback interface by matching process.env.HOSTNAME against MCP_LOOPBACK_BINDS = new Set(["127.0.0.1", "::1", "localhost"]).
However, on default local installations (e.g., running npm start, bun run dev, or standalone node server.js without explicitly exporting HOSTNAME=127.0.0.1), process.env.HOSTNAME is undefined. It defaults to binding to 127.0.0.1, but isLoopbackBind() evaluates (process.env.HOSTNAME ?? "").trim().toLowerCase() to "", which returns false.
Consequently, in mcpOriginHostRefusal(request), line 59 (if (!isLoopbackBind()) return null;) exits early without running hostHeaderValidationResponse(request, mcpHostAllowlist()). The Host header validation is completely bypassed on default loopback instances, defeating the DNS rebinding protection designed for local MCP instances.
Furthermore, if an operator configures IPv6 loopback as bracketed HOSTNAME="[::1]" (documented in docs/DISTRIBUTION.md) or binds to another loopback address in 127.0.0.0/8 (such as 127.0.0.2 or 127.0.1.1), MCP_LOOPBACK_BINDS exact match fails and also returns false.
Steps to Reproduce
- Start LibreDB Studio locally without setting
HOSTNAMEin the environment (npm startorbun run dev). The server binds to127.0.0.1:3000by default. - Send an MCP request to
http://127.0.0.1:3000/api/mcpwith an external Host header (simulating a DNS rebinding attack):curl -i -X POST http://127.0.0.1:3000/api/mcp \ -H "Host: attacker-controlled-rebind.com:3000" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' - Inspect the response.
- Notice that the request is not rejected by the Host header check (
421 Misdirected Requestor403with reasonmcp_host_not_allowed), but passes directly to authentication becauseisLoopbackBind()returnedfalse.
Expected Behavior
When running on loopback (including the default unset HOSTNAME, bracketed "[::1]", and any 127.0.0.0/8 address):
isLoopbackBind()should returntrue.mcpOriginHostRefusalshould executehostHeaderValidationResponse(request, mcpHostAllowlist()).- Requests with untrusted or rebound
Hostheaders should be rejected withmcp_host_not_allowed.
Actual Behavior
isLoopbackBind() returns false when HOSTNAME is unset or bracketed "[::1]". Host header validation is skipped, leaving local deployments unprotected against DNS rebinding.
Environment
- LibreDB Studio Version: 1.2.0
- Browser: All / Headless MCP
- OS: macOS / Linux / Windows
- Node.js/Bun Version: Bun 1.4.3 / Node.js 24
- Database Type: All (MCP Server Layer)
Additional Context
bin/lib/launcher-utils.mjs correctly handles this in resolveBindAddress and isLoopbackBind:
export function resolveBindAddress({ host, libredbBind, hostnameEnv, systemHostname } = {}) {
if (host) return host;
const chosen = envText(libredbBind);
if (chosen !== "") return chosen;
const inherited = envText(hostnameEnv);
if (inherited !== "" && inherited !== envText(systemHostname)) return inherited;
return "127.0.0.1"; // defaults to 127.0.0.1
}
export function isLoopbackBind(hostname) {
const host = envText(hostname)
.toLowerCase()
.replace(/^\[(.*)\]$/, "$1");
return host === "localhost" || host === "::1" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
}
src/lib/mcp/origin-policy.ts should follow the same pattern instead of an exact match against three literal strings with an empty fallback.
Possible Solution
In src/lib/mcp/origin-policy.ts, update isLoopbackBind():
export function isLoopbackBind(): boolean {
const host = (process.env.HOSTNAME ?? "127.0.0.1")
.trim()
.toLowerCase()
.replace(/^\[(.*)\]$/, "$1");
return host === "" || host === "localhost" || host === "::1" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
}
- Dominant language
- TypeScript
- Stars
- 810
- Forks
- 158
- Avg merge
- 10h 44m
- Merged PRs (30d)
- 511
Getting set up
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from libredb/libredb-studio
-
[BUG] Column profiling fails with SQL syntax errors on columns named after reserved keywords due to unforced identifier quoting in /api/db/profilePossibly taken @saad-works claimed this 3 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
libredb/libredb-studio#2245 · 2 comments · 1 assignee ·
Maintainers usually reply within 1 day
-
[BUG] inspect_schema matches named container case-insensitively without preferring exact matchPossibly taken @Maaz2212 claimed this 7 days ago. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
libredb/libredb-studio#2128 · 1 comment · 1 assignee ·
Maintainers usually reply within 1 day
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 58/100
libredb/libredb-studio#2271 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 4/5 3-5 days Newbie friendliness 58/100
libredb/libredb-studio#2270 ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 42/100
libredb/libredb-studio#2267 · 3 comments ·
Maintainers usually reply within 1 day
All issues in libredb/libredb-studio
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
earendil-works/pi-claude-code#36 ·
Maintainers usually reply within 6 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
Resources: New work of jabodetabekPossibly taken @rmt-svc claimed this today. Openresources
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
railmapgen/rmp-gallery#4167 ·
Maintainers usually reply within 2 days
-
documentation p1
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
microsoft/monaco-editor#5577 ·
Maintainers usually reply within 1 day