docs: define initial ghidra-cli product

This commit is contained in:
hermes 2026-07-28 12:01:28 +00:00
commit 57597e7d6d
4 changed files with 454 additions and 0 deletions

368
PRD.md Normal file
View file

@ -0,0 +1,368 @@
# Product Requirements Document
## ghidra-cli
- Status: Draft
- Revision: 0.1
- Audience: maintainers and early users
## Summary
`ghidra-cli` is a small Rust command-line interface for reproducible, read-only
analysis and decompilation through Ghidra. It hides Ghidra project management
and Java process details behind commands that accept a sample path directly.
The first release favors a narrow, reliable workflow over a broad command
surface. It is intended to work equally well for a person at a terminal and an
automation agent consuming structured output.
## Problem
Ghidra supports headless analysis, but its native interface exposes concepts
such as project directories, project names, import and process modes, script
paths, and JVM configuration. Answering a simple question about a sample
therefore requires orchestration knowledge unrelated to reverse engineering.
Existing wrappers commonly address this by becoming persistent services with
background JVMs, sockets, job queues, project lifecycle commands, custom query
languages, and mutating operations. Those capabilities are useful in mature
interactive systems, but they increase the number of states that can fail and
make a small tool harder to trust.
## Product principles
1. A sample path is enough. Users do not manage Ghidra projects.
2. Read-only by default and throughout the first release.
3. Content determines identity. Moving or renaming a sample does not invalidate
compatible analysis.
4. Structured output is a public interface, not formatted log text.
5. Every failure is explicit, typed, and actionable.
6. No background process is required for the initial command set.
7. Concurrency is introduced only with a concrete concurrent workflow.
8. Ghidra remains the analysis engine; Rust owns orchestration and policy.
## Users
### Human analyst
Wants to inspect an unfamiliar executable, discover functions, and decompile a
selected function without opening the Ghidra GUI or learning its project model.
### Automation agent
Needs deterministic commands, bounded output, stable JSON schemas, meaningful
exit codes, and enough provenance to explain where a result came from.
### Maintainer
Needs a small dependency graph, reproducible fixtures, a clear Java boundary,
and compatibility failures that are discovered by tests rather than users.
## Goals for the first release
- Diagnose whether the local Ghidra and JDK installation is usable.
- Inspect basic sample and analysis metadata.
- List discovered functions with stable identifiers.
- Decompile one selected function.
- Reuse compatible analysis without requiring user-managed project names.
- Emit concise terminal output and versioned JSON.
- Bound Ghidra execution by configurable time and memory limits.
- Operate without network access after dependencies are installed.
## Non-goals for the first release
- Replacing Ghidra's decompiler or analysis engine.
- A daemon, TCP bridge, HTTP service, or MCP server.
- Multiple concurrent Ghidra analyses.
- An interactive shell.
- Binary patching or exporting modified executables.
- Renaming symbols, applying types, or changing function signatures.
- Arbitrary Java or Python script execution.
- A custom filtering or query language.
- Automatically downloading Ghidra or a JDK.
- Managing user-visible Ghidra projects.
- Supporting every historical Ghidra version.
## Command-line experience
### Installation diagnosis
```console
$ ghidra-cli doctor
Ghidra 12.1: ready
JDK 21: ready
Analysis store: writable
```
`doctor` must not modify a Sample or create an Analysis. It may create and
remove a temporary file inside the Analysis Store to verify writability.
### Inspect a sample
```console
$ ghidra-cli inspect ./sample
Sample: 8d969eef...
Format: ELF
Language: x86:LE:64
Functions: 42
```
If no compatible Analysis exists, the command creates one automatically. The
same bytes at another path reuse the Analysis. Different bytes at the same path
create a different Analysis.
### List functions
```console
$ ghidra-cli functions ./sample
ADDRESS SIZE NAME
0x00401000 112 _start
0x00401120 248 main
```
The command supports an explicit result limit. It does not implement a
filtering expression language in the first release; JSON consumers can use
existing tools such as `jq`.
### Decompile a function
```console
$ ghidra-cli decompile ./sample main
int main(int argc, char **argv)
{
...
}
```
A function may be selected by address or a uniquely resolved symbol. An
ambiguous symbol is an error and returns the candidates instead of silently
choosing one.
### Structured output
```console
$ ghidra-cli --format json inspect ./sample
```
JSON uses a versioned envelope:
```json
{
"schema_version": 1,
"kind": "inspection",
"provenance": {
"sample_sha256": "<digest>",
"ghidra_version": "<version>",
"analysis_profile": "<digest>"
},
"data": {},
"warnings": []
}
```
Standard output contains only the requested result. Diagnostics and progress
belong on standard error.
## Functional requirements
### Sample identity
- Compute a SHA-256 digest from the Sample bytes.
- Detect if the Sample changes while it is being read.
- Treat identical bytes as the same Sample regardless of path.
- Never write to the Sample.
- Record the observed path as provenance, not identity.
### Analysis compatibility
An Analysis is reusable only when its Sample digest and Analysis Profile match.
At minimum, the Analysis Profile includes:
- Ghidra version.
- Ghidra language and compiler specification.
- Enabled analyzer configuration.
- Java adapter protocol version.
An incompatible Analysis is preserved until normal retention policy removes it;
it is not overwritten in place.
### Analysis execution
- Invoke the official Ghidra headless launcher as a child process.
- Pass requests to a small, bundled Java adapter.
- Exchange structured request and response files rather than scrape logs.
- Capture Ghidra logs separately for diagnostics.
- Terminate the child process after each command in the first release.
- Apply a configurable wall-clock timeout.
- Report whether termination was graceful or forced.
### Analysis Store
- Use a platform-appropriate user data location by default.
- Allow an explicit store path through one documented option.
- Use atomic writes for manifests and Artifacts.
- Detect incomplete Analysis creation after interruption.
- Serialize writers for the same Sample and Analysis Profile.
- Treat the entire store as disposable.
### Output behavior
- Default to concise human-readable output on a terminal.
- Support explicit JSON output.
- Never change JSON shape based on terminal detection.
- Include schema version and provenance in every JSON response.
- Use hexadecimal addresses with an explicit address space where necessary.
- Place progress and logs on standard error.
### Errors and exit status
Distinct error categories must cover:
- Invalid arguments.
- Missing or unreadable Sample.
- Unsupported or ambiguous format/language.
- Missing or incompatible Ghidra/JDK installation.
- Analysis timeout.
- Ghidra analysis failure.
- Function not found or function selector ambiguous.
- Invalid or incompatible cached Analysis.
- Internal protocol violation.
The exact numeric exit-code allocation is a design task before implementation.
## Architecture boundary
### Rust CLI responsibilities
- Argument parsing and validation.
- Sample hashing and stable identity.
- Analysis Profile construction.
- Analysis Store layout, locking, and atomicity.
- Child-process lifecycle, timeouts, and log capture.
- Typed request and response schemas.
- Human and JSON rendering.
- Exit-code policy.
### Java adapter responsibilities
- Use supported Ghidra APIs.
- Import and analyze a Sample.
- Resolve Function Selectors.
- Extract program and function metadata.
- Invoke the Ghidra decompiler.
- Serialize only the response defined by the protocol.
The Java adapter must not listen on a network socket, download dependencies, or
execute user-provided scripts.
## Synchronous execution decision
The first release uses a synchronous command lifecycle because each invocation
coordinates one Ghidra child process and returns one result. Ghidra performs the
CPU-intensive work; an asynchronous Rust runtime would not make that work
faster.
The design must not prevent a future asynchronous frontend. A server, parallel
batch analysis, interactive session, or responsive cancellation channel would
constitute a concrete reason to add an async runtime.
## Security and safety
- Treat every Sample and every Ghidra-produced value as untrusted input.
- Do not execute the Sample.
- Do not provide arbitrary script execution.
- Do not require network access during analysis.
- Recommend a sandbox or container for untrusted Samples.
- Avoid including Sample bytes or extracted secrets in routine logs.
- Validate all paths crossing the Rust/Java boundary.
- Apply bounded output, memory, and execution-time policies.
- Document the supported Ghidra version and relevant security advisories.
## Performance expectations
Correctness and reproducibility take priority over startup latency in the first
release.
- A compatible cached Analysis should avoid repeating auto-analysis.
- Metadata Queries should reuse exported Artifacts where correctness permits.
- Targeted decompilation may start a new JVM.
- The CLI should explain whether it reused or created an Analysis when verbose
diagnostics are enabled.
Persistent workers and interactive sessions are deferred until measured usage
shows JVM startup to be the dominant usability problem.
## Test strategy
- Unit-test Sample identity, profile identity, store layout, schema validation,
selector parsing, and error mapping without Ghidra.
- Test process lifecycle against a controllable fake child process.
- Maintain tiny, source-controlled fixture programs with known properties.
- Compile fixtures reproducibly for supported architectures.
- Run integration tests against a pinned supported Ghidra release.
- Compare JSON against reviewed golden fixtures after normalizing unstable
values.
- Verify interruption leaves no Analysis that can be mistaken for complete.
- Verify identical bytes at different paths reuse one compatible Analysis.
- Verify changed bytes at the same path do not reuse the old Analysis.
## Delivery milestones
### Milestone 0: design
- Review this PRD and the domain language.
- Choose the project and executable name.
- Choose a license.
- Pin the initially supported Ghidra and JDK versions.
- Define protocol schemas and exit codes.
### Milestone 1: vertical inspection slice
- Create the Rust crate and minimal Java adapter.
- Implement `doctor`.
- Implement `inspect`.
- Prove Rust to `analyzeHeadless` to Java to JSON end to end.
### Milestone 2: function discovery
- Implement `functions`.
- Define stable address and Function Selector representations.
- Add reproducible native fixtures.
### Milestone 3: targeted decompilation
- Implement `decompile`.
- Add ambiguity handling and decompiler time limits.
- Stabilize the first public JSON schema.
### Milestone 4: packaging
- Provide a reproducible Nix development and test environment.
- Document installation and sandboxed use.
- Publish the first pre-release.
## Release acceptance criteria
The first release is acceptable when:
- A new user can run `doctor`, `inspect`, `functions`, and `decompile` without
manually creating a Ghidra project.
- Repeating a Query against an unchanged Sample reuses compatible analysis.
- All commands provide versioned JSON and documented exit behavior.
- An interrupted analysis cannot be mistaken for a complete one.
- Tests exercise a real pinned Ghidra release and known fixture binaries.
- No first-release command mutates the Sample or Analysis.
- No analysis command requires a network connection.
## Open decisions
- Final repository and executable name.
- License.
- Initially supported Ghidra patch release.
- Whether the first supported host is Linux-only.
- Default Analysis Store retention policy.
- Numeric exit-code allocation.
- JSON Schema publication and compatibility policy.
- Whether Nix is the sole supported installation path for the first
pre-release.