https://github.com/biniamf/ai-reverse-engineering/
1.6K
Local headless Ghidra REST API — analysis, decompilation, xrefs, strings, and deterministic attack-surface scoring over HTTP.
A local, single-user REST service that wraps Ghidra analyzeHeadless. Upload a
binary, and query functions, decompilation, xrefs, imports, strings, types,
globals, a bounded call graph, and a deterministic Attack Surface triage
index — over HTTP, with no GUI. Usable directly, or as the backend for the
Rev·Deck AI reverse-engineering WebUI.
Uploaded binaries are analyzed, never executed.
Source / WebUI: https://github.com/biniamf/ai-reverse-engineering/
docker run --rm -p 9090:9090 \
-v "$(pwd)/data:/data/ghidra_projects" \
biniamfd/ghidra-headless-rest:latest
Recommended for local use (loopback bind + hardening):
docker run --rm -p 127.0.0.1:9090:9090 \
-v "$(pwd)/data:/data/ghidra_projects" \
--security-opt no-new-privileges:true \
biniamfd/ghidra-headless-rest:latest
Reproducible pin (immutable digest):
docker run --rm -p 127.0.0.1:9090:9090 \
-v "$(pwd)/data:/data/ghidra_projects" \
biniamfd/ghidra-headless-rest:1.2.1@sha256:971591a3a8448d8ed969079b452306e806f36079c3ddd298f4a618d6e2f1442d
http://localhost:90900.0.0.0:9090./data/ghidra_projects.ghidra-11.3.2 · artifact schema 2.1.| Tag | Meaning |
|---|---|
latest | Current stable (= 1.2.1) |
1.2.1 | Pinned current release |
1.0.0 | Previous stable (rollback) |
0.2-legacy | Pre-1.0 build (rollback) |
GET /v1/health → { "status": "ok" }
GET /v1/version → service / API / artifact / analyzer versions
GET /v1/capabilities → advertised features + tool names
/v1/capabilities reports capabilities (service flags) and features
(client-facing flags: summary, types, globals, annotations, callgraph, multipart_upload, attack_surface) so clients can feature-detect.
POST JSON to /tools/<name>. These names, request fields, and response
container types are frozen for compatibility.
| Endpoint | Method | Description | Parameters | Returns |
|---|---|---|---|---|
/tools/analyze | POST | Upload a base64-encoded binary and start analysis. | file_b64 (string, required) filename (string, required) | job_id (string) status (string) |
/tools/status | POST | Get status for a job. | job_id (string, required) | job_id (string) status (string) – queued | running | done | failed | cancelled | interrupted |
/tools/list_functions | POST | List discovered functions. | job_id (string, required) offset/limit (int, optional) | functions (array) of { name, addr, size, signature, … } |
/tools/decompile_function | POST | Decompiled pseudocode for a function. | job_id (string, required) addr (string, required — hex, padded or unpadded) | Decompiled C text (text/plain) |
/tools/get_xrefs | POST | Callers and callees for a function. | job_id (string, required) addr (string, required) | addr (string) xrefs (object) – to / from |
/tools/list_imports | POST | Imported symbols/libraries. | job_id (string, required) | imports (array) of { name, address, library, … } |
/tools/list_strings | POST | Extracted printable strings. | job_id (string, required) min_length (int, optional) | count (int) strings (array) |
/tools/query_artifacts | POST | Substring/regex query over artifacts. | job_id (string, required) query (string, required) regex (bool, optional) | count (int) matches (array) |
Note:
statusreturnsdone(notcompleted). Direct legacy routesPOST /analyze,POST /analyze_b64,GET /jobs,GET /status/{job_id},GET /results/{job_id}/…, andPOST /queryalso remain available.
POST /v1/jobs multipart upload (field: file) ?persist=&force=
POST /v1/jobs/base64 { file_b64, filename, persist, force }
GET /v1/jobs paginated job list
GET /v1/jobs/{id} job metadata
POST /v1/jobs/{id}/cancel cancel a queued/running job
DELETE /v1/jobs/{id} delete a terminal job + its artifacts
Upload returns { job_id, status:"queued" }, or an immediate
{ status:"done", cache_hit:true, reused_job_id } when an identical binary was
already analyzed (SHA-256 dedup). Poll until a terminal state
(done | failed | cancelled | interrupted).
GET /v1/results/{id}/summary program summary + counts
GET /v1/results/{id}/functions?offset=&limit=&q= paginated, with global search
GET /v1/results/{id}/function/{addr}/decompile structured decompile { addr, status, content }
GET /v1/results/{id}/xrefs/{addr} normalized cross-references
GET /v1/results/{id}/graph/{addr}?depth=&limit= bounded call-graph neighborhood
GET /v1/results/{id}/imports?offset=&limit= grouped + flat imports
GET /v1/results/{id}/strings?offset=&limit=&min_length=
GET /v1/results/{id}/types?offset=&limit= structs/enums/unions/typedefs/…
GET /v1/results/{id}/globals?offset=&limit= global symbols
GET /v1/results/{id}/hexdump/{start}?length= bounded bytes from exported memory
POST /v1/query bounded substring/regex query
?q= on /functions matches by name, display name, or
address (padded/unpadded, e.g. 0x00401000 == 0x401000) before
pagination, so totals reflect the whole program, not one page./v1/jobs/{id}/summary, /v1/jobs/{id}/types, /v1/jobs/{id}/globals exist
as aliases of the corresponding /v1/results/… routes.GET /v1/jobs/{id}/annotations
PUT /v1/jobs/{id}/annotations # whole document (If-Match / ETag)
PATCH /v1/jobs/{id}/annotations # merge
PUT /v1/jobs/{id}/annotations/{addr} # single entity
Names/comments/tags/confidence are stored in a revisioned annotations.json
sidecar with ETag/If-Match optimistic concurrency (stale writes → 409).
The original Ghidra artifacts and project are never modified.
GET /v1/jobs/{id}/export → deterministic, path-safe ZIP (artifacts + manifest)
Every completed analysis produces a deterministic, explainable triage index
ranking functions by security relevance (attack surface, memory safety,
command/format/filesystem, crypto, indirect calls, Android/JNI native interop,
etc.), stored as security_index.json + an indexed SQLite security.db.
GET /v1/results/{id}/security/summary
GET /v1/results/{id}/security/functions?band=&category=&min_score=&q=&rank=&sort=&order=&offset=&limit=
GET /v1/results/{id}/security/functions/{addr} # ranked function + signal evidence
POST /v1/jobs/{id}/rescore # rebuild index (no Ghidra re-run)
critical | high | medium | low. Every score carries inspectable
signals with signed weights, confidence, and evidence references.rescore without re-running Ghidra;
a missing/stale/corrupt index returns a bounded, explicit contract.⚠️ The security score is a deterministic review-prioritization signal — not a vulnerability verdict, proof of exploitability, or an AI judgment. No LLM participates in scoring.
Common: DATA_DIR, MAX_UPLOAD_SIZE, MAX_CONCURRENT_ANALYSES,
ANALYSIS_TIMEOUT, DECOMPILE_TIMEOUT, RETENTION_SECONDS,
SECURITY_SCORER_ENABLED, SECURITY_SCORER_TIMEOUT, SECURITY_MAX_FUNCTIONS.
Local, single-user static analysis. Binaries are never executed. Bind to loopback for local use; public/multi-user hosting and authentication are out of scope. Runs non-root with a dedicated writable data volume.
This service is standalone — you can drive it directly from curl, scripts, or
your own tooling. One reference use case is the Rev·Deck AI assistant, where an
LLM agent calls the /tools/* and /v1/* endpoints (with
GHIDRA_API_BASE=http://localhost:9090) to analyze binaries conversationally.
The architecture below is one integration example, not a requirement for
using the API.
Content type
Image
Digest
sha256:971591a3a…
Size
1 GB
Last updated
2 months ago
docker pull biniamfd/ghidra-headless-rest