diff --git a/docs/runbooks/securing-mcp-setup.md b/docs/runbooks/securing-mcp-setup.md new file mode 100644 index 0000000..171cf9c --- /dev/null +++ b/docs/runbooks/securing-mcp-setup.md @@ -0,0 +1,133 @@ +# Runbook: Securing an MCP / Connector Setup + +*A worked audit of my own AI-assistant tool configuration, applying the +[MCP trust-boundary threat model](../threat-models/mcp-trust-boundaries.md) to a +live setup. The threat model is the theory; this is the same five boundaries as +a checklist that was actually run. Findings are real — including one that was +remediated during the audit itself, and the ones that required no action, which +is itself a finding.* + +## 1. The setup under audit + +Host: **Claude Desktop**, using platform-managed connectors. At audit time: + +| Connector | State at audit start | Surface it grants | +|---|---|---| +| Google Drive | Connected | Read access to personal documents | +| Claude in Chrome | Connected | Agentic browsing — the model acts in a live browser session | +| Gmail | Connected → **disconnected during audit** | Read access to mail | +| Google Calendar | Connected → **disconnected during audit** | Read access to schedule | +| GitHub | Available, not connected | — | + +Architecture note that shapes everything below: these are **OAuth-brokered, +platform-managed connectors**, not locally-run community MCP servers. The +classic worst finding of these audits — a personal access token sitting in +plaintext in a local JSON config — is structurally absent here, because no +local config holds credentials at all: authorization happens via OAuth consent, +and tokens are held platform-side. That is the vendor doing B3 (credential +custody) correctly on my behalf. The audit therefore concentrates where the +risk actually lives in this pattern: **scopes, surfaces, and the model +boundary.** + +## 2. The audit — five boundaries, applied + +### B1 · User ↔ Host — does the assistant act on more than I mean? +**Checked:** which connectors can take *actions* versus only *read*. +**Found:** Drive is a read surface; Chrome is an action surface (the model can +navigate, click, and submit in a real browser). +**Action:** treat the two differently — see B5 and the standing rules. Reading +my documents and acting in my browser are not the same trust decision, and the +audit's main outcome is refusing to blur them. + +### B2 · Host ↔ Server — what third-party code am I trusting? +**Checked:** provenance of every connected integration. +**Found:** all connected surfaces are first-party (platform-built), not +community packages. The supply-chain exposure (LLM03) that dominates the threat +model's B2 — typosquatted servers, poisoned updates, unreviewed npm installs — +is **not present** because nothing third-party is installed. But the inventory +itself produced the audit's main remediation: **Gmail and Calendar were +connected with no active workflow consuming them** — surface granted, utility +zero. +**Action:** both disconnected during the audit; they return the day a workflow +needs them, not before. GitHub stays dark for the same reason. Least privilege +as "not connected" rather than "connected but carefully" — and where that +wasn't the standing state, the audit made it so. + +### B3 · Server ↔ Downstream — where do credentials live, and how wide? +**Checked:** local filesystem for credential material; the Google account's +third-party access page for what the Drive consent actually granted. +**Found:** no tokens on disk (OAuth pattern, above). The Drive grant is +account-wide read — broader than the handful of folders I actually use with +the assistant. +**Action:** accepted, documented: Drive's consent model doesn't offer +folder-scoping on this integration, so the real choice is account-wide or +nothing. The compensating control is behavioral and reviewable — periodic +review of the grant (calendar reminder, quarterly, alongside the standing +review of all third-party app access), and disconnection if usage stops. +**This is the audit's honest gap:** the scope is wider than the need, the +narrowing control doesn't exist, and saying so beats pretending the OAuth +badge means least privilege. + +### B4 · Server ↔ Server — can one surface's output steer another? +**Checked:** whether both connected surfaces coexist in the same conversations. +**Found:** they can — a session may both read Drive content and drive the +browser. Content from one becomes context that influences actions in the +other. +**Action:** covered by the B5 rule below; the cross-contamination case here +collapses into the tool-output rule because there are only two surfaces and +one of them is the action surface. + +### B5 · Model ↔ Tool output — untrusted content re-entering the loop +**Checked:** which surface ingests content I don't control. +**Found:** the browser is the wide-open channel — every page visited is +third-party content entering the model's context, and the browser is +simultaneously the surface that *acts*. This is the threat model's +no-classic-analogue boundary, live in my own setup: a hostile page could +attempt instruction injection against the same session that can click. +**Action (the audit's main hardening):** standing rules of use rather than +configuration — because no configuration option closes B5: +1. **Sensitive-session isolation** — agentic browsing does not run in sessions + where Drive content or credentials-adjacent workflows are present. +2. **Irreversible actions get a human gate** — anything that submits, + purchases, sends, or deletes is confirmed by me, not auto-approved. +3. **Untrusted-site skepticism** — the browser surface is for known + destinations; letting the agent roam arbitrary search results is treated + as feeding untrusted input directly to the action loop. + +## 3. Findings summary + +| # | Finding | Severity | Disposition | +|---|---|---|---| +| 1 | No plaintext credentials anywhere on disk | — | OAuth pattern; the classic finding is structurally absent | +| 2 | No third-party/community servers installed | — | Supply-chain surface minimal; standing rule governs additions | +| 3 | Drive grant broader than actual use (account-wide read) | Low | Accepted + quarterly review; no narrower option exists on this integration | +| 4 | Browser surface = untrusted input feeding an action-capable loop (B5) | Medium | Behavioral rules 1–3; no configuration control exists for this boundary | +| 5 | **Gmail and Calendar connected but unused** — OAuth grants against the account's highest-sensitivity data (mail, schedule) with no workflow consuming them | Low–Medium | **Remediated — disconnected during the audit.** The orphaned-integration discipline applied at home: unused grants are attack surface with zero utility; the before/after is two fewer standing grants | + +## 4. Standing rules (the part that outlives the audit) + +- **Connect on demand, disconnect on disuse.** A connector nobody used this + quarter gets disconnected at the quarterly review. +- **New server = vendor onboarding.** Any future third-party MCP server gets + the B2 treatment before install: provenance, publisher, what scopes it asks + for, where its credentials would live. The threat model's mitigations table + is the checklist. +- **The action surface is special.** Anything that can *do* (browser today; + any write-capable connector tomorrow) inherits the three B5 rules + automatically. +- **Quarterly third-party access review** across the Google account — the + same review that covers every OAuth grant now explicitly includes AI + connectors. + +## 5. Scope and limitations + +- This audits **one person's setup at one point in time** — a workstation + pattern, not an enterprise MCP deployment. Fleet-scale controls (server + allowlisting via policy, centralized tool-call audit logs, per-agent + identity) are the threat model's mitigations table, not this runbook. +- The OAuth-connector pattern outsources B3 to the platform. That is a real + security benefit and a real dependency — this setup trusts the platform's + token custody, and says so. +- B5 has **no configuration fix** in any current host. The rules above are + behavioral controls, honestly labeled as such — which is the state of the + art, not a deficiency of this setup.