1. 恶意 MCP 服务器劫持 Cursor 内置浏览器到底怎么发生的
MCP(Model Context Protocol)是让 AI 编程助手调用外部工具、读取外部数据的一套通信协议,Cursor、VS Code、Windsurf 这类 IDE 都原生支持。它让 Agent 能查数据库、读文档、跑脚本,效率提升非常明显。但问题也出在这里:MCP 服务器本质上是一个能向 IDE 注入内容、返回指令、甚至触发本地操作的进程。当你在 Cursor 里启用一个来源不明的 MCP 服务器,它返回的内容会被内置浏览器渲染,而内置浏览器又跑在 Electron 里,拥有 Node.js 的文件系统权限。
安全研究已经证实:单个恶意 MCP 服务器可以向 Cursor 内置浏览器注入 JavaScript,把登录页替换成攻击者控制的钓鱼页面,URL 却保持不变;更严重的情况下,还能借助 IDE 权限执行系统级操作。Cursor 作为 VS Code 分支,缺少 VS Code 那样的文件完整性校验,代码被改动不会弹警告,这让攻击更隐蔽。
这篇文章面向正在用 MCP 的 IDE 开发者,交付一套在 TaoToken 统一 Key/API 通道下的可复制配置骨架(settings.json 与 config.toml),并给出劫持检测与验证动作。目标很明确:不牺牲 MCP 功能的前提下,阻断恶意服务器对内置浏览器的控制。适合谁?任何在 Cursor 或 VS Code 里挂了 MCP 服务器、又不想哪天被钓鱼页骗走凭证的人。
2. 用 TaoToken 统一通道收敛 MCP 的信任边界
我试过把每个 MCP 服务器都单独配一套 Key,结果就是密钥散落在十几个配置文件里,哪个服务器被投毒根本查不过来。后来改成用 TaoToken 做统一入口,所有模型调用和 MCP 相关请求都走同一个 API 通道,信任边界一下子清晰了。
TaoToken 在这里的作用不是替代 IDE,而是把「谁在调用模型、调用哪个模型、用哪把 Key」集中管理。官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api (这个不加 UTM)。你可以在控制台里为不同项目、不同 MCP 服务器分配独立的 Key,一旦某个 Key 出现异常调用,直接吊销即可,不用动整个 IDE 配置。
具体操作路径:
- 模型对话入口:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
- Coding Plan(长期编码/Agent 场景):https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
- 控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
- API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
- Claude Code / Anthropic 兼容:https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude_code&utm_campaign=rewrite
注意:MCP 服务器本身不直接连生产数据库,也不要把高权限 Key 交给来源不明的服务器。统一通道的价值在于「可审计、可吊销」,不是「免审查」。
3. 可复制的 settings.json 与 config.toml 配置骨架
下面这套配置的核心思路是:MCP 服务器只允许访问 TaoToken 的 API 端点,不允许它自行发起任意网络请求;同时把内置浏览器的自动执行关掉。
3.1 Cursor / VS Code 的 settings.json
{ "mcp.servers": { "taotoken-gateway": { "command": "npx", "args": ["-y", "@taotoken/mcp-gateway"], "env": { "TAOTOKEN_API_BASE": "https://taotoken.net/api", "TAOTOKEN_API_KEY": "${env:TAOTOKEN_API_KEY}", "MCP_ALLOWED_HOSTS": "taotoken.net", "MCP_DISABLE_BROWSER_INJECT": "true" } } }, "cursor.browser.autoRun": false, "cursor.browser.allowScriptInjection": false, "cursor.browser.sandbox": true, "security.workspace.trust.enabled": true, "extensions.autoUpdate": false }关键参数说明:
| 参数 | 作用 | 建议值 |
|---|---|---|
| MCP_ALLOWED_HOSTS | 限制 MCP 服务器可访问的域名 | 只填 taotoken.net |
| MCP_DISABLE_BROWSER_INJECT | 禁止 MCP 向内置浏览器注入脚本 | true |
| cursor.browser.autoRun | 关闭浏览器自动执行 | false |
| cursor.browser.sandbox | 开启浏览器沙箱 | true |
| security.workspace.trust.enabled | 工作区信任 | true |
3.2 MCP 网关的 config.toml
[gateway] api_base = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" timeout_seconds = 30 max_retries = 2 [security] allowed_hosts = ["taotoken.net"] block_browser_injection = true require_workspace_trust = true audit_log = "./logs/mcp-audit.log" [security.browser] sandbox = true allow_remote_content = false allow_inline_script = false [servers] # 只挂载经过审查的服务器,禁止通配符 enabled = ["taotoken-gateway"]提示:
block_browser_injection = true是这套配置里最关键的一行。它让 MCP 返回的内容无法直接变成内置浏览器里的可执行脚本,钓鱼页替换就失去了注入路径。
4. 验证请求与劫持检测动作
配好之后不能只看「没报错」就完事,得实际验证。下面几个动作可以确认恶意注入是否被阻断。
4.1 用 curl 验证 TaoToken 通道连通
curl -s -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-3-5-sonnet", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 16 }'返回里能看到正常的choices字段,说明统一通道是通的。如果返回 401,先去 API Keys 页面确认 Key 状态。
4.2 检测内置浏览器是否被注入
在 Cursor 里打开内置浏览器,按Ctrl+Shift+I(macOS 是Cmd+Option+I)打开 DevTools,执行:
// 检查是否有非预期的全局脚本注入 const suspicious = []; for (const key of Object.keys(window)) { if (/inject|hook|override|steal/i.test(key)) { suspicious.push(key); } } console.log("可疑全局变量:", suspicious); // 检查 DOM 是否被替换 const forms = document.querySelectorAll("form"); forms.forEach((f) => { console.log("表单 action:", f.action, "来源:", f.ownerDocument.location.href); });如果suspicious数组非空,或者表单action指向了非你预期的域名,说明注入可能已经发生。正常情况下,开启block_browser_injection后这里应该是空的。
4.3 审计日志检查
tail -f ./logs/mcp-audit.log | grep -E "blocked|inject|denied"被拦截的注入尝试会记录在这里。实测下来,恶意服务器第一次尝试注入时就会被allowed_hosts拦掉,日志里能看到host not in allowlist的记录。
5. 本篇常见错排查
问题一:配了 MCP_ALLOWED_HOSTS 但 MCP 服务器还是能联网。检查是不是用了npx -y直接拉最新版,某些包会忽略环境变量。改成锁定版本号,比如@taotoken/mcp-gateway@1.2.0,并在 config.toml 里显式写allowed_hosts。
问题二:内置浏览器 DevTools 打不开。cursor.browser.sandbox = true在某些版本会限制 DevTools。临时排查时可以设为 false,确认问题后再开回来。生产环境建议保持 true。
问题三:curl 返回 403。多半是 Key 权限范围不对。去控制台确认这把 Key 是否绑定了正确的项目,以及是否开启了对应模型的访问权限。
问题四:MCP 服务器启动报command not found。npx路径问题。在 settings.json 里把command改成绝对路径,比如/usr/local/bin/npx,Windows 下用npx.cmd。
问题五:日志文件不生成。audit_log路径是相对工作区的,确认工作区目录有写权限。或者改成绝对路径。
注意:排查时不要为了图快把
block_browser_injection关掉去「测试功能」,那等于把门打开再检查锁好不好用。
6. 长期编码场景下的接入与验证入口
如果你是把 MCP 用在长期编码、Agent 自动化这类场景,建议直接走 Coding Plan,把 Key 管理和调用配额集中起来:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
需要新建或吊销 Key 时,去 API Keys 页面操作:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
配置细节和参数含义以接入文档为准:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
想先验证模型返回是否正常,可以用模型对话快速试一条:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
最后提醒一句:MCP 的便利和风险是同一枚硬币。统一通道能帮你收敛信任边界、快速吊销异常 Key,但「只挂审查过的服务器、关掉自动执行、人工核验 Agent 生成的代码」这三件事,任何工具都替代不了。