Bind MCP server to loopback#208
Conversation
|
Hi @fa1k3 — thank you for this PR. The security instinct here is exactly right, and digging into it surfaced that the gap is broader than the bind axis alone (cleartext over LAN, self-signed-on-LAN trust model, and the agent-visible side of the problem all interact). Rather than land the hardcoded The design preserves the spirit of your patch:
What it adds over the hardcoded approach:
Closing this in favor of #211. Credit and inspiration noted in the ADR's "Alternatives Considered" section and in the commit messages. Genuinely appreciate the contribution — please keep them coming. — Aaron |
Models the combined (protocol × bind × cert) state as a 9-cell table with three verdicts (🟢 OK / 🟡 WARN / 🔴 JAIL). Server always starts; 🔴 surfaces a Notice + log + MCP `initialize` instructions warning so the LLM client also sees it. New settings: `bindMode` discriminated enum + `customBindHost`. Migration flips the implicit `0.0.0.0` to a real `127.0.0.1` default, making ADR-103's "loopback by default" premise actually true. Inspired by community PR aaronsb#208 (fa1k3) — preserves the security instinct while keeping the LAN/remote escape hatch the hardcoded approach would have removed.
Replaces the implicit 0.0.0.0 Node-default bind with a deliberate,
classified network exposure model.
Server: MCPHttpServer.start() now resolves the listen host from
settings (bindMode + customBindHost), passes it to listen(), and
classifies the combined (protocol × bind × certSource) state. On 🔴
fires a 15s Notice + Debug.error; on 🟡 logs Debug.warn; on 🟢 silent.
The verdict-derived agent-visible warning is pushed into MCPServerPool
which conditionally injects it into the MCP initialize.instructions
field for every session created after bind, so an LLM client sees the
warning on its first turn against the server. node-mcp-server.ts (the
unused fallback) is hardcoded to loopback for consistency.
Settings: MCPPluginSettings gains bindMode ('loopback' | 'all' |
'custom') default 'loopback', customBindHost default '', and a
one-shot hasShownBindMigrationNotice flag. Existing installs with a
data.json that predates bindMode see a one-time 20s Notice on next
load pointing them at the new setting; fresh installs are silent.
UI: a new "Network binding" section between server config and the
HTTPS section, with a live colored verdict badge that re-classifies
on every change, the bind dropdown (Loopback / All interfaces /
Custom), an inline red caution under "All interfaces" whose wording
adapts to whether HTTPS is enabled, and a custom-host text field
that normalizes loopback/wildcard aliases on blur (typing 127.0.0.1
collapses the dropdown to Loopback; 0.0.0.0 collapses to All).
Tests: 7 integration assertions verifying the source wires listen()
to the resolved host, pushes verdict-derived instructions into the
pool, conditionally injects on 🔴 only, and that the pool spread-
pattern omits the field on ok/warn.
Inspired by PR aaronsb#208 (fa1k3) — preserves the security instinct while
keeping the LAN/remote escape hatch the hardcoded approach removed.
Summary
127.0.0.1instead of Node's default wildcard interfaceWhy
This keeps the local Obsidian MCP HTTP endpoint scoped to the local machine by default.
Tests
npm test -- --runInBand tests/listen-loopback.test.tsnpm run lint(passes with existing warnings only)npm run buildnpm test -- --runInBand --forceExit