docs: define v0.1 product and architecture contract

This commit is contained in:
hermes 2026-07-28 18:21:19 +00:00
commit c4fb0c6a01
12 changed files with 2130 additions and 64 deletions

38
AGENTS.md Normal file
View file

@ -0,0 +1,38 @@
# AGENTS.md
## Read first
- Read `PRD.md`, `CONTEXT.md`, and applicable ADRs before changing behavior.
- Keep the Rust harness synchronous and the Ghidra worker
capability-sandboxed.
- Treat JSON schemas, error codes, ordering, and exit statuses as public API.
## Change rules
- Add a regression test for every bug fix.
- Never change golden files or schemas implicitly.
- Keep the Rust operation registry, Java dispatch, schemas, and goldens in
exact parity.
- Preserve strict stdout/stderr framing.
- Preserve the worker's no-network and no-default-data-egress boundary.
- Do not add `unsafe`, an async runtime, database, daemon, or dependency
without an accepted design change.
- Do not weaken bounds, validation, sandboxing, or atomic store behavior.
## Required checks
- `cargo fmt --check`
- `cargo clippy --all-targets --all-features -- -D warnings`
- `cargo test --all-targets`
- `cargo deny check`
- `nix flake check`
## Source control
- Use Jujutsu for normal local change management.
- Use `jj status`, `jj diff`, `jj log`, `jj describe`, and `jj new`.
- Push Forgejo bookmarks through `jj git push`.
- Keep changes focused and preserve unrelated user work.
- Use direct Git commands only for interop or recovery that Jujutsu cannot
perform.
- Never run `git clean -xdf`; it can delete colocated `.jj/` metadata.

View file

@ -12,32 +12,88 @@ names or locations.
_Avoid_: Binary, file, target _Avoid_: Binary, file, target
**Analysis Profile**: **Analysis Profile**:
The declared analysis inputs other than the Sample, including the Ghidra The canonical analysis inputs other than the Sample, including Ghidra and Java
version, analyzer configuration, language selection, and tool protocol version. versions, Target Specification, fully resolved loader and analyzer options,
analysis-adapter version, and maximum CPU count.
_Avoid_: Configuration, settings _Avoid_: Configuration, settings
**Target Specification**:
The loader, executable format, processor language, and compiler specification
used by Ghidra to interpret a Sample.
_Avoid_: Architecture, target
**Analysis**: **Analysis**:
The reusable body of facts Ghidra derives from one Sample under one Analysis The reusable body of facts Ghidra derives from one Sample under one Analysis
Profile. Profile.
_Avoid_: Project, database, workspace _Avoid_: Project, database, workspace
**Quarantined Analysis**:
Analysis data that failed integrity validation and is retained for diagnosis
but can never satisfy a Query.
_Avoid_: Analysis, cache miss
**Query**: **Query**:
A read-only question evaluated against an Analysis. A Query cannot change the A read-only question evaluated against an Analysis. A Query cannot change the
Sample or Analysis. Sample or Analysis.
_Avoid_: Command, job, operation _Avoid_: Command, job, operation
**Function**:
A Ghidra-discovered unit of executable behavior identified within an Analysis
by its entry Address.
_Avoid_: Symbol, method
**Function Selector**: **Function Selector**:
An unambiguous reference used to choose a function by address or resolved An unambiguous reference used to choose a function by address or resolved
symbol name. symbol name.
_Avoid_: Function name, function ID _Avoid_: Function name, function ID
**Call Edge**:
A directed relationship from a caller Function through a callsite Address to
a resolved or unresolved callee, produced by a future targeted Query.
_Avoid_: Call, dependency
**Reference**:
A Ghidra-derived relationship from one Address to another Address or external
symbol, produced by a future targeted Query.
_Avoid_: Pointer, cross-reference result
**Address**:
A location within an Analysis identified by an explicit Ghidra address space
and an offset whose width is defined by that space.
_Avoid_: Integer, offset, pointer
**Artifact**: **Artifact**:
A durable, schema-versioned representation of a Query result tied to its A durable, schema-versioned representation of a Query result tied to its
Analysis. Analysis.
_Avoid_: Output, report, result file _Avoid_: Output, report, result file
**Analysis Store**: **Diagnostic Log**:
The tool-owned collection of reusable Analyses and Artifacts. It is disposable A tool-owned record of execution diagnostics that may explain an Analysis or
and never contains the authoritative Sample. Query failure but is not part of the Query result.
_Avoid_: Project directory, cache directory, workspace _Avoid_: Artifact, output
**Invocation**:
One synchronous attempt by the Rust harness to run a bounded Ghidra/Java
operation and validate its response.
_Avoid_: Job, session, worker
**Worker Sandbox**:
The capability-limited environment containing only the untrusted Ghidra/JVM
child for one Invocation; the trusted Rust harness remains outside it.
_Avoid_: Analysis Store, containerized CLI
**Cleanup Transaction**:
Tool-owned staged data that has been removed from active store visibility and
is awaiting complete deletion or a recoverable retry.
_Avoid_: Analysis, quarantine
**Target Verification**:
The declared relationship between a Target Specification and the project's
integration-test matrix: either verified or unverified.
_Avoid_: Ghidra confidence, supported Sample
**Analysis Store**:
The tool-owned collection of reusable Analyses, Quarantined Analyses, Artifacts,
and Diagnostic Logs. It is disposable and never contains the authoritative
Sample.
_Avoid_: Project directory, cache directory, workspace

201
LICENSE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

1365
PRD.md

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
# ghidra-cli # ghidra-cli
A small, dependable command-line interface for read-only Ghidra analysis. A small, dependable command-line interface for read-only Ghidra analysis.
The repository is named `ghidra-cli`; the installed executable is `ghidr`.
The project is currently in its design phase. Its first objective is to make The project is currently in its design phase. Its first objective is to make
common reverse-engineering questions easy to ask without exposing Ghidra common reverse-engineering questions easy to ask without exposing Ghidra
@ -9,14 +10,15 @@ project management, JVM lifecycle, or scripting details to the user.
Proposed usage: Proposed usage:
```console ```console
ghidra-cli doctor ghidr doctor
ghidra-cli inspect ./sample ghidr inspect ./sample
ghidra-cli functions ./sample ghidr functions ./sample
ghidra-cli decompile ./sample main ghidr decompile ./sample --name main
``` ```
Each command will identify the sample by its content, reuse compatible cached Each command will identify the sample by its content, reuse compatible cached
analysis, and produce human-readable or versioned JSON output. analysis, and produce versioned JSON by default. Human-readable presentation is
available explicitly through `--format human`.
## Documents ## Documents
@ -24,6 +26,10 @@ analysis, and produce human-readable or versioned JSON output.
scope. scope.
- [CONTEXT.md](./CONTEXT.md) defines the domain language used throughout the - [CONTEXT.md](./CONTEXT.md) defines the domain language used throughout the
project. project.
- [docs/adr/](./docs/adr/) records durable architectural decisions.
- [docs/research/](./docs/research/) records cited prior-art investigations,
including the Ghidra MCP comparison that informed protocol safeguards and
the post-version-0.1 roadmap.
## Status ## Status
@ -32,5 +38,8 @@ begins after the initial requirements and interface are reviewed.
## License ## License
No license has been selected yet. Until one is added, all rights are reserved. Licensed under the [Apache License 2.0](./LICENSE).
Development may use AI assistance. Maintainers remain responsible for reviewing
the resulting design and code, verifying provenance, testing behavior, and
ensuring dependency-license compatibility.

View 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.

View 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.

View 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.

View 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.

View 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.

View 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.

View 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.