x64dbgMCP
§ 00 · Source-level Observatory

Bridge Security source deep dive

一个原生 x64dbg 插件,把调试器能力通过 MCP Streamable HTTP 与 SSE 暴露给 AI 客户端。真正值得拆的是:它不是脚本壳,而是把 Win32 socket、JSON-RPC、工具 schema、x64dbg bridge 和菜单配置全部编进同一个 Zig 插件。

★ 956
GitHub Stars
5.2K
LOC
12
Files
2026-08-24
Updated
x64dbg plugin→Win32 HTTP server
MCP client→JSON-RPC tools/list + tools/call
tool handler→bridge wrapper
debugger state→event log + SSE
§ 00 · OVERVIEW

Bridge 是能力边界,不只是 FFI

bridge 层决定 AI 客户端最后能触到什么。src/core/bridge.zig:267(证据 src/evidence.md:28) 声明运行时函数指针,src/core/bridge.zig:338(证据 src/evidence.md:29) 解析 x64dbg/bridge DLL,src/core/bridge.zig:419(证据 src/evidence.md:30) 暴露表达式求值,src/core/bridge.zig:433(证据 src/evidence.md:31) 与 src/core/bridge.zig:437(证据 src/evidence.md:32) 直接读写调试目标内存。

▸边界意识

server 的 token 校验在 src/core/mcp_server.zig:374(证据 src/evidence.md:22) 和 src/core/mcp_server.zig:388(证据 src/evidence.md:23),配置 token 在 src/core/config.zig:250(证据 src/evidence.md:39) 生成并保存,UI 在 src/core/config.zig:406(证据 src/evidence.md:58) 可复制/生成 token。它有认证,但不是完整安全沙箱。

§ 01 · BRIDGE

运行时符号解析

▸动态解析

src/core/bridge.zig · L267-320Zig
267var bridge_module: ?HMODULE = null;
268var dbg_module: ?HMODULE = null;
269
270// Bridge functions
271pub var DbgCmdExec: *const fn ([*:0]const u8) callconv(.c) BOOL = undefined;
272pub var DbgCmdExecDirect: *const fn ([*:0]const u8) callconv(.c) BOOL = undefined;
273pub var DbgIsDebugging: *const fn () callconv(.c) BOOL = undefined;
274pub var DbgIsRunning: *const fn () callconv(.c) BOOL = undefined;
275pub var DbgIsRunLocked: *const fn () callconv(.c) BOOL = undefined;
276pub var DbgValFromString: *const fn ([*:0]const u8) callconv(.c) duint = undefined;
277pub var DbgMemRead: *const fn (va: duint, dest: [*]u8, size: duint) callconv(.c) BOOL = undefined;
278pub var DbgMemWrite: *const fn (va: duint, src: [*]const u8, size: duint) callconv(.c) BOOL = undefined;
279pub var DbgGetLabelAt: *const fn (addr: duint, segment: c_int, text: [*]u8) callconv(.c) BOOL = undefined;
280pub var DbgGetCommentAt: *const fn (addr: duint, text: [*]u8) callconv(.c) BOOL = undefined;
281pub var DbgGetModuleAt: *const fn (addr: duint, text: [*]u8) callconv(.c) BOOL = undefined;
282pub var DbgModBaseFromName: *const fn ([*:0]const u8) callconv(.c) duint = undefined;
283pub var DbgMemFindBaseAddr: *const fn (addr: duint, size: *duint) callconv(.c) duint = undefined;
284pub var DbgSetCommentAt: *const fn (addr: duint, text: [*:0]const u8) callconv(.c) BOOL = undefined;
285pub var DbgSetLabelAt: *const fn (addr: duint, text: [*:0]const u8) callconv(.c) BOOL = undefined;
286
287// Plugin functions
288pub var _plugin_registercallback: *const fn (pluginHandle: c_int, cbType: CBTYPE, cbPlugin: CBPLUGIN) callconv(.c) void = undefined;
289pub var _plugin_unregistercallback: *const fn (pluginHandle: c_int, cbType: CBTYPE) callconv(.c) BOOL = undefined;
290pub var _plugin_registercommand: *const fn (pluginHandle: c_int, command: [*:0]const u8, cbCommand: CBPLUGINCOMMAND, debugonly: BOOL) callconv(.c) BOOL = undefined;
291pub var _plugin_unregistercommand: *const fn (pluginHandle: c_int, command: [*:0]const u8) callconv(.c) BOOL = undefined;
292pub var _plugin_menuadd: *const fn (hMenu: c_int, title: [*:0]const u8) callconv(.c) c_int = undefined;
293pub var _plugin_menuaddentry: *const fn (hMenu: c_int, hEntry: c_int, title: [*:0]const u8) callconv(.c) BOOL = undefined;
294pub var _plugin_menuaddseparator: *const fn (hMenu: c_int) callconv(.c) BOOL = undefined;
295const MenuSetIconFn = *const fn (hMenu: c_int, icon: *const ICONDATA) callconv(.c) void;
296pub var _plugin_menuseticon: ?MenuSetIconFn = null;
297pub var _plugin_logputs: *const fn ([*:0]const u8) callconv(.c) void = undefined;
298pub var _plugin_menuclear: *const fn (hMenu: c_int) callconv(.c) BOOL = undefined;
299const EntrySetIconFn = *const fn (pluginHandle: c_int, hEntry: c_int, icon: *const ICONDATA) callconv(.c) void;
300pub var _plugin_menuentryseticon: ?EntrySetIconFn = null;
301
302// Extended bridge functions (optional)
303const DbgDisasmFastAtFn = *const fn (addr: duint, info: *BASIC_INSTRUCTION_INFO) callconv(.c) BOOL;
304pub var DbgDisasmFastAt: ?DbgDisasmFastAtFn = null;
305const DbgGetBpListFn = *const fn (bptype: c_int, list: *BPMAP) callconv(.c) BOOL;
306pub var DbgGetBpList: ?DbgGetBpListFn = null;
307const DbgMemMapFn = *const fn (memmap: *MEMMAP) callconv(.c) BOOL;
308pub var DbgMemMap: ?DbgMemMapFn = null;
309const DbgGetThreadListFn = *const fn (list: *THREADLIST) callconv(.c) void;
310pub var DbgGetThreadList: ?DbgGetThreadListFn = null;
311const BridgeFreeFn = *const fn (ptr: *anyopaque) callconv(.c) void;
312pub var BridgeFree: ?BridgeFreeFn = null;
313
314// Extended bridge functions (optional) — continued
315const DbgFunctionGetFn = *const fn (addr: duint, start: *duint, end: *duint) callconv(.c) BOOL;
316pub var DbgFunctionGet: ?DbgFunctionGetFn = null;
317
318// GUI functions (from x64bridge.dll)
319const GuiGetDisassemblyFn = *const fn (addr: duint, text: [*]u8) callconv(.c) BOOL;
320pub var GuiGetDisassembly: ?GuiGetDisassemblyFn = null;
src/core/bridge.zig · L338-396Zig
338pub fn init() !void {
339    const bridge_dll: [*:0]const u8 = if (@sizeOf(usize) == 8)
340        "x64bridge.dll"
341    else
342        "x32bridge.dll";
343
344    const plugin_dll: [*:0]const u8 = if (@sizeOf(usize) == 8)
345        "x64dbg.dll"
346    else
347        "x32dbg.dll";
348
349    bridge_module = GetModuleHandleA(bridge_dll) orelse
350        return error.BridgeNotFound;
351
352    dbg_module = GetModuleHandleA(plugin_dll) orelse
353        return error.BridgeNotFound;
354
355    const m = bridge_module.?;
356    const d = dbg_module.?;
357
358    // Dbg functions (from x64bridge.dll / x32bridge.dll)
359    DbgCmdExec = resolve(@TypeOf(DbgCmdExec), m, "DbgCmdExec");
360    DbgCmdExecDirect = resolve(@TypeOf(DbgCmdExecDirect), m, "DbgCmdExecDirect");
361    DbgIsDebugging = resolve(@TypeOf(DbgIsDebugging), m, "DbgIsDebugging");
362    DbgIsRunning = resolve(@TypeOf(DbgIsRunning), m, "DbgIsRunning");
363    DbgIsRunLocked = resolve(@TypeOf(DbgIsRunLocked), m, "DbgIsRunLocked");
364    DbgValFromString = resolve(@TypeOf(DbgValFromString), m, "DbgValFromString");
365    DbgMemRead = resolve(@TypeOf(DbgMemRead), m, "DbgMemRead");
366    DbgMemWrite = resolve(@TypeOf(DbgMemWrite), m, "DbgMemWrite");
367    DbgGetLabelAt = resolve(@TypeOf(DbgGetLabelAt), m, "DbgGetLabelAt");
368    DbgGetCommentAt = resolve(@TypeOf(DbgGetCommentAt), m, "DbgGetCommentAt");
369    DbgGetModuleAt = resolve(@TypeOf(DbgGetModuleAt), m, "DbgGetModuleAt");
370    DbgModBaseFromName = resolve(@TypeOf(DbgModBaseFromName), m, "DbgModBaseFromName");
371    DbgMemFindBaseAddr = resolve(@TypeOf(DbgMemFindBaseAddr), m, "DbgMemFindBaseAddr");
372    DbgSetCommentAt = resolve(@TypeOf(DbgSetCommentAt), m, "DbgSetCommentAt");
373    DbgSetLabelAt = resolve(@TypeOf(DbgSetLabelAt), m, "DbgSetLabelAt");
374
375    // Extended bridge functions (optional)
376    DbgDisasmFastAt = resolveOptional(DbgDisasmFastAtFn, m, "DbgDisasmFastAt");
377    DbgGetBpList = resolveOptional(DbgGetBpListFn, m, "DbgGetBpList");
378    DbgMemMap = resolveOptional(DbgMemMapFn, m, "DbgMemMap");
379    DbgGetThreadList = resolveOptional(DbgGetThreadListFn, m, "DbgGetThreadList");
380    BridgeFree = resolveOptional(BridgeFreeFn, m, "BridgeFree");
381    GuiGetDisassembly = resolveOptional(GuiGetDisassemblyFn, m, "GuiGetDisassembly");
382    DbgFunctionGet = resolveOptional(DbgFunctionGetFn, m, "DbgFunctionGet");
383
384    // Plugin functions (from x64dbg.dll / x32dbg.dll)
385    _plugin_registercallback = resolve(@TypeOf(_plugin_registercallback), d, "_plugin_registercallback");
386    _plugin_unregistercallback = resolve(@TypeOf(_plugin_unregistercallback), d, "_plugin_unregistercallback");
387    _plugin_registercommand = resolve(@TypeOf(_plugin_registercommand), d, "_plugin_registercommand");
388    _plugin_unregistercommand = resolve(@TypeOf(_plugin_unregistercommand), d, "_plugin_unregistercommand");
389    _plugin_menuadd = resolve(@TypeOf(_plugin_menuadd), d, "_plugin_menuadd");
390    _plugin_menuaddentry = resolve(@TypeOf(_plugin_menuaddentry), d, "_plugin_menuaddentry");
391    _plugin_menuaddseparator = resolve(@TypeOf(_plugin_menuaddseparator), d, "_plugin_menuaddseparator");
392    _plugin_menuseticon = resolveOptional(MenuSetIconFn, d, "_plugin_menuseticon");
393    _plugin_menuclear = resolve(@TypeOf(_plugin_menuclear), d, "_plugin_menuclear");
394    _plugin_menuentryseticon = resolveOptional(EntrySetIconFn, d, "_plugin_menuentryseticon");
395    _plugin_logputs = resolve(@TypeOf(_plugin_logputs), d, "_plugin_logputs");
396}

src/core/bridge.zig:338(证据 src/evidence.md:29) 依指针宽度选择 x64dbg 或 x32dbg 相关 DLL,src/core/bridge.zig:267(证据 src/evidence.md:28) 后的函数指针把必须能力和 optional 能力分开。好处是单代码库可建双架构,坏处是缺失符号只能运行期发现。

§ 02 · CONFIG

令牌和配置

▸AuthToken 生命周期

src/core/config.zig · L152-258Zig
152pub const Config = struct {
153    ip: [64]u8 = undefined,
154    ip_len: usize = 0,
155    port: u16 = 0,
156    auto_start: bool = true,
157    auth_token: [64]u8 = undefined,
158    auth_token_len: usize = 0,
159
160    pub fn ipSlice(self: *const Config) []const u8 {
161        return self.ip[0..self.ip_len];
162    }
163
164    pub fn tokenSlice(self: *const Config) []const u8 {
165        return self.auth_token[0..self.auth_token_len];
166    }
167
168    pub fn hasAuth(self: *const Config) bool {
169        return self.auth_token_len > 0;
170    }
171};
172
173var config_path: [512]u8 = undefined;
174var config_path_len: usize = 0;
175
176fn getConfigPath() [*:0]const u8 {
177    if (config_path_len > 0) return @ptrCast(&config_path);
178
179    var module_path: [512]u8 = undefined;
180    const len = GetModuleFileNameA(null, &module_path, 512);
181    if (len == 0) {
182        const fallback = "mcp_config.json";
183        @memcpy(config_path[0..fallback.len], fallback);
184        config_path[fallback.len] = 0;
185        config_path_len = fallback.len;
186        return @ptrCast(&config_path);
187    }
188
189    // Find last backslash
190    var last_sep: usize = 0;
191    for (0..len) |i| {
192        if (module_path[i] == '\\') last_sep = i;
193    }
194
195    const filename = "mcp_config.json";
196    @memcpy(config_path[0 .. last_sep + 1], module_path[0 .. last_sep + 1]);
197    @memcpy(config_path[last_sep + 1 .. last_sep + 1 + filename.len], filename);
198    config_path[last_sep + 1 + filename.len] = 0;
199    config_path_len = last_sep + 1 + filename.len;
200    return @ptrCast(&config_path);
201}
202
203pub fn load() Config {
204    const default_port: u16 = if (@sizeOf(usize) == 8) 9094 else 9095;
205    var cfg = Config{ .port = default_port };
206    const default_ip = "0.0.0.0";
207    @memcpy(cfg.ip[0..default_ip.len], default_ip);
208    cfg.ip_len = default_ip.len;
209
210    const path = getConfigPath();
211    const h = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, null, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, null);
212    if (h == INVALID_HANDLE or h == null) return ensureToken(&cfg);
213    defer _ = CloseHandle(h);
214
215    var buf: [512]u8 = undefined;
216    var bytes_read: u32 = 0;
217    if (ReadFile(h, &buf, 512, &bytes_read, null) == 0) return ensureToken(&cfg);
218
219    const json_data = buf[0..bytes_read];
220
221    // Parse "IpAddress":"<value>"
222    if (findJsonString(json_data, "IpAddress")) |ip_val| {
223        if (ip_val.len > 0 and ip_val.len < 64) {
224            @memcpy(cfg.ip[0..ip_val.len], ip_val);
225            cfg.ip_len = ip_val.len;
226        }
227    }
228
229    // Parse "Port":<number>
230    if (findJsonNumber(json_data, "Port")) |port_val| {
231        cfg.port = port_val;
232    }
233
234    // Parse "AutoStart":true/false
235    if (findJsonBool(json_data, "AutoStart")) |val| {
236        cfg.auto_start = val;
237    }
238
239    // Parse "AuthToken":"<value>"
240    if (findJsonString(json_data, "AuthToken")) |token_val| {
241        if (token_val.len > 0 and token_val.len <= 64) {
242            @memcpy(cfg.auth_token[0..token_val.len], token_val);
243            cfg.auth_token_len = token_val.len;
244        }
245    }
246
247    return ensureToken(&cfg);
248}
249
250fn ensureToken(cfg: *Config) Config {
251    if (cfg.auth_token_len == 0) {
252        var tok: [32]u8 = undefined;
253        generateToken(&tok);
254        @memcpy(cfg.auth_token[0..32], tok[0..32]);
255        cfg.auth_token_len = 32;
256        save(cfg);
257    }
258    return cfg.*;

src/core/config.zig:152(证据 src/evidence.md:65) 的 Config 同时存 IP、端口、auto_start 和 token;src/core/config.zig:203(证据 src/evidence.md:38) 设默认值,src/core/config.zig:250(证据 src/evidence.md:39) 首次缺 token 就生成并保存,src/core/config.zig:286(证据 src/evidence.md:40) 写回 JSON。这个 token 是基本门锁,不是细粒度权限系统。

§ 03 · UI

配置 UI 也是治理面

▸用户可见边界

src/core/config.zig · L406-474Zig
406pub fn showDialog(parentHwnd: usize) void {
407    parent_hwnd = @ptrFromInt(parentHwnd);
408    _ = CreateThread(null, 0, dialogThread, null, 0, null);
409}
410
411fn dialogThread(_: ?*anyopaque) callconv(.winapi) u32 {
412    const hInst = GetModuleHandleA(null);
413
414    if (!class_registered) {
415        const wc = WNDCLASSEXA{
416            .lpfnWndProc = wndProc,
417            .hInstance = hInst,
418            .hbrBackground = @ptrFromInt(@as(usize, @intCast(1 + @as(c_int, 15)))),
419            .lpszClassName = CLASS_NAME,
420            .hCursor = null,
421        };
422        _ = RegisterClassExA(&wc);
423        class_registered = true;
424    }
425
426    ui_font = CreateFontA(-14, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 5, 0, "Segoe UI\x00");
427    ui_font_bold = CreateFontA(-14, 0, 0, 0, 700, 0, 0, 0, 0, 0, 0, 5, 0, "Segoe UI\x00");
428    ui_font_small = CreateFontA(-12, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 5, 0, "Segoe UI\x00");
429
430    dlg_hwnd = CreateWindowExA(
431        WS_EX_DLGMODALFRAME,
432        CLASS_NAME,
433        "MCP Server Configuration\x00",
434        WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU,
435        100,
436        100,
437        DLG_W,
438        DLG_H,
439        parent_hwnd,
440        null,
441        hInst,
442        null,
443    );
444
445    if (dlg_hwnd == null) return 1;
446
447    // Disable parent
448    if (parent_hwnd != null) _ = EnableWindow(parent_hwnd, 0);
449
450    // Center on screen
451    const sw = GetSystemMetrics(SM_CXSCREEN);
452    const sh = GetSystemMetrics(SM_CYSCREEN);
453    _ = SetWindowPos(dlg_hwnd, null, @divTrunc(sw - DLG_W, 2), @divTrunc(sh - DLG_H, 2), 0, 0, SWP_NOZORDER | SWP_NOSIZE);
454
455    _ = ShowWindow(dlg_hwnd, 5);
456    _ = UpdateWindow(dlg_hwnd);
457
458    var msg: MSG = undefined;
459    while (GetMessageA(&msg, null, 0, 0) > 0) {
460        if (IsDialogMessageA(dlg_hwnd, &msg) == 0) {
461            _ = TranslateMessage(&msg);
462            _ = DispatchMessageA(&msg);
463        }
464    }
465
466    if (ui_font != null) { _ = DeleteObject(ui_font); ui_font = null; }
467    if (ui_font_bold != null) { _ = DeleteObject(ui_font_bold); ui_font_bold = null; }
468    if (ui_font_small != null) { _ = DeleteObject(ui_font_small); ui_font_small = null; }
469
470    return 0;
471}
472
473fn createCtrl(class: [*:0]const u8, text: ?[*:0]const u8, style: u32, x: c_int, y: c_int, w: c_int, h: c_int, id: c_int) HWND {
474    const hInst = GetModuleHandleA(null);

src/core/config.zig:406(证据 src/evidence.md:58) 附近生成 token 字段、Generate、Copy 按钮,src/core/config.zig:550(证据 src/evidence.md:41) 保存时校验 IP/port 并重启服务。UI 文案提醒 0.0.0.0 与 127.0.0.1,但默认仍需用户主动改。

§ 04 · EVENTS

事件回流

▸调试事件进入日志

src/main.zig · L344-430Zig
344    pushEvent(msg[0 .. msg.len - 1]); // strip null terminator for event log
345}
346
347fn cbInitDebug(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
348    logAndPush("[x64dbg-MCP Server] Debug session started.\x00");
349}
350
351fn cbStopDebug(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
352    logAndPush("[x64dbg-MCP Server] Debug session stopped.\x00");
353}
354
355fn cbCreateProcess(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
356    logAndPush("[x64dbg-MCP Server] Process created.\x00");
357}
358
359fn cbExitProcess(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
360    logAndPush("[x64dbg-MCP Server] Process exited.\x00");
361}
362
363fn cbCreateThread(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
364    logAndPush("[x64dbg-MCP Server] Thread created.\x00");
365}
366
367fn cbExitThread(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
368    logAndPush("[x64dbg-MCP Server] Thread exited.\x00");
369}
370
371fn cbSystemBreakpoint(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
372    logAndPush("[x64dbg-MCP Server] System breakpoint.\x00");
373}
374
375fn cbLoadDll(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
376    logAndPush("[x64dbg-MCP Server] DLL loaded.\x00");
377}
378
379fn cbUnloadDll(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
380    logAndPush("[x64dbg-MCP Server] DLL unloaded.\x00");
381}
382
383fn cbOutputDebugString(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
384    logAndPush("[x64dbg-MCP Server] OutputDebugString received.\x00");
385}
386
387fn cbException(_: bridge.CBTYPE, info: *anyopaque) callconv(.c) void {
388    const cb: *bridge.PLUG_CB_EXCEPTION = @ptrCast(@alignCast(info));
389    const rec = &cb.Exception.ExceptionRecord;
390    const first_chance = cb.Exception.dwFirstChance;
391
392    var buf: [256]u8 = undefined;
393    const prefix = "Exception 0x";
394    @memcpy(buf[0..prefix.len], prefix);
395    var pos: usize = prefix.len;
396    pos += fmtHex(rec.ExceptionCode, buf[pos..]);
397    const at = " at 0x";
398    @memcpy(buf[pos .. pos + at.len], at);
399    pos += at.len;
400    pos += fmtHexAddr(rec.ExceptionAddress, buf[pos..]);
401    if (first_chance != 0) {
402        const fc = " (first chance)";
403        @memcpy(buf[pos .. pos + fc.len], fc);
404        pos += fc.len;
405    } else {
406        const sc = " (second chance)";
407        @memcpy(buf[pos .. pos + sc.len], sc);
408        pos += sc.len;
409    }
410    pushEvent(buf[0..pos]);
411
412    // Also log to x64dbg console with prefix
413    var log_buf: [256]u8 = undefined;
414    const log_prefix = "[x64dbg-MCP Server] ";
415    @memcpy(log_buf[0..log_prefix.len], log_prefix);
416    @memcpy(log_buf[log_prefix.len .. log_prefix.len + pos], buf[0..pos]);
417    log_buf[log_prefix.len + pos] = 0;
418    bridge.logPuts(@ptrCast(&log_buf));
419}
420
421fn cbBreakpoint(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
422    logAndPush("[x64dbg-MCP Server] Breakpoint hit.\x00");
423}
424
425fn cbPauseDebug(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
426    logAndPush("[x64dbg-MCP Server] Debugger paused.\x00");
427}
428
429fn cbResumeDebug(_: bridge.CBTYPE, _: *anyopaque) callconv(.c) void {
430    logAndPush("[x64dbg-MCP Server] Debugger resumed.\x00");

src/main.zig:344(证据 src/evidence.md:42)、src/main.zig:387(证据 src/evidence.md:43)、src/main.zig:421(证据 src/evidence.md:44) 说明插件把生命周期、异常和断点事件推入 event log。结合 src/core/mcp_server.zig:499(证据 src/evidence.md:45) 的 SSE,这能给客户端反馈,但它还不是不可抵赖审计。

§ 05 · RISKS

安全建议

▸部署建议

基于源码,最务实的安全配置是:把 src/core/mcp_server.zig:37(证据 src/evidence.md:52) 的默认开放监听改为 127.0.0.1;保留 src/core/mcp_server.zig:374(证据 src/evidence.md:22) 的 bearer 检查;对 src/mcp/tools.zig:943(证据 src/evidence.md:53) ExecuteDebuggerCommand、src/mcp/tools.zig:1321(证据 src/evidence.md:35) WriteMemToAddress 和 src/mcp/tools.zig:2506(证据 src/evidence.md:54) DumpMemory 做 per-tool allowlist。