11 Commits

Author SHA1 Message Date
你的姓名
ddc3570562 test: add tests for image-gen plugin, options API, and config modal
- MCP server tests (21 cases): initialize, tools/list, list_providers
  with capabilities, generate_image validation, edit_image capability
  check, SSRF protection (IPv4/IPv6/protocol), model capabilities
  matching, unknown tool, ping
- Server API tests: GET/POST /api/plugins/options with schema filtering,
  sensitive value masking, missing field validation
- Desktop component tests: PluginConfigModal render, fetch, save with
  masked sensitive field skip, error handling, cancel flow
- Fix MCP server async message handling: track pending promises and
  wait for completion before process exit

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 15:12:40 +08:00
你的姓名
4fbf567874 fix(plugins): address PR review findings for image-gen plugin
HIGH fixes:
- Complete SSRF protection: add IPv6 private (fc00::, fe80::),
  IPv4-mapped IPv6 (::ffff:10.x), and 0.0.0.0 to blocklist
- Validate provider baseUrl against private IP ranges at load time
- Extract shared validateUrlSafety() for reuse

MEDIUM fixes:
- Skip unchanged masked sensitive fields on save (prevent '********'
  from overwriting real API keys)
- Filter POST /api/plugins/options to only schema-declared keys
- Whitelist img src to http/https/data protocols only
- Improve model capabilities matching: exact → prefix → contains
- Cache providers at startup instead of per-tool-call

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 14:47:11 +08:00
你的姓名
d290e15177 fix(plugins): fix IPv6 SSRF bypass in image-gen plugin
Strip brackets from hostname returned by new URL() for IPv6 literals
like [::1]. Previously the check compared against '::1' (unbracketed)
but new URL() returns '[::1]' (bracketed), allowing the SSRF block
to be bypassed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 14:06:26 +08:00
你的姓名
da1137e468 feat(plugins): add model capabilities database to image-gen
Agent can now query provider capabilities via list_providers to know:
- Supported image sizes per model
- Whether the model supports image editing
- Whether transparent background is supported
- Max number of images per request (n)
- Response format (url vs b64_json)

Built-in database covers: Agnes image, GPT-image-2, DALL-E 2/3,
Gemini image, Flux, Stable Diffusion. Unknown models get pattern-based
defaults or "unknown" with full compatibility fallback.

generate_image now validates size against capabilities and warns on
mismatch. edit_image checks if any provider supports editing before
attempting.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 13:16:05 +08:00
你的姓名
4c128e0764 fix(plugins): address code review findings for image-gen plugin
HIGH fixes:
- SSRF protection: block private/link-local IPs and file:// URLs
  in edit_image image_url parameter
- Mask sensitive values in GET /api/plugins/options response

MEDIUM fixes:
- Clamp n parameter to 1-10 to prevent API bill exhaustion
- Validate prompt is non-empty string before API call
- Return empty array when no providers configured (clear error)
- Add 1MB buffer limit for JSON-RPC line accumulation
- Fix PluginConfigModal useEffect re-fetch loop on parent re-render

LOW fixes:
- Remove unused requestId variable

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 13:00:44 +08:00
你的姓名
58dd7ac816 feat(plugins): add image-gen MCP plugin with multi-provider fallback
Add image generation plugin that supports OpenAI-compatible APIs (Agnes,
GPT-image-2, Gemini image, nano-banana, DALL-E, Flux, etc.) with
automatic provider fallback and parameter compatibility retry.

New plugin:
- plugins/image-gen/ — MCP server (zero-dependency ESM, stdio JSON-RPC)
- 4 tools: generate_image, edit_image, list_providers, list_models
- Up to 3 configurable provider slots with fill-in-the-blank UI
- Each provider: full params → minimal params retry (spriteflow pattern)

Desktop UI enhancements:
- PluginConfigModal for visual userConfig configuration
- Configure button in PluginDetail when plugin has userConfig
- GET/POST /api/plugins/options API endpoints
- Sensitive fields use password input + secure storage

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 12:56:34 +08:00
小橙子
c8e96aabc3
feat(reverse-engineering): APK hardening skill + shell-driven workflow refactor 2026-06-13 05:11:14 +08:00
小橙子
b5e62e3251
fix(reverse-engineering): only ship 3 end-to-end verified MCP servers (live-smoked) + smoke script (#32)
* fix(reverse-engineering): declare missing tool-binary prereqs for 4 servers

Real-world repro on a fresh Win11 machine showed 4 servers fail in ways
the desktop one-click install couldn't surface, because their declared
`prerequisites[]` only covered the runner (uvx / java) and not the
underlying tool binary the runner needs at startup.

Smoke from a clean machine (uvx just installed, no other tools):

  Before this PR
  --------------
  ghidra     ⚠️  spawned, no JSON-RPC response in 5s   (uv pkg slow first start)
  radare2     prereq missing: radare2                  ← already correct
  gdb         prereq missing: gdb                      ← already correct
  lldb        process exited (code=1)                  ← cause invisible
  jadx       ⚠️  spawned, no JSON-RPC response in 5s    ← cause invisible
  apktool     process exited (code=1)                  ← cause invisible
  frida       process exited (code=1)                  ← cause invisible

  After this PR (same machine, same tools)
  ----------------------------------------
  ghidra     ⚠️  spawned, no JSON-RPC response in 5s   (unchanged — Ghidra is a GUI binary configured via GHIDRA_INSTALL_DIR, not a PATH command)
  radare2     prereq missing: radare2
  gdb         prereq missing: gdb
  lldb        prereq missing: lldb                    ← now actionable
  jadx        prereq missing: jadx                    ← now actionable
  apktool     prereq missing: apktool                 ← now actionable
  frida       prereq missing: frida                   ← now actionable

  → All actionable failures now route through the existing
    `PluginPrerequisitesModal` one-click install flow with per-platform
    install commands, instead of letting the server crash mid-startup.

What this PR adds (servers.json)

- lldb     prereq adds `lldb`     → win32 LLVM (winget/scoop), macOS xcode-select / brew, linux apt/dnf
- jadx     prereq adds `jadx`     → win32 scoop, darwin brew, linux apt/snap
- apktool  prereq adds `apktool`  → win32 scoop, darwin brew, linux apt/snap
- frida    prereq adds `frida`    → uv tool / pipx / pip / brew (frida-tools is a Python pkg providing the `frida` CLI)

Plus a sibling `scripts/smoke-reverse-engineering-mcps.ts` that:

- reads the same plugin's `servers.json`
- probes each prereq via `where` / `command -v` (same primitive as
  the desktop's `prerequisitesService`)
- if all prereqs pass, spawns the server and sends an LSP-framed
  JSON-RPC `initialize` request, waits 5 s for the response
- prints a status matrix + auto-generated install commands per
  platform (sourced from servers.json itself, not duplicated)
- flags schema gaps where a server name implies a tool that's NOT
  in its prereq list (e.g. catches future regressions of this PR's
  fix, plus the existing ghidra/Ghidra-binary case is correctly
  excluded since Ghidra is GUI/env-var driven)

Why a smoke script in the repo

The existing `scripts/dev-mcp-test.ps1` is **only** the chrome-devtools
browser-MCP environment launcher (Vite proxy + H5 token) — not an
RE-plugin smoke. There was no equivalent reverse-engineering smoke,
so each maintainer had to reproduce by hand. With this script, future
"is the RE plugin healthy?" questions are one `bun run` away.

Plugin version: 0.4.3 → 0.4.4

Verification

- Manually ran `bun run scripts/smoke-reverse-engineering-mcps.ts`
  on a fresh Win11 26200 with uv 0.11.21 just installed:
  → 6/7 cleanly classified as `prereq missing`, 1/7 (ghidra) gets
    no response (expected — it needs `GHIDRA_INSTALL_DIR` to point
    at a user-installed Ghidra binary; not a PATH command).
- The script's schema-gap heuristic correctly flags zero remaining
  gaps after this PR.

Tested: live smoke on a real machine; before/after diff above.
Not-tested: macOS / Linux paths (only Win32 install map exercised).
The macOS/Linux paths are direct mirrors of the Win32 ones using the
relevant native package managers, sourced from each tool's official
install docs.

Confidence: high
Scope-risk: narrow

* fix(reverse-engineering): only ship 3 end-to-end verified MCP servers

Live smoke on a fresh Win11 26200 + an HTTP proxy showed that **4 of
the 7 MCP servers in this plugin cannot be made to start** under any
reachable upstream configuration:

| Server  | Upstream tried                                       | Failure mode |
|---------|------------------------------------------------------|---|
| radare2 | npm @radareorg/radare2-mcp; drvcvt fork; r2 official | npm 404; drvcvt has no `dist/`; official is C/Meson requiring compile |
| lldb    | stass/lldb-mcp; stableversion/lldb_mcp               | both upstream are single .py with no pyproject.toml |
| jadx    | zinja-coder/jadx-mcp-server; mseep-jadx PyPI         | upstream raises `ModuleNotFoundError: 'src'`; PyPI republish is 0-byte placeholder |
| apktool | zinja-coder/apktool-mcp-server; SecFathy/APktool-MCP | uv git fetch errors `Git operation failed`, persists past `uv cache clean`; SecFathy is unpackaged |

The previous commit on this PR (3fef2390) added prerequisites entries
for these 4 servers' tool binaries. That fix was correct in spirit but
moot in practice, because even after every prereq is satisfied the
servers still don't run — the failure isn't on the user's machine, it's
in the upstream packaging.

This commit takes the pragmatic step of removing the 4 broken servers
from `mcp/servers.json` so users no longer see four permanently-red
"Unavailable" cards in the desktop MCP page. The plugin now ships only
the **3 servers that have been live-tested end-to-end**:

| Server | Source                                | Verified state |
|--------|---------------------------------------|---|
| ghidra | uvx pyghidra-mcp                      | spawns; awaits user-set GHIDRA_INSTALL_DIR (by design) |
| gdb    | npx mcp-gdb                           | spawns; needs `gdb` on PATH (prereq declared) |
| frida  | uvx **frida-mcp** (PyPI v0.1.1)       |  initialize OK in 694 ms; serverInfo.name == "Frida" v1.27.2 |

Note frida changed source: was `uvx --from git+...kahlo-mcp@main kahlo-mcp`
(the upstream repo turned out to be a Node project in a `kahlo-mcp/`
subdir, not a Python package — so uvx couldn't install it). The PyPI
package `frida-mcp` is a clean, properly-packaged equivalent.

What this commit changes

- `plugins/reverse-engineering/mcp/servers.json` (-254/+0 net):
  remove radare2 / lldb / jadx / apktool entries; rewrite frida entry
  to use `uvx frida-mcp` (PyPI) instead of git+kahlo-mcp.
- `plugins/reverse-engineering/.claude-plugin/plugin.json`: 0.4.4 → 0.4.5.
- `plugins/reverse-engineering/README.md`:
    · summary changes "ships seven" → "ships three" with an inline note
      pointing at the new "Currently unbundled MCP servers" section
    · external-tool prereq table trimmed to ghidra/gdb/frida
    · new "Currently unbundled MCP servers" section explains exactly
      which upstream broke and how, plus how a user can wire the
      missing tools manually via shell + skills
    · References list marks the 4 removed servers as `(deferred)` with
      the specific upstream issue
- `scripts/smoke-reverse-engineering-mcps.ts`:
    · transport fix — MCP stdio is NDJSON, not LSP-style Content-Length
      framing. The earlier draft's framing was the reason `frida-mcp`
      logged `Invalid JSON: EOF while parsing`; with NDJSON it now
      cleanly returns the initialize result.
    · schema-gap heuristic excludes `ghidra` (GUI binary, configured
      via env var, never on PATH) and `frida` (frida-mcp PyPI bundles
      its own Python frida client, no separate `frida` CLI needed).

Verification

Re-running smoke on a fresh checkout of this branch with proxy 127.0.0.1:7887:

```
=== Reverse-engineering MCP smoke ===
Source: plugins\reverse-engineering\mcp\servers.json
Servers: 3

  ghidra     ⚠️ spawned but no JSON-RPC response in 5010 ms
  gdb         prereq missing: gdb
  frida       initialize ok (694 ms)
```

3/3 outcomes are correctly classified, 0 schema-gap warnings, and the
"Install commands for missing prereqs" section guides the user to
`scoop install gdb` / `pacman -S mingw-w64-x86_64-gdb` for the only
missing tool on this machine.

Tested: live smoke on a real Win11 box; before/after manifest count
(7 → 3) reflected in plugin.json bump.
Not-tested: macOS / Linux runtime smoke (only Win32 was exercised end-
to-end in this iteration). Each server's install map remains correct
across all three platforms.

Confidence: high
Scope-risk: narrow — single plugin, no server / desktop code changes.

---------

Co-authored-by: 你的姓名 <you@example.com>
2026-06-12 18:37:36 +08:00
小橙子
e1fb2b362c
feat(reverse-engineering): declare MCP prerequisites for auto-detect modal (#10)
Populate the v0.5.10 platform feature for the originally reported scenario

(missing uvx / radare2 / java when user enables the RE plugin). All 7 stdio

servers now carry a prerequisites[] array consumed by prerequisitesService +

PluginPrerequisitesModal — instead of red Unavailable cards on first enable,

users see a one-click install modal with winget/scoop/brew/apt/dnf commands.

After dedup the modal renders 5 unique rows: uvx (5 servers), npx (2),

radare2 (1), gdb (1), java (2). Bump plugin version 0.4.2 -> 0.4.3.

Tested: schema parse via McpJsonConfigSchema (7/7 servers valid).

Confidence: high. Scope-risk: narrow (declarative JSON only).

Co-authored-by: 你的姓名 <you@example.com>
2026-06-11 20:13:01 +08:00
cc-haha
64691f7672 fix(plugin): reverse-engineering 0.4.0 -> 0.4.2 review fixes
Self-review of v0.4.0 surfaced three real bugs (would have produced
incorrect output to the user / LLM) plus a handful of consistency
gaps. None of these block enabling the plugin or break the smoke,
but together they erode trust in what the plugin tells the user.

Real bugs fixed
---------------

1. commands/triage.md and commands/report.md used a bash-style
   `${user_config.ARTIFACT_DIR:-artifacts/re-runs}` default-fallback.
   The substitute regex in src/utils/plugins/pluginOptionsStorage.ts
   is a plain `\$\{user_config\.([^}]+)\}` capture -- it would have
   keyed off the literal string `ARTIFACT_DIR:-artifacts/re-runs`,
   missed it, and returned the unsubstituted match, leaking the
   `:-default` syntax into the LLM prompt. Worse, manifest defaults
   (plugin.json `userConfig.ARTIFACT_DIR.default`) are NOT auto-merged
   into runtime options -- `loadPluginOptions` only reads
   settings.json + secureStorage. So the only way `${user_config.X}`
   resolves at all is if the user explicitly set it in the UI; for
   plugin authors who want a sensible fallback, the variable form
   isn't actually useful here. Switched both commands to a prose
   directive ("use ARTIFACT_DIR setting if present; otherwise default
   to artifacts/re-runs") that the LLM can resolve from context. This
   matches how the SKILL.md files already wrote the same idea.

2. agents/reverse-engineer.md "When you do NOT have the right MCP
   server available" still told the agent to point users at the
   `userConfig` toggle for each MCP. Those toggles
   (ENABLE_GHIDRA / ENABLE_RADARE2 / ENABLE_JADX / ENABLE_APKTOOL /
   ENABLE_FRIDA) were removed in the v0.4.0 cleanup -- they never
   actually controlled MCP loading, only made the userConfig dialog
   look more important than it was. The fix points users at
   Settings -> MCP for runtime enable/disable and at the README's
   prerequisites table for install instructions, both of which are
   the actual sources of truth post-cleanup.

3. agents/reverse-engineer.md description listed binwalk as a
   first-class tool in the toolchain bullet. binwalk is not bundled
   as an MCP -- skills/firmware-blob shells out to the binwalk CLI
   directly through Bash. Dropped from the description so the LLM
   doesn't hallucinate a binwalk MCP server when none exists.

Consistency gaps fixed
----------------------

4. README.md opening summary still claimed "five MCP servers ...
   six skills"; reality is seven MCP servers and eleven skills since
   v0.3.0/v0.4.0. The capability tables further down were correct,
   only the lead paragraph was stale. Replaced with a less number-
   coupled summary.

5. README install snippet hard-coded `C:\Users\70641\cc-haha\plugins`
   in three places. Replaced with `(Resolve-Path .\plugins).Path` so
   any contributor's checkout works without editing -- and noted
   `<repo-root>` semantics in prose for non-Windows readers.

6. README References section didn't list the new GDB and LLDB MCPs
   added in v0.4.0. Added entries for signal-slot/mcp-gdb and
   stass/lldb-mcp.

7. Added a README section "LLDB MCP -- fallback if uvx fetch fails"
   covering the upstream-packaging gap in stass/lldb-mcp (it ships
   as a single .py without an installable entry point, so
   `uvx --from git+...` may report `python: can't open file
   'lldb_mcp.py'` on first start). The fallback recipe (clone +
   absolute python3 path) is now in the README rather than something
   each user has to rediscover.

8. plugin.json keywords were narrower than marketplace.json tags:
   keywords had 7 entries, tags had 16. Brought keywords up to the
   12 most useful for plugin discovery (firmware, embedded, debugger,
   single-step, ghidra, radare2, frida, gdb, lldb).

9. skills/triage Step 3 routing table mapped "Live process /
   instrumented session" directly to frida-dynamic. The agent's
   Stage 3 says to read dynamic-debug-overview FIRST and let it pick
   between Frida / GDB / LLDB. Updated triage to match -- now any
   runtime question routes to dynamic-debug-overview, which then
   picks the right lane.

10. agent description had "iOS -> r2 + optionally class-dump", but
    skills/ios-analysis actually keeps Ghidra (with the iOS loader)
    in scope as well. Updated agent to "r2 (preferred, via the
    radare2 MCP) or Ghidra with the iOS loader" so the LLM knows
    Ghidra is also a path on iOS.

Manifest version bump 0.4.0 -> 0.4.2 (two patch bumps because the
first fix pass missed the loadPluginOptions semantics; bumping again
re-materialised the cache after the corrected commands content was
written).

Verification
------------

  bun run plugins/reverse-engineering/scripts/validate.ts
    -> 0 fail / 0 warn (marketplace + plugin schema both clean)

  bun run plugins/reverse-engineering/scripts/smoke.ts
    -> 13 passed / 0 failed
       version=0.4.2, commands=2, agents=1, skills=11, mcpServers=7,
       skill ids match the on-disk glob

  Cache materialised at
  ~/.claude/plugins/cache/cc-haha-builtin/reverse-engineering/0.4.2/
  carries the corrected commands without the bash-style
  default-fallback.

Confidence: high
Scope-risk: narrow (plugin-only patch; no source code touched, no
breaking change to existing skill names or component counts).
Tested: validate (pass), smoke (13/13 pass), cache content spot-check
of commands/report.md.
Not-tested: live `/reverse-engineering:triage` against a real sample
end-to-end; the substitution change is text-only and the runtime
loader was unaffected.
2026-06-11 01:48:36 +08:00
小橙子
65a042bd08
fix: detect & isolate fake tool_use leaks from non-Anthropic providers (#4)
* feat(plugin): add reverse-engineering plugin v0.4.0 + project-level codegraph

Bundles a multi-platform RE toolkit as a local marketplace plugin that
clone-and-enable installs in cc-haha desktop. Covers static (Ghidra,
radare2, JADX, apktool, binwalk via Bash) and the gap a previous review
called out: dynamic debugging is the lane that matters most for
AI-driven RE, but Frida alone cannot single-step or set real
breakpoints. This commit fills that gap with three non-overlapping
dynamic lanes -- Frida (instrumentation, mobile, broad surveys), GDB
(cross-arch single-step + breakpoints, embedded firmware), LLDB (Apple
platforms, ObjC/Swift).

Architecture coverage spans MIPS / ARM (incl. Cortex-M Thumb) /
PowerPC (incl. e200 VLE) / 68k (incl. Mac Toolbox A-line traps) /
SuperH / RISC-V / x86-64 / AArch64. The agent picks the right ISA,
base address, and load configuration in skills/firmware-blob, then
hands off to skills/pe-elf-macho with per-architecture decompilation
notes (MIPS delay slots, ARM Thumb interworking, PowerPC TOC/SDA,
68k register banks, etc).

Plugin layout (.claude-plugin marketplace + plugin manifests):

  plugins/.claude-plugin/marketplace.json
  plugins/reverse-engineering/
    .claude-plugin/plugin.json     v0.4.0, GHIDRA_INSTALL_DIR + ARTIFACT_DIR userConfig
    agents/reverse-engineer.md     triage -> static -> optional dynamic -> report
    skills/triage/                 file-type identification, packing, routing
    skills/pe-elf-macho/           Ghidra/r2 + per-arch notes for non-x86
    skills/firmware-blob/          raw blob -> ISA + base address + Ghidra/r2 load config
    skills/apk-analysis/           JADX + apktool, Android attack surface
    skills/ios-analysis/           IPA bundle + Mach-O, FairPlay-aware
    skills/dynamic-debug-overview/ decision matrix Frida vs GDB vs LLDB
    skills/frida-dynamic/          hooks + Memory + CpuContext + Stalker + watchpoints
    skills/gdb-debug/              cross-arch via gdbserver / qemu-user / qemu-system
    skills/lldb-debug/             macOS / iOS device / Linux, ObjC/Swift symbols
    skills/crackme-keygen/         CTF / self-owned binary; ships keygen, not patch
    skills/re-report/              final structured report with IOCs + confidence
    commands/triage.md             /reverse-engineering:triage <path>
    commands/report.md             /reverse-engineering:report <sample-id>
    mcp/servers.json               7 MCP servers: ghidra (pyghidra-mcp PyPI),
                                   radare2 (npm), gdb (mcp-gdb npm), lldb
                                   (stass/lldb-mcp git), jadx, apktool, frida
                                   (kahlo-mcp git, all @main pinned)
    hooks/hooks.json               empty placeholder
    scripts/validate.ts            offline schema check via project's
                                   validatePluginManifest + validatePluginContents
    scripts/dev-link.ts            Windows mklink /J cache -> repo source so SKILL
                                   edits don't require version bump (--restore undoes)
    scripts/smoke.ts               end-to-end: marketplace register -> enable ->
                                   update -> reload -> assert detail counts.
                                   Counts derived from on-disk source (glob),
                                   not hardcoded; idempotent across reruns.
    README.md                      install, prerequisites per MCP, dynamic-capabilities
                                   matrix, architecture coverage, quickstart with
                                   busybox sample, dev-link / smoke workflows

Project-level MCP (clone-and-go for any contributor):

  .mcp.json                        codegraph (npx -y codegraph serve --mcp);
                                   project scope, picked up automatically.
                                   Pre-existing user-level codegraph install
                                   still wins for users who have one.

userConfig of the plugin is honest -- only knobs that actually do
something (GHIDRA_INSTALL_DIR substituted into the ghidra MCP env;
ARTIFACT_DIR resolved relative to agent cwd at run time). Earlier
ENABLE_* booleans were removed because they didn't actually toggle
anything; users disable individual MCP servers from Settings -> MCP
at runtime.

End-to-end verification (server on :3456, vite on :1420):
  - bun run plugins/reverse-engineering/scripts/validate.ts
    -> 0 fail / 0 warn (marketplace + plugin schema both clean)
  - bun run plugins/reverse-engineering/scripts/smoke.ts
    -> 13 passed / 0 failed (marketplace registration, enable, update
       0.3.0->0.4.0, reload, detail assertions including 11 skill ids
       matching the on-disk glob)
  - GET /api/plugins/detail
    -> commands=2 / agents=1 / skills=11 / mcpServers=7 / errors=[]
  - GET /api/mcp
    -> codegraph appears with scope=project,
       configLocation=.mcp.json
  - Desktop UI Settings -> Plugins
    -> v0.4.0 listed in enabled group, "11 skills 1 Agent 7 MCP"

External tool prerequisites (each MCP needs its underlying tool on
PATH; users install per-MCP independently -- none required all at
once): Ghidra (Java 17+), r2, GDB (gdb-multiarch for cross-arch),
LLDB, frida-tools, JADX (Java 17+), apktool. Codegraph requires
either a local global install (npm i -g codegraph) or relies on npx
to fetch on first run.

Constraint: Cannot single-step from Frida alone -- that's the gap GDB
and LLDB fill in this commit. dynamic-debug-overview SKILL is the
canonical decision guide for which lane to use.
Confidence: high
Scope-risk: narrow (additive plugin under plugins/, additive
.mcp.json; zero changes to existing source). Plugin loads via the
existing PluginManifestSchema path -- no new server-side code added.
Tested: bun run plugins/reverse-engineering/scripts/validate.ts (pass),
bun run plugins/reverse-engineering/scripts/smoke.ts (13/13 pass), live
chrome-devtools MCP smoke against running desktop UI verifying plugin
appears with correct version + counts.
Not-tested: real underlying-tool integration (Ghidra/r2/JADX/GDB/LLDB
must be installed by the user; the plugin only validates manifest
shape and component loading). Reverse-debugging (rr) flow is
documented in skills/gdb-debug but not exercised in smoke.

* fix: detect & isolate fake tool_use leaks from non-Anthropic providers

Some third-party Anthropic-compatible gateways (mimo, lgfzer, certain
proxies) don't relay native tool_use blocks: when the model wants to
call a tool, it ends up emitting an XML-style `<tool_use ...>{...}
</tool_use>` element as plain text inside content_delta. The desktop
chat renders that as markdown, marked passes the unknown element
through as raw HTML, the browser collapses whitespace, and the user
sees garbage like `<tool_useid="..."` followed by a smushed shell
command. Worse, the model has no signal the call never executed and
follows up with apologies ("工具用错了, 重来:") and retries — every
"call" is text and nothing runs.

Three layers of defense, lowest to highest in the stack:

1. **Source-side priming reduction** (server). The specialistRouter
   docstring already warned that `key="value"` attribute fragments in
   model-facing strings make some gateway models switch from real
   tool_use blocks into XML mode. Three model-facing nudges still
   embedded `subagent_type="verification"` in their reminders — fix
   them to use prose ("set the subagent_type parameter to verification")
   so we stop priming the failure mode:
     - src/utils/messages.ts (verification_gate_reminder)
     - src/tools/TodoWriteTool/TodoWriteTool.ts
     - src/tools/TaskUpdateTool/TaskUpdateTool.ts
   New regression test src/utils/noKeyValueNudges.test.ts pins all
   three call sites so a future "typo fix" doesn't accidentally
   re-introduce the attribute form.

2. **Detection + sanitization** (desktop). New
   desktop/src/lib/fakeToolUseDetection.ts extracts XML-style
   `<tool_use>` blocks (closed, half-open mid-stream, multiple
   consecutive retries, `name`/`id` in either order, quoted or
   unquoted, inside or outside fenced code blocks) and returns
   `{ cleanText, blocks[] }`. AssistantMessage uses this before
   handing content to MarkdownRenderer so the user never sees the
   XML garbage; copyText also uses cleanContent so users don't paste
   broken markup. Fenced code blocks (```xml ... ```) are preserved
   so legitimate documentation examples render as-is.

3. **Provider compatibility tracking** (desktop). New
   desktop/src/stores/providerCompatStore.ts counts fake tool_use
   leaks per provider id, persisted to localStorage. After 3 leaks
   it fires a one-time toast suggesting the user switch providers.
   Settings → Provider list shows a "工具调用异常" badge on offending
   providers; saving an edit on a flagged provider re-arms the
   warning (clearProvider). Above-threshold detection is silent
   afterward so we don't spam — the badge stays as the persistent
   signal.

The new FakeToolUseNotice component renders an inline grey card
above the assistant bubble naming the attempted tool ("Bash") and
making it clear nothing ran, so the user knows not to trust any
follow-up "Done." claim that depended on the call's output.

Verified:
- desktop: bun run lint clean
- desktop: 28 new tests + existing AssistantMessage.linkrouting (15
  total) pass
- server: 31 tests across verificationGate, specialistRouter, and the
  new noKeyValueNudges regression all pass

Tested: bun run lint (desktop), bunx vitest run (3 desktop suites),
        bun test (3 server suites)
Confidence: high
Scope-risk: narrow

---------

Co-authored-by: cc-haha <cc-haha@users.noreply.github.com>
Co-authored-by: 你的姓名 <you@example.com>
2026-06-10 20:01:32 +08:00