Skip to main content
The warden command is a thin shell over the library. Every command takes --db <path> to select the project database (default warden.db, or the WARDEN_DB environment variable).
Run warden --help, or warden <command> --help, for the authoritative, version-specific flag list. This page documents the commands as of the current release.

Project lifecycle

Print the installed WARDEN version.
Create a project: a .warden/ directory with the knowledge base inside it (.warden/warden.db). Every command run from anywhere in that directory tree then finds and uses that database automatically, so you never pass --db.
Manage settings without juggling environment variables. There are two layers: a global file at ~/.config/warden/config.json (your defaults) and a per-project file at <project>/.warden/config.json (overrides for one project). Resolution is project over global, and the resolved keys and models are applied for you, so the backends just work.
Known keys: openrouter_api_key, openai_api_key, anthropic_api_key, openrouter_model, openai_model, agent_model. API key values are masked in get and list.
Run the entire pipeline end-to-end on generated sample modules. No network or toolchain needed. The fastest way to see what WARDEN does.

Ingest and inspect

Parse, fingerprint, and load a module version into the KB. Seeds names from the name section, exports, and imports.
  • <wasm>: path to the .wasm file (required).
  • --glue, -g: optional Emscripten .js glue (recovers version, dynCall sigs, pthread shape).
  • --label, -l: version label (defaults to the filename stem).
List all ingested module versions.
Show symbol-coverage statistics for a version (named / by oracle / by human / by agent).
List functions for a version with their current names and provenance.
  • --unnamed: show only functions that still have no name.
Show everything known about a single function: type, stable id, calls, provenance, and evidence.
Set a human-verified name for a function. Human names are sovereign and locked against agent overwrite by default.

The Oracle

Build (or extend) an Oracle signature store from labeled modules (modules that still carry a name section).
  • <wasm...>: one or more labeled .wasm files.
  • --out, -o: signature store path (default oracle.json).
  • --emver: the Emscripten version these were built with (recorded on each signature).
  • --opt: the opt level, e.g. -O2.
Identify runtime/libc functions in a version against a signature store, and infer the Emscripten version from the distribution of matches.
  • --indexed: build a MinHash-LSH index over the store before matching, giving sublinear candidate lookup for large signature stores. Produces the same results as the default linear scan at any threshold above 0.82, but is significantly faster when the store exceeds a few thousand signatures.

Decompile and execute

Decompile a single function (or every function in a module) to readable pseudo-C using the built-in pure-Python stack-machine lifter.
  • <label>: the version to lift (required).
  • --index, -i: lift only function at index N. Omit to lift the entire module.
  • --out, -o: write output to a file instead of stdout.
The same lifter backs warden export --format pseudo; that command now emits real pseudo-C rather than a mnemonic dump.Example output for parse_token:
Execute a single function in the built-in mini interpreter and print the return values. Covers the integer instruction subset; no external toolchain is required.
  • <label>: the version containing the function (required).
  • <index>: function index (required).
  • [args...]: zero or more integer arguments passed to the function.
Raises UnsupportedExecution when an instruction outside the supported subset is encountered. For behavioral comparison across versions use warden diff or call warden.interp.differential_execute from the Python API directly.
Run the concurrency and struct-layout analyzers over a version and persist their findings as KB facts. This populates the thread_model and structs tables that were previously empty.
The command runs both analyzers in sequence:
  • Concurrency analyzer: detects shared-memory access patterns, atomic sites, and pthread markers; writes facts via kb.add_thread_fact.
  • Struct analyzer: recovers struct layouts (name, field offsets, sizes, and inferred types) from access patterns; writes records via kb.upsert_struct.
Results are visible in warden show and in the HTML report produced by warden report.
Generate a self-contained HTML reverse-engineering report for a version. The file has inline CSS and requires no server to view.
  • --out, -o: output path (default <label>-report.html).
The report includes:
  • A coverage summary (named / oracle / human / agent / unnamed).
  • A confidence heatmap: every function colored by provenance and confidence score.
  • A thread and memory model section (populated after warden analyze).
  • The diff changelog from all prior warden diff runs against this version.

Agents, diff, verify, export

Run one propose → verify → write-back sweep over unnamed functions. Auto-selects cheapest-first: OpenRouter (Kimi K2.6) when OPENROUTER_API_KEY is set, then OpenAI, then Anthropic, otherwise the deterministic offline backend.
--strategy controls how functions are ordered and processed (default: call-graph).call-graph (default): walks the call graph bottom-up.
  1. Builds the intra-module call graph. Direct calls are exact. Indirect calls (call_indirect / dynCall) are over-approximated to table targets of the matching type, so the graph is a static skeleton.
  2. Condenses strongly-connected components (mutual recursion) and sorts them into bottom-up layers. Every function in a layer has all of its defined callees in earlier layers.
  3. Runs the concurrency and struct analyzers first and routes their findings into per-function notes (atomic sites, struct layouts).
  4. Processes layers bottom-up. Each function is named with its callees’ recovered names already in view, carried in FunctionFacts.callee_names and FunctionFacts.notes. This is the main quality win over a flat pass.
  5. Functions in the same layer are independent and are proposed concurrently via asyncio (blocking LLM backends run in worker threads, capped by --concurrency, default 8). Writes still go through the provenance and confidence economy, so concurrent branches that share a callee cannot clobber each other.
flat: the original single-pass, leaves-first ordering. Available for comparison or when call-graph analysis is not needed.
The per-function deep engine: one agent per function, walked bottom-up over the call graph (leaves first). Each function’s agent is fed its disassembly facts, its decompiled C, and the recovered understanding of the functions it calls (read from the knowledge base, not the callees’ raw bytes). It returns a name, a one-paragraph understanding, a variable rename map, the cleaned C, and whether the function is now closed.
  • Closed functions are reused, not re-analyzed, and identical functions (same stable identity) are analyzed once. This is what makes a module with thousands of functions tractable.
  • Results go to the function_analysis table and the symbol economy; every rename is logged for reversibility; progress is written to agent_events and, with --watch, streamed to the terminal.
  • Tier the models: a cheap --backend kimi for the many leaf functions and a stronger --parent-model for the harder, high-fan-in parents.
  • Runs on the deterministic offline backend with no API key (a dry run of the whole flow). Set OPENROUTER_API_KEY and --backend kimi to make the agents real. Browse the inferred C with warden serve.
List agent backends, availability, required credentials, and default models. Use this before a provider-backed run to confirm what auto-selection will see.
Diff two versions, carry annotations forward, and print the semantic changelog.
  • --no-carry: classify only; do not port annotations forward.
Export a deliverable. All formats are deterministic so they diff cleanly in git.
The csv and json formats complete the round-trip: export, edit externally, then import back with warden import.
Import annotations from a previously exported CSV or JSON file and merge them into the KB. This is the return leg of the round-trip that begins with warden export --format csv|json.
  • <label>: the version to import into (required).
  • <file>: the CSV or JSON file produced by warden export or edited by hand (required).
  • --format: csv (default) or json. Inferred from the file extension when omitted.
  • --provenance: override the provenance tag for every imported row (e.g. human). By default the provenance stored in the file is preserved.
  • --lock: mark each imported symbol as locked, preventing future agent overwrites.
How matching works. The importer keys on the stable function identity first. A name recovered against one build lands on the same logical function in another build, even when the function index shifts between releases. The function index is used as a fallback when no stable identity match exists.Provenance economy. Every imported row passes through the same provenance and confidence economy as all other writes. An import never clobbers a higher-authority annotation (for example, a human-locked name). The returned result reports matched, written, rejected_by_economy, skipped, and unmatched counts so you can audit what changed.Example: editing in Ghidra and importing back.
Verify ingestion determinism and report differential-equivalence readiness (whether a wasm2c/w2c2 + C toolchain is present).
Serve the knowledge base over the Model Context Protocol (stdio). Requires pip install -e '.[mcp]'. See the MCP reference.

A full session

Last modified on June 10, 2026