Repository navigation
Releases: JoeyZzZzZz/treasure-map
Release list
tmap 0.1.1
Still a developer preview. Several MCP tool parameters and result fields changed (see Changed);
an agent prompt or script written against 0.1.0 may need adjusting.
Upgrading from 0.1.0
- Run
tmap rescanonce. A run scanned by 0.1.0 records no code stamp, so every run is offered.
The extraction pass is the same as 0.1.0's, so no binary is decompiled again (only binaries
whose last extraction failed are retried): the rescan rebuilds the inventory, cross-binary
references and non-binary facts, and hunts the run again. - Run
tmap diff --before A --after Bagain for each pair you diffed. The diff code changed, so
every stored diff reads as out of date (source_stale) until it is re-run. - Restart your MCP client (or reconnect the
treasure-mapserver) so it loads the new tool list. analysis.dband the atlas gain their new tables and columns automatically on first open.
Added
start_diff_review(diff_id): one call that opens a diff review — the entries to read
first, the change at the top shown inline, the diff's metadata and its non-binary files, and
the review workflow — sized to fit one response.- The changed lines, inline. A function pair's
pseudocode_deltacarriesinline_hunk: the
changed blocks as each side's original text writes them, with substitutions (a moved number, a
renamed identifier) grouped one per line. The comparisons a change introduces (a variable
checked against a constant) are listed beside it asguards. - More reasons to read a pair first: a sink call that changes in the callee set but not at
the matched instruction (sink_callee_swap); an undetermined pair that shares an address or
real name across the two sides and reads differently (anchored_differs); a function that
loses every direct caller (callers_a/callers_b, bucket E). same_shape_siblings: the other calls of one sink call's shape in the same binary, and
where each landed in the diff. A judgement that names such a group is flagged for re-checking
when the group changes.- Non-binary files in a diff.
get_file_diffcompares two runs' scripts, config files,
credentials and web assets by path (credential values and embedded secrets are never
returned), andannotate_diff(..., run_pair=...)records a judgement on one file. annotate_diff_batch: up to 200 judgements on one diff in a single call, all or none.
no_real_changenow has to point at something it checked in the current view.get_data_tableand entry words: where a function's entry address is stored as data (a
dispatch table, a callback slot), and a reader for the table around it.get_tool_reference: a tool's full description, the working guide, and the parameter-name
table.get_pseudocodesays why a function has no pseudocode (skipped_micro,decompile_empty,
size_unknown); a function too small to decompile comes with its disassembly inline.- Scripts that start a binary indirectly are recorded as its entry evidence: through a
function's positional parameter (run_it svc), a variable holding a command ($DAEMON,
$BASE/$NAME), or a command substitution (X=$(svc ...)). Each recorded script line says
where its command came from (via) and which line that was (via_line).
get_script_callsitespages its results (tmap fact script-callsites --offset). - An interrupted scan keeps its finished binaries. Each binary is stored as soon as its
decompile finishes; running the same command again (scan, analyze or rescan) decompiles only
the rest. An interrupted bare--reanalyzecontinues where it stopped the next time it is run. tmap rescanandtmap runsjudge a run by the code that produced it (a hash of the scan
and hunt code, plus the parsing library version and the content-affecting config), not by the
tmap commit: an update that does not touch the scan or the hunt leaves every run current. Runs
that need work are grouped by what it costs — a full scan, re-extraction of the binaries the
running pass has not yet extracted, or a re-hunt with no decompile.tmap diffretries a binary whose toolchain step failed transiently once more in the same
run, after the parallel phase, and reports it underrecovered.
Changed
- MCP tool descriptions are short cards (the first line names the required parameters); the
full text is oneget_tool_referencecall away. What the server sends at startup drops from
about 95,000 to 23,000 characters. An unknown parameter name is now refused instead of
being silently ignored. get_diff_patch_viewreads in significance order by default (the entries a reviewer
should open first lead); the previous default issort_by="candidate_tier". Pages are cut to
fit the response budget, an entry carries at most 20 candidate rows inline (the rest paged by
rows_offset),entry_idreads one entry in full, anddetail="summary"gives a whole
ranking as counts.- A change that only shifts
__FILE__/__LINE__build-info numbers is moved out of the
read-first list, but only when the two sides' callee sets and sizes are proven equal. - Every successful diff stores its candidate baseline, so reading a diff no longer recomputes
the overlay. - A promoted judgement records which kind of diff row it was carried from.
- Sink arguments are read by their form (a literal, a variable, an address, a cast, a field
read, a call), so a type name, the first word of a string or a callee name is no longer taken
for the variable a value flows from; the safe-value downweights are stricter about aliases. - Script lines are split the way the shell splits them, quotes respected: a quoted value with
spaces is one word, never a command. The command text recorded for a script line no longer
carries those quotes. - A binary whose extraction finished, that has executable code, and from which Ghidra recovered
no function is recorded asno_functions: still listed as incomplete, named on its own line,
and not decompiled again by the same extraction pass. - Interactive
tmap initdownloads missing tools without asking about each one and shows a
short numbered plan;--dry-runstill prints the full plan with URLs and checksums. - The agent prompts printed by
tmap initand shown in the README ask for the whole firmware
pair, allow an inconclusive judgement, and end with a report.
Fixed
tmap uninstallno longer leaves an empty dsh patch file that stops dsh from starting.- A transient toolchain failure in
tmap diffno longer needs a second run. - Downloads under 1 MB show their size in KB instead of
0 / 0 MB. tmap initsays that tab-completion takes effect in a new shell.tmap uninstallremoves the backup and the emptied config it left in Cursor and Claude
Desktop, putting back the user's original file byte for byte.- Interrupting a scan no longer discards every binary already decompiled in it.
- A run that has one binary the current extraction pass cannot extract is no longer listed as
needing re-extraction forever;tmap runs,tmap rescanandtmap initname such binaries
instead. - A binary cut off by Ctrl-C (or SIGTERM / SIGHUP) is no longer recorded as failed.
- A timeout or import failure logged by an earlier Ghidra run of the same binary is no longer
read as this run's. tmap rescanno longer changes which runtmap mcpserves by default.- An interrupted
tmap diffcounts as new only the binaries it actually diffed.
tmap 0.1.0
First public release of tmap — a developer preview. It maps an extracted firmware filesystem into a fact base an AI agent can work through over MCP: every place a binary runs a shell command, copies into a buffer, formats a string or opens a file, annotated with what is and is not known about it — plus a patch diff between two firmware versions.
Commands, configuration, the atlas database and MCP tool fields may still change incompatibly between versions.
Install
curl -LsSf https://github.com/JoeyZzZzZz/treasure-map/releases/download/v0.1.0/install.sh | shThis installs tmap (PyPI: tmap-agents) with a uv-managed Python 3.11, then starts tmap init. Without the script: uv tool install --python 3.11 tmap-agents==0.1.0, then tmap init.
tmap init reuses a JDK 21, Ghidra 11.4.3, BinExport and BinDiff 8 it finds, and downloads what is missing (about 730 MB) from pinned URLs, each checked by size and sha256. It then registers the MCP server with the agents you pick: Claude Code, Codex, Cursor, Claude Desktop or dsh.
- Already have the tools somewhere tmap does not look? Pass
--ghidra,--java-home,--bindiff. - Behind a proxy, set
HTTPS_PROXY. Mirrored or offline:--mirror BASEor--offline DIR(the pinned archives by file name). tmap doctorre-checks the setup at any time and writes nothing.
What's in it
- Firmware map —
tmap scan. Decompiles every ELF in an extracted filesystem with Ghidra (headless, in parallel; a binary that fails gets one serial retry at a doubled budget) and records functions, pseudocode, disassembly, callees, cross-binary references, strings, imports/exports, data bytes, and the shell scripts, config files and web assets around them. Every call that runs a shell command, copies into a buffer, formats a string or opens a file by path becomes a candidate in the shared atlas. - Candidates annotated layer by layer, with no score. Each one carries a YES / NO / UNKNOWN reading — with the reason — on where its data comes from, how controllable it is, who wrote the value, how the spot is reached, whether anything filters it, its impact, and whether the function was fully analyzed. The default view ranks by impact and only sinks candidates proven safe; an UNKNOWN never pushes one down. Command, copy and format-string sinks carry argument provenance from Ghidra's def-use data.
- Patch diff —
tmap diff --before RUN --after RUN. Runs Ghidra, BinExport and BinDiff over every binary whose content changed and gives a function-level patch view, grouped by what changed around each function's dangerous calls, with an address-normalized pseudocode comparison. A binary that could not be diffed is listed as a blind spot, never counted as unchanged; a function without a counterpart is reported as undetermined, never as added or removed. - MCP server —
tmap mcp, 34 tools. The candidate map, fact readers, cross-firmware and nvram views, the diff views, and an annotation layer (annotate,annotate_diff,promote, …) that records the agent's judgements without changing the facts. Every result carries an anchor (binary + function + address) and the lineage of the scan that answered; a scan made by different code than the one answering is refused, with the command that refreshes it. - Command-line readers for a person checking the agent's work:
tmap runs,tmap triage,tmap fact,tmap atlas-view. - Lifecycle:
tmap updateupgrades a uv-managed install;tmap uninstallremoves only what tmap installed (--purge-dataalso deletes scans and the atlas, after you type a confirmation phrase).
Platforms
| Platform | Status |
|---|---|
| Linux x86_64 (WSL2 included) | supported |
| macOS on Apple Silicon | supported |
| macOS on Intel | runs the same toolchain; not acceptance-tested |
| Linux arm64 | not supported (no Ghidra decompiler or BinDiff build for it) |
| Windows | use WSL2; native Windows is not supported |
Known limitations
- Reachability is graded one function at a time. It is not a taint-propagation engine and never claims a path is unreachable, so most candidates read UNKNOWN on that layer.
- Path and file sinks are detected and ranked, but their controllability comes from a text-level reading of the pseudocode, and is marked as such.
Known issues in 0.1.0
Found after release; all are fixed on main and will ship in the next version.
tmap uninstallcan stop dsh from starting. Whentmap initcreated dsh's home patch file (~/.dsh/cordis.patch.yml, or the same file under$DSH_HOME) just for tmap's entry, uninstalling leaves it empty — and dsh refuses a patch file that exists but is not a YAML list. Workaround: delete the file if it is empty (no file means no patches), or set its content to[].- A one-off toolchain failure in
tmap diffneeds a second run. A binary that fails with a transient error — for exampleBinExport produced no file … although the process exited 0— is recorded as a blind spot and retried on the nexttmap diff, not within the same run. Workaround: run the sametmap diffagain; the binary is retried and reported underrecovered. - Small downloads read
0 / 0 MB. The BinExport archive (about 306 KB) shows100% (0 / 0 MB)duringtmap init; it is downloaded in full and checked. Cosmetic. - Tab-completion needs a new shell. Completion installed by
tmap initworks in a new terminal (or afterexec $SHELL -l), not in the shell that ran init.
Full details: CHANGELOG.md. Licensed under the Apache License 2.0.