docs: define v0.1 product and architecture contract
This commit is contained in:
parent
57597e7d6d
commit
c4fb0c6a01
12 changed files with 2130 additions and 64 deletions
16
docs/adr/0001-version-the-json-contract.md
Normal file
16
docs/adr/0001-version-the-json-contract.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
status: accepted
|
||||
---
|
||||
|
||||
# Version the JSON contract by compatibility boundary
|
||||
|
||||
Automation agents are the primary interface consumers, so success and error
|
||||
shapes must remain predictable across upgrades. The project publishes reviewed
|
||||
JSON Schema files under `schemas/v1/`: optional fields may be added within v1
|
||||
and consumers must ignore unknown fields, while removing a field or changing
|
||||
its type or meaning requires a new major schema version. The CLI initially
|
||||
emits only its current schema rather than carrying multiple encoders; golden
|
||||
fixtures and schema validation gate every success and error change. JSON mode
|
||||
also preserves strict stream framing: a success is the only stdout document, a
|
||||
failure is the only stderr document, and progress or diagnostic logs never
|
||||
share either stream.
|
||||
11
docs/adr/0002-keep-exit-statuses-coarse.md
Normal file
11
docs/adr/0002-keep-exit-statuses-coarse.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
status: accepted
|
||||
---
|
||||
|
||||
# Keep exit statuses coarse
|
||||
|
||||
Shell callers need broad control-flow signals, while automation agents need
|
||||
precise diagnoses. The CLI therefore reserves exit statuses for success,
|
||||
invalid invocation, runtime failure, timeout, and interruption; the versioned
|
||||
JSON `error.code` carries the detailed failure taxonomy. This avoids maintaining
|
||||
two overlapping error classifications that could drift apart.
|
||||
13
docs/adr/0003-default-to-json-everywhere.md
Normal file
13
docs/adr/0003-default-to-json-everywhere.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
status: accepted
|
||||
---
|
||||
|
||||
# Default to JSON everywhere
|
||||
|
||||
Automation agents are the primary consumers, so the shortest invocation must
|
||||
produce the stable machine interface. The CLI defaults to JSON regardless of
|
||||
whether stdout is a terminal or pipe; human-readable presentation requires
|
||||
`--format human`. Avoiding TTY-dependent format selection ensures that the same
|
||||
command has the same framing and semantics in every execution environment.
|
||||
JSON mode never emits interactive prompts; required confirmation is represented
|
||||
as a structured error and satisfied by an explicit flag.
|
||||
11
docs/adr/0004-never-reuse-partial-analysis.md
Normal file
11
docs/adr/0004-never-reuse-partial-analysis.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
status: accepted
|
||||
---
|
||||
|
||||
# Never reuse a partial Analysis
|
||||
|
||||
Ghidra preserves analyzer results completed before its analysis timeout, but
|
||||
automation consumers cannot safely infer which facts are missing. New Analyses
|
||||
are therefore built in staging and promoted atomically only after full success;
|
||||
a timeout, interruption, or analysis failure preserves the Diagnostic Log,
|
||||
removes the partial staging project, and leaves no reusable Analysis.
|
||||
13
docs/adr/0005-analyze-a-staged-sample-snapshot.md
Normal file
13
docs/adr/0005-analyze-a-staged-sample-snapshot.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
status: accepted
|
||||
---
|
||||
|
||||
# Analyze a staged Sample snapshot
|
||||
|
||||
Passing the caller's path to Ghidra after hashing creates a race in which the
|
||||
bytes analyzed may differ from the recorded Sample identity. The CLI instead
|
||||
copies the input through a read-only handle into private staging while hashing,
|
||||
verifies that source metadata did not change during the copy, and gives only
|
||||
that snapshot to Ghidra. The extra temporary I/O and disk use buy reproducible
|
||||
identity, isolate Java from the caller's path, and allow the snapshot to be
|
||||
removed after either success or failure.
|
||||
14
docs/adr/0006-sandbox-the-ghidra-worker.md
Normal file
14
docs/adr/0006-sandbox-the-ghidra-worker.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
status: accepted
|
||||
---
|
||||
|
||||
# Sandbox the Ghidra worker
|
||||
|
||||
Ghidra parses adversarial Samples through a large Java analysis engine, while
|
||||
the Rust harness must manage trusted locks, store data, and atomic promotion.
|
||||
The harness therefore remains outside a narrow sandbox containing only the
|
||||
Ghidra/JVM worker and its invocation capabilities. Bubblewrap is the default
|
||||
Linux backend, with explicit external-harness and disabled modes for
|
||||
environments that cannot nest it. This limits a compromised worker's access
|
||||
without turning the CLI into a daemon or allowing arbitrary sandbox-runner
|
||||
commands.
|
||||
425
docs/research/ghidra-mcp-comparison.md
Normal file
425
docs/research/ghidra-mcp-comparison.md
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
# Ghidra MCP comparison for `ghidra-cli`
|
||||
|
||||
Status: reviewed and adopted into the PRD, 2026-07-28
|
||||
|
||||
## Scope and source snapshots
|
||||
|
||||
This note compares the accepted `ghidra-cli` version 0.1 design with two
|
||||
primary-source repositories:
|
||||
|
||||
- LaurieWired/GhidraMCP at commit
|
||||
[`27f316f80139e2d5dec882519a1bdf4aa46ac04c`](https://github.com/LaurieWired/GhidraMCP/tree/27f316f80139e2d5dec882519a1bdf4aa46ac04c),
|
||||
tagged `1.4`.
|
||||
- bethington/ghidra-mcp at commit
|
||||
[`8cd2078e10b9ba28b188cb84ce5b9051a904b995`](https://github.com/bethington/ghidra-mcp/tree/8cd2078e10b9ba28b188cb84ce5b9051a904b995),
|
||||
tagged `v6.0.0`.
|
||||
|
||||
The comparison uses repository source, manifests, CI, licenses, changelogs,
|
||||
and the repositories' own issue reports. It does not treat README marketing
|
||||
claims as independently verified performance evidence.
|
||||
|
||||
## Executive conclusion
|
||||
|
||||
Do not change the version 0.1 architecture or widen its command surface because
|
||||
of these MCP implementations. The accepted synchronous Rust harness, private
|
||||
file protocol, one Sample per command, immutable Analysis, strict JSON, and
|
||||
sandboxed short-lived Ghidra worker directly avoid the largest sources of
|
||||
complexity visible in both repositories.
|
||||
|
||||
The repositories nevertheless validate several useful mechanisms:
|
||||
|
||||
- Relationship queries are the most valuable next capability for an agent.
|
||||
Prioritize `strings`, `xrefs`, and direct `calls` after version 0.1, followed
|
||||
by targeted disassembly and detailed single-Function inspection.
|
||||
- A large agent surface needs categories and capability discovery. Do not build
|
||||
that for four commands, but preserve command metadata so a future MCP adapter
|
||||
can expose a small, generated catalog without hand-written duplication.
|
||||
- Every process/protocol boundary needs independent size validation and
|
||||
parity tests. Add a small explicit ceiling for internal `request.json`, and
|
||||
test that Rust operation types, Java dispatch, and published schemas contain
|
||||
exactly the same operations.
|
||||
- Explicit target selection is essential. Keep requiring the Sample and exact
|
||||
Function Selector on every Query; never introduce a mutable “current
|
||||
program” fallback.
|
||||
- An eventual MCP integration should be a thin, separate adapter that invokes
|
||||
`ghidr` and forwards its schemas. It should not add an embedded HTTP server,
|
||||
persistent Ghidra session, or a second result contract to the core.
|
||||
|
||||
The principal rejection is breadth. One examined project exposes 272 catalog
|
||||
entries, including mutations, arbitrary scripts, project lifecycle, emulation,
|
||||
and debugging; its own users subsequently requested filtering because the
|
||||
surface was too large for agents ([issue #267](https://github.com/bethington/ghidra-mcp/issues/267))
|
||||
and raised broader maintainability and provenance concerns
|
||||
([issue #307](https://github.com/bethington/ghidra-mcp/issues/307)). The current
|
||||
`ghidra-cli` narrow vertical slice is a feature, not a gap.
|
||||
|
||||
## LaurieWired/GhidraMCP
|
||||
|
||||
### Architecture and state
|
||||
|
||||
The original project is a three-hop interactive topology:
|
||||
|
||||
```text
|
||||
MCP client -> Python FastMCP bridge -> HTTP -> plugin in a running Ghidra GUI
|
||||
```
|
||||
|
||||
The Python process speaks MCP over stdio by default and can expose SSE; every
|
||||
tool then makes an HTTP request to a configured Ghidra URL
|
||||
([bridge initialization and transports](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L15-L24),
|
||||
[CLI transport selection](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L290-L334)).
|
||||
The Java extension embeds `com.sun.net.httpserver.HttpServer` in Ghidra and
|
||||
operates on the GUI's `ProgramManager.getCurrentProgram()`
|
||||
([server construction](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L95-L114),
|
||||
[current Program resolution](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L1627-L1629)).
|
||||
Installation therefore requires installing and enabling a GUI extension and
|
||||
opening Ghidra, in addition to Python and the MCP SDK
|
||||
([installation prerequisites and workflow](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/README.md#L30-L79)).
|
||||
|
||||
This is session-oriented rather than content-oriented. The Sample is not an
|
||||
argument to a tool call; the implicit target is whichever Program is current
|
||||
in the GUI. Cursor-dependent tools expose the current address and Function,
|
||||
further coupling automation to interactive state
|
||||
([current-selection tool wrappers](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L150-L176)).
|
||||
|
||||
### Tool surface and mutation
|
||||
|
||||
The snapshot has 27 Python `@mcp.tool` declarations mapped to 27 Java HTTP
|
||||
contexts. Read tools cover Functions, classes/namespaces, segments, imports,
|
||||
exports, data, strings, disassembly, and cross-references. Write tools rename
|
||||
Functions, data, and variables; change prototypes and local types; and add
|
||||
comments
|
||||
([Python tool declarations](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L60-L288),
|
||||
[HTTP context registration](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L109-L342)).
|
||||
Writes run Ghidra transactions on the Swing event thread, so this is not a
|
||||
read-only analysis layer
|
||||
([rename transaction](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L512-L540)).
|
||||
|
||||
### Result contract and bounds
|
||||
|
||||
The outer MCP parameter schemas are inferred from Python type annotations, but
|
||||
the inner HTTP protocol is mostly newline-delimited text. `safe_get` splits
|
||||
text into lines, while failures are returned as strings in the same value
|
||||
channel rather than typed MCP errors
|
||||
([GET behavior](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L26-L43),
|
||||
[POST behavior](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L45-L58)).
|
||||
The Java server always responds with HTTP 200 and `text/plain`, including
|
||||
application errors
|
||||
([response writer](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L1632-L1638)).
|
||||
|
||||
Many lists have `offset` and `limit`, normally defaulting to 0 and 100, but
|
||||
there is no `total`, `returned`, or `has_more` metadata. Some duplicate tools
|
||||
are unpaginated, such as `list_functions`
|
||||
([list wrapper](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L171-L176)).
|
||||
Pagination accepts negative and arbitrarily large values without a public
|
||||
validation contract
|
||||
([pagination implementation](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L1582-L1606)).
|
||||
There is no whole-response byte bound or spill mechanism.
|
||||
|
||||
Function selection is ambiguous by construction: name selection returns the
|
||||
first exact basename encountered, and address selection accepts an interior
|
||||
address by resolving the containing Function
|
||||
([name selection](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L493-L509),
|
||||
[interior-address fallback](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L782-L805)).
|
||||
These are precisely the ambiguities the accepted `ghidra-cli` selector
|
||||
contract should continue rejecting.
|
||||
|
||||
The timeout layers are inconsistent. The Python bridge times every HTTP call
|
||||
out after five seconds, while Java decompilation can run for 30 or 60 seconds
|
||||
([bridge timeouts](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L35-L51),
|
||||
[decompiler timeout](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L1202-L1217)).
|
||||
An MCP caller can therefore receive a timeout while Ghidra continues working.
|
||||
This validates `ghidra-cli`'s single derived child watchdog and process-group
|
||||
termination design.
|
||||
|
||||
### Security, tests, and dependencies
|
||||
|
||||
`new InetSocketAddress(port)` binds the embedded server without an explicit
|
||||
loopback address, and the request path shown has neither authentication nor a
|
||||
body-size cap
|
||||
([server bind](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L95-L108),
|
||||
[unbounded form read](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/main/java/com/lauriewired/GhidraMCPPlugin.java#L1559-L1579)).
|
||||
The Ghidra process and plugin are unsandboxed and inherit the GUI user's
|
||||
filesystem and network access.
|
||||
|
||||
Runtime dependencies are Python 3.10+, `mcp`, and `requests`; the Java build
|
||||
uses Maven plus manually copied Ghidra JARs
|
||||
([Python requirements](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/bridge_mcp_ghidra.py#L1-L6),
|
||||
[system-scoped Ghidra JARs](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/pom.xml#L12-L87)).
|
||||
The only committed Java test is the Maven-template `assertTrue(true)` smoke
|
||||
test, so no endpoint, schema, timeout, security, or real-Ghidra regression is
|
||||
covered
|
||||
([test source](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/src/test/java/com/lauriewired/AppTest.java#L1-L35)).
|
||||
|
||||
## bethington/ghidra-mcp
|
||||
|
||||
### Architecture and state
|
||||
|
||||
This repository identifies itself as a substantial derivative of the Laurie
|
||||
project, not an independent implementation
|
||||
([NOTICE](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/NOTICE#L1-L10)).
|
||||
It retains the bridge topology but expands it:
|
||||
|
||||
```text
|
||||
MCP client -> Python MCP bridge -> UDS or HTTP -> Ghidra GUI plugin
|
||||
-> headless Java server
|
||||
-> optional debugger service
|
||||
```
|
||||
|
||||
The bridge supports stdio, Streamable HTTP, and deprecated SSE
|
||||
([bridge CLI](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/cli.py#L102-L143)),
|
||||
while repository documentation describes separate GUI and headless Java
|
||||
servers and a Python protocol-conversion process
|
||||
([architecture](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L977-L995)).
|
||||
|
||||
The bridge discovers server instances, remembers a connected project and
|
||||
transport, dynamically registers tools, and attempts reconnection after Ghidra
|
||||
restarts
|
||||
([shared mutable state](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/state.py#L14-L35),
|
||||
[reconnection](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/dispatch.py#L67-L128)).
|
||||
The cost is visible in the project's own issue history. Concurrent sub-agent
|
||||
calls caused intermittent bridge shutdown and forced a serialization lock
|
||||
([issue #91](https://github.com/bethington/ghidra-mcp/issues/91),
|
||||
[current lock](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/state.py#L18-L25)).
|
||||
On macOS, a GUI/agent environment mismatch in `$TMPDIR` prevented Unix-socket
|
||||
discovery until the implementation learned to scan multiple candidate
|
||||
locations ([issue #170](https://github.com/bethington/ghidra-mcp/issues/170),
|
||||
[current discovery rationale](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/discovery.py#L20-L40)).
|
||||
|
||||
These are real solutions for a persistent multi-instance service, but they are
|
||||
states `ghidra-cli` intentionally does not have.
|
||||
|
||||
### Tool surface, discovery, and mutation
|
||||
|
||||
The committed endpoint catalog declares 272 tools across 15 categories
|
||||
([catalog](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/tests/endpoints.json)).
|
||||
The surface includes read-only listing, Functions, cross-references, call
|
||||
graphs, strings, data flow, and memory inspection, but also symbol/type/comment
|
||||
mutation, project deletion and version control, arbitrary Java script
|
||||
execution, P-code emulation, and live debugging
|
||||
([feature inventory](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L69-L95),
|
||||
[API categories](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L624-L740)).
|
||||
|
||||
Its most reusable mechanism is annotation-driven registration. Java service
|
||||
methods carry tool/parameter annotations; one scanner creates deterministic
|
||||
HTTP endpoint definitions and a machine-readable input schema
|
||||
([scanner contract and sorting](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/AnnotationScanner.java#L13-L87),
|
||||
[descriptor construction](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/AnnotationScanner.java#L94-L118)).
|
||||
The Python bridge fetches that schema and generates callable MCP signatures at
|
||||
runtime
|
||||
([dynamic handler construction](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/registry.py#L22-L146),
|
||||
[schema fetch](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/registry.py#L302-L318)).
|
||||
|
||||
The current bridge can load categories lazily and exposes `search_tools`,
|
||||
`list_tool_groups`, and load/unload/check helpers
|
||||
([agent discovery workflow](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L377-L393)).
|
||||
This directly addresses tool overload, but it also introduces another mutable
|
||||
session dimension and depends on clients honoring `tools/list_changed`.
|
||||
|
||||
### Result contract and bounds
|
||||
|
||||
The Java response type is an improvement over the original but remains a
|
||||
union of structured success, simple `{ "error": "message" }`, and raw text
|
||||
passthrough
|
||||
([sealed `Response`](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/Response.java#L3-L43)).
|
||||
Consequently, some endpoints return JSON while many paginated endpoints still
|
||||
return newline-delimited text, and the dynamically generated MCP handlers all
|
||||
declare `str` results
|
||||
([text pagination](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/ServiceUtils.java#L232-L244),
|
||||
[generated return annotation](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/registry.py#L111-L145)).
|
||||
The live tool schema describes inputs, not stable versioned output schemas or
|
||||
typed error codes
|
||||
([schema translation](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/schema.py#L54-L99)).
|
||||
|
||||
Request hardening is much stronger: version 6.0 applies a 64 MiB body ceiling
|
||||
and bounds actual reads rather than trusting `Content-Length`
|
||||
([request ceiling](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/SecurityConfig.java#L51-L75),
|
||||
[HTTP enforcement](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/GhidraMCPPlugin.java#L2253-L2263)).
|
||||
Response bounding remains endpoint-specific rather than a universal contract.
|
||||
For example, the whole-program call graph accepts `limit=0` as unlimited and
|
||||
can emit text, DOT, Mermaid, adjacency, or address-edge JSON
|
||||
([call-graph parameters and implementation](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/XrefCallGraphService.java#L782-L877)).
|
||||
`ghidra-cli`'s one schema, disclosed collection bounds, and universal serialized
|
||||
byte cap are materially stronger for an automation agent.
|
||||
|
||||
The bridge coordinates endpoint-specific HTTP timeouts and retries GET calls;
|
||||
write calls deliberately avoid blind retry because completion is ambiguous
|
||||
after a connection loss
|
||||
([timeout catalog](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/config.py#L7-L35),
|
||||
[retry policies](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/dispatch.py#L131-L195)).
|
||||
Notably, the bridge's `decompile_function` timeout is 45 seconds while the
|
||||
Java endpoint defaults to 60 seconds
|
||||
([bridge value](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/config.py#L21-L34),
|
||||
[Java value](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/core/FunctionService.java#L149-L186)).
|
||||
This is further evidence that one owning harness should derive and enforce the
|
||||
complete child lifetime.
|
||||
|
||||
The project learned an important target-selection lesson. Calls default to a
|
||||
mutable active Program; strict explicit Program selectors exist only behind an
|
||||
environment variable because compatibility retained the fallback
|
||||
([strict-mode rationale](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L350-L375),
|
||||
[bridge enforcement](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/python/bridge_mcp_ghidra/registry.py#L26-L87)).
|
||||
`ghidra-cli` should keep Sample selection mandatory from its first release and
|
||||
never acquire this compatibility burden.
|
||||
|
||||
### Security, tests, and dependencies
|
||||
|
||||
Current security controls include loopback defaults, refusal of a non-loopback
|
||||
bind without bearer authentication, anti-CSRF/DNS-rebinding checks, script
|
||||
execution disabled by default, filesystem-root containment, request-body
|
||||
caps, and generic uncaught-error responses
|
||||
([security configuration](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/README.md#L440-L493),
|
||||
[safe handler](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/src/main/java/com/xebyte/GhidraMCPPlugin.java#L2210-L2279)).
|
||||
The change history states that these were hardening changes after earlier
|
||||
unauthenticated, ungated, or insufficiently bounded behavior
|
||||
([v6.0.0 security history](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/CHANGELOG.md#L7-L73)).
|
||||
|
||||
These controls are valuable for a network service, but they do not isolate
|
||||
Ghidra from the user's account. `ghidra-cli` should retain the stronger design
|
||||
of no listener plus a capability-limited worker sandbox. If it ever adds an
|
||||
MCP adapter, stdio should remain the default and network transport should be a
|
||||
separately threat-modeled feature.
|
||||
|
||||
The shipped bridge requires Python 3.10+ and the `mcp` package; tests and
|
||||
unshipped subsystems add many optional dependencies
|
||||
([package manifest](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/pyproject.toml#L1-L24),
|
||||
[dependency groups](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/pyproject.toml#L53-L101)).
|
||||
The repository has a materially stronger regression practice than the original:
|
||||
Java offline tests and coverage, a Python version matrix, Windows-specific
|
||||
tests, performance regressions, and Pester setup tests are gating; formatting
|
||||
and lint jobs are currently advisory
|
||||
([CI gates](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/.github/workflows/tests.yml#L16-L181),
|
||||
[quality jobs](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/.github/workflows/tests.yml#L288-L358)).
|
||||
Its annotation/schema/catalog parity approach is worth borrowing; its broad
|
||||
multi-subsystem matrix is not needed for version 0.1.
|
||||
|
||||
## Recommendations for `ghidra-cli`
|
||||
|
||||
### Adopt now
|
||||
|
||||
1. **Keep one source of truth for operations.** Define every internal operation
|
||||
once with its request type, response type, public command kind, and schema
|
||||
name. Generate or mechanically verify the Rust schema set and Java dispatch
|
||||
set against it. The bethington annotation scanner demonstrates the value of
|
||||
preventing hand-maintained endpoint/schema drift, while `ghidra-cli` can do
|
||||
this without runtime reflection or dynamic registration.
|
||||
|
||||
2. **Cap both sides of the private protocol.** The accepted 256 MiB response
|
||||
ceiling is good. Add a much smaller fixed serialized `request.json` ceiling,
|
||||
suggested at 1 MiB, enforced by Rust before launch and Java before parse.
|
||||
Requests contain metadata and tool-owned paths, not Sample bytes, so a
|
||||
larger allowance has no known use. This is a protocol-hardening detail, not
|
||||
a public CLI flag.
|
||||
|
||||
3. **Add protocol/catalog parity regressions.** A fixture should fail if a Rust
|
||||
operation lacks Java handling, if Java accepts an unpublished operation, or
|
||||
if a public schema/golden is missing. Keep the already accepted real-Ghidra
|
||||
ELF and PE integration tests and strict lint/format gates.
|
||||
|
||||
4. **Retain mandatory targets and stable identifiers.** Every Query keeps an
|
||||
explicit Sample; Function operations keep exact entry Address or exact name
|
||||
selection with ambiguity errors. Do not add GUI cursor, active Program, or
|
||||
implicit containing-Function behavior.
|
||||
|
||||
5. **Preserve extension metadata without exposing it yet.** Command/domain
|
||||
definitions may carry a stable category and one-sentence agent description.
|
||||
This costs little and lets a later MCP adapter generate a discoverable
|
||||
surface, but version 0.1 needs no `search-tools`, runtime group loading, or
|
||||
config file.
|
||||
|
||||
### Defer in priority order
|
||||
|
||||
1. **`strings` Query.** Return a bounded, stably ordered list with exact Address,
|
||||
byte length, decoded representation with explicit encoding, and referring
|
||||
Addresses or a separate bounded reference link. Avoid case-folded server-side
|
||||
substring filters initially; agents can use `jq`, and raw Sample-derived
|
||||
strings may be non-UTF-8 or extremely large.
|
||||
|
||||
2. **`xrefs` Query.** Model each Reference structurally: source Address,
|
||||
destination Address or external target, Ghidra reference type, operand index,
|
||||
and containing source Function entry when present. Direction should be an
|
||||
explicit enum. Never emit prose such as `From ... in ... [READ]`.
|
||||
|
||||
3. **Direct `calls` Query.** Start with flat Call Edges containing caller entry,
|
||||
callsite, and resolved or unresolved callee. Support pagination and stable
|
||||
ordering. Do not begin with recursive whole-program graphs, DOT/Mermaid
|
||||
renderers, graph algorithms, or `limit=0` unlimited output; agents can build
|
||||
graphs from bounded edge pages.
|
||||
|
||||
4. **Targeted `disassemble` and `function` detail Queries.** These are more
|
||||
useful than widening every `functions` item. A detailed Function result can
|
||||
later carry signature, parameters, locals, body ranges, thunk resolution,
|
||||
and alternate symbols without making discovery pages expensive.
|
||||
|
||||
5. **Batch Query execution.** Multiple decompilations in one Ghidra process may
|
||||
eventually reduce startup cost, but only add it after measurements. It needs
|
||||
explicit per-item success/error objects, deterministic order, aggregate
|
||||
byte bounds, cancellation semantics, and no hidden parallelism.
|
||||
|
||||
6. **Thin MCP adapter.** After schemas and CLI behavior stabilize, a separate
|
||||
package can expose one MCP tool per stable `ghidr` Query, invoke the CLI over
|
||||
stdio/process execution, and forward its JSON unchanged. If the surface later
|
||||
grows, generated categories and a capability-search tool become appropriate.
|
||||
The adapter should not call Ghidra directly or own Analysis Store policy.
|
||||
|
||||
7. **Interactive/mutating analysis.** Renames, types, comments, scripts,
|
||||
debugging, and shared-project workflows solve a different problem. If ever
|
||||
pursued, put them behind a separate explicit write-capability model and a
|
||||
separate domain contract; do not weaken immutable Analysis semantics.
|
||||
|
||||
### Explicitly reject for version 0.1
|
||||
|
||||
- Embedded HTTP, TCP, UDS, SSE, Streamable HTTP, discovery scans, reconnect
|
||||
logic, persistent daemons, and background JVMs.
|
||||
- Mutable current-Program/current-Address/current-Function session state.
|
||||
- Any automatic first match for duplicate Function names or interior addresses.
|
||||
- Raw text success/error unions, HTTP-status tunneling, untyped addresses, and
|
||||
prose records that agents must parse.
|
||||
- Hidden retry escalation. A caller may explicitly retry a failed read-only
|
||||
Query; `ghidr` must not multiply time budgets behind the contract.
|
||||
- Arbitrary Java/Python scripts, mutation, project management, debugger control,
|
||||
emulation, binary export, and Ghidra Server administration.
|
||||
- Whole-program graph renderers and custom search/query languages. Emit bounded
|
||||
facts; let `jq` and downstream tools transform them.
|
||||
- Runtime tool-group loading for the initial four-command surface.
|
||||
- A database or global mutable index merely to support discovery or MCP state.
|
||||
|
||||
## Licensing and provenance
|
||||
|
||||
Both repositories use Apache-2.0
|
||||
([LaurieWired license](https://github.com/LaurieWired/GhidraMCP/blob/27f316f80139e2d5dec882519a1bdf4aa46ac04c/LICENSE),
|
||||
[bethington package license](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/pyproject.toml#L1-L9)).
|
||||
The bethington repository explicitly retains attribution to LaurieWired for
|
||||
portions that remain
|
||||
([NOTICE](https://github.com/bethington/ghidra-mcp/blob/8cd2078e10b9ba28b188cb84ce5b9051a904b995/NOTICE#L1-L10)).
|
||||
|
||||
Concepts, public Ghidra API usage patterns, and independently designed schemas
|
||||
may be studied without making `ghidra-cli` a derivative. If any source or close
|
||||
adaptation is copied, Apache-2.0 requires preserving applicable copyright,
|
||||
license, change, and NOTICE obligations. The cleanest course is to keep the
|
||||
implementation independent, cite these projects as prior art in research and
|
||||
design notes, and record provenance for any deliberately reused code before it
|
||||
enters a commit.
|
||||
|
||||
The open maintainability issue also alleges that some contributor changes were
|
||||
recommitted in ways that obscured authorship. This report does not adjudicate
|
||||
that allegation, but it reinforces the accepted `ghidra-cli` rule: AI assistance
|
||||
does not replace review, attribution, or a traceable change history
|
||||
([issue #307](https://github.com/bethington/ghidra-mcp/issues/307)).
|
||||
|
||||
## Decision impact
|
||||
|
||||
No accepted version 0.1 product decision should be removed. The research adds
|
||||
three concrete implementation-time checks and a clearer post-0.1 order:
|
||||
|
||||
- internal request-size ceiling;
|
||||
- operation/Java/schema parity test;
|
||||
- relationship-query order: `strings`, `xrefs`, direct `calls`, then targeted
|
||||
disassembly and Function detail.
|
||||
|
||||
Everything else belongs either in a future thin MCP adapter or in a separately
|
||||
designed interactive/mutating product.
|
||||
|
||||
The PRD now records the request ceiling, operation/dispatch/schema parity,
|
||||
no-default-egress regression coverage, ordered relationship Queries, bounded
|
||||
batch prerequisites, and thin-adapter MCP boundary. These adoptions do not add
|
||||
a public version 0.1 command or widen the Java adapter's authority.
|
||||
Loading…
Add table
Add a link
Reference in a new issue