6.2

inspect-wasm-artifact and the WABT provider

rea 6.7.0 runs three WABT 1.0.42 programs on a private copy of the module and returns one observed Evidence record with sections, a typed interface, and decoded WAT.

You wrote capture/fixtures/wasm/ledger.wat by hand, with the same four functions as ledger.c, and assembled it to ledger.wasm with wat2wasm. You ask rea 6.7.0 to inspect the module, on the CLI and over MCP, and both entry points return the same Evidence record. When you finish this section, you can set up the WABT provider and read the record. You can also say what the record does not prove.

Setup

WebAssembly is not a native target. open_binary on a module returns target_unavailable with the constraint wasm_requires_static_inspection, and the error names the inspect_wasm_artifact call to make. The tool needs three WABT programs that you install yourself. Set REA_WABT_BIN_DIRECTORY to the absolute directory that holds wasm-validate, wasm-objdump, and wasm2wat. The tool is available only when that variable is absolute, and binary_session reports that availability.

the WABT block of the version recordtextFull log
REA_WABT_BIN_DIRECTORY=/opt/homebrew/bin
$ /opt/homebrew/bin/wasm-validate --version
1.0.42
[exit 0]
$ /opt/homebrew/bin/wasm-objdump --version
1.0.42
[exit 0]
$ /opt/homebrew/bin/wasm2wat --version
1.0.42
[exit 0]
The fourth program, wat2wasm, built the fixture. rea does not run it.

The command and the record

The CLI command is rea inspect-wasm-artifact PATH. In the capture it exited 0 after about two seconds and printed one Evidence record.

the CLI calltextFull log
$ rea inspect-wasm-artifact fixtures/wasm/ledger.wasm --format json
[exit 0] (… s) -> 16-inspect-wasm-artifact.json
The time is cut, because it changes with every recording.
the head of the inspect_wasm_artifact recordjsonFull log
{
  "evidence_id": "ev_3a09e4b0…",
  "subject": {
    "name": "ledger.wasm",
    "digest": {
      "sha256": "699f6ff7…"
    },
    "local_path": "$CAPTURE/fixtures/wasm/ledger.wasm",
    "format": "file",
    "architecture": null
  },
  "provider": {
    "id": "wabt-artifact",
    "name": "REA WABT artifact adapter",
    "version": "wabt@1.0.42"
  },
  "analysis_profile": null,
  "predicate_type": "rea.analysis",
  "operation": "inspect_wasm_artifact",
  "parameters": {
    "path": "$CAPTURE/fixtures/wasm/ledger.wasm",
    "glue_paths": [],
    "candidate_paths": []
  },
The ids and digests are cut to eight characters. The normalized_result follows.

The subject format is file and the architecture is null, because no native format applies. The provider version names the WABT release. The record is observed, with shipped-artifact as its authority, because the bytes on disk are the source. The two empty arrays are optional inputs. glue_paths names JavaScript files whose .wasm string literals rea links to candidate modules, and candidate_paths names those modules.

What rea ran

rea copies the module into a private workspace as a read-only module.wasm. It then runs three programs from the directory that REA_WABT_BIN_DIRECTORY names. Each program must print exactly 1.0.42 as its version. If one does not, rea does not run it. The tool_profile records what ran.

the tool profile, first commandjsonFull log
"tool_profile": {
  "source": "https://github.com/WebAssembly/wabt",
  "release": "1.0.42",
  …
  "commands": [
    {
      "tool": "wasm-validate",
      "path": "/opt/homebrew/Cellar/wabt/1.0.42_1/bin/wasm-validate",
      "sha256": "99a52c78…",
      "version_banner": "1.0.42\n",
      "arguments": [
        "--enable-all",
        "module.wasm"
      ]
    },
    …
  ],
  "timeout_ms": 30000,
  "output_budget_bytes": 33554432
},
The source commit, the digests, and the wasm-objdump and wasm2wat entries are cut. They have the arguments -h -x and --enable-all.

The limits are 8 MiB of input, 30 s for each program, and 32 MiB of output.

rea checks the digest of the copy before and after each program. It also checks the digest of each program again after the command. A digest identifies the bytes of a program. It does not prove who published the program.

Sections

The sections array holds the seven sections with their byte ranges and the count of entries in each.

the seven sections of ledger.wasmjsonFull log
"artifact": {
  "path": "$CAPTURE/fixtures/wasm/ledger.wasm",
  "sha256": "699f6ff7…",
  "bytes": 290
},
"validation": "valid",
"sections": [
  {
    "kind": "Type",
    "start": 10,
    "end": 26,
    "bytes": 16,
    "description": "count: 3"
  },
  {
    "kind": "Import",
    "start": 28,
    "end": 39,
    "bytes": 11,
    "description": "count: 1"
  },
  …
  {
    "kind": "Data",
    "start": 273,
    "end": 290,
    "bytes": 17,
    "description": "count: 1"
  }
],
The Function, Memory, Export, and Code entries are cut. They start at 41, 48, 53, and 113.

The file is 290 bytes. Eight bytes of magic and version come first. Each section starts with a one-byte id and a length, and the payload follows. The Type payload therefore starts at offset 0x0a, which is 10. The data segment holds the five grade labels at address 1024, two bytes apart.

The typed interface

The record holds the imports and exports two times. The imports and exports arrays keep the exact WAT forms. The interface entries add the direction, the index, and a resolved signature for each one.

two interface entriesjsonFull log
"interface": {
  "state": "complete",
  "entries": [
    {
      "direction": "import",
      "module": "env",
      "name": "log",
      "kind": "func",
      "index": 0,
      "signature": {
        "kind": "func",
        "params": [
          "i32"
        ],
        "results": []
      },
      "wat_form": "(import \"env\" \"log\" (func (;0;) (type 0)))",
      "issue": null
    },
    …
    {
      "direction": "export",
      "module": null,
      "name": "percent_change",
      "kind": "func",
      "index": 1,
      "signature": {
        "kind": "func",
        "params": [
          "i32",
          "i32"
        ],
        "results": [
          "i32"
        ]
      },
      "wat_form": "(export \"percent_change\" (func 1))",
      "issue": null
    },
    …
The memory export and three function exports are cut.

percent_change takes two i32 values and returns one, the same shape as the C function. The import env.log is function 0, so the four functions of the module are 1 to 4. A second tool, compare_wasm_interfaces, reads two of these records and reports each import and export as added, removed, changed, or unchanged. It reads no files and runs no WABT program.

The WAT

The wat field holds the text that wasm2wat printed, its SHA-256 37eab56a…, and the representation decoded-wat. The body of percent_change is the C function in stack form. It tests the first parameter for zero. It then pushes 0, or it computes the subtraction, the multiplication by 100, and the signed division.

percent_change in the text formtextFull log
  (func (;1;) (type 1) (param i32 i32) (result i32)
    local.get 0
    i32.eqz
    if (result i32)  ;; label = @1
      i32.const 0
    else
      local.get 1
      local.get 0
      i32.sub
      i32.const 100
      i32.mul
      local.get 0
      i32.div_s
    end)
Cut from normalized_result.wat.text. The other three function bodies are cut.

Two things in the text form echo Part 4. The grade body is a br_table with twelve targets, the WebAssembly form of the jump table that clang built for the same switch in ledger-x86_64-O1.o. The grade_label body calls function 2, then function 0, which is the imported env.log. It then adds twice the grade to 1024, the address of the data segment, two bytes per label. You can read that from the WAT without a decompiler, because the module has no stripped symbols and no relocations.

Limitations

The record has everything Part 5 described: an evidence_id, a confidence, an authority, and five limitations. The record does not decompile the functions. It gives the WAT text, and the reading of that text is your work.

the five limitations of the recordjsonFull log
"limitations": [
  "WAT is a decoded representation, not original source or proof of runtime behavior. Custom annotations may retain payloads, but WAT does not establish original binary layout or round-trip byte identity. Original bytes and objdump output remain authoritative.",
  "Imports and exports retain exact top-level WABT WAT forms. Names remain escaped producer text; unescaped objdump display names are not parsed as unambiguous identifiers.",
  "JavaScript references are AST-observed static literals only. Candidate matches are associations, not proven data flow, URL-to-file identity or runtime loading. Computed references and aliases are not resolved.",
  "No target execution or implicit fetch. WABT tools are trusted caller-supplied executables; SHA-256 identifies their bytes, not publisher authenticity. No OS memory sandbox is claimed.",
  "Each subprocess has a 30-second deadline and 32 MiB combined output budget. Oversized output fails instead of producing truncated success."
],
Nothing is cut.

The first limitation says that the WAT is a decoded representation. The WAT does not show the original source, and it does not prove runtime behaviour. The record is observed for the bytes and the tool output only. The fields runtime_execution and network_fetch both read not-performed, because rea never runs the module.

Over MCP

Over MCP the tool is inspect_wasm_artifact with the same path. Step 24 of the session returned ok after 404 ms. The reply is a tool result. Its structuredContent is the same Evidence record, with the same id ev_3a09e4b0…. Its _meta carries the untrusted-content marker of Section 8.1.

the head of the MCP resultjsonFull log
"result": {
  "_meta": {
    "provenance": {
      "untrusted_content": true,
      "source": "analysis_target"
    }
  },
  …
  "structuredContent": {
    "evidence_id": "ev_3a09e4b0…",
The text copy and the rest of the record are cut.

The session retains the record, so export_evidence_bundle writes it to a bundle with the other records. For a large module, pass detail: "summary" on the first call. rea then keeps the complete record. It returns counts, the validation result, and the WAT identity, without the WAT text. You read pages of the full record with inspect_analysis_view.

A cross-check with wabt

The capture also ran wasm-objdump -h -x on the module without rea, in 16-wabt.txt. The section table there has the same seven rows as the headers text in the record. Type starts at 0x0000000a, and Data ends at 0x00000122, which is 290. The offsets agree with sections. Figure 6.2 draws the path from the two entry points to the one record.

Fig. 6.2inspect_wasm_artifact on ledger.wasmflow
inspect_wasm_artifact on ledger.wasm Top row: two entry points, the CLI command rea inspect-wasm-artifact and the MCP call tools/call inspect_wasm_artifact. Both reach the wabt-artifact provider, which reads REA_WABT_BIN_DIRECTORY=/opt/homebrew/bin and copies ledger.wasm, 290 bytes, SHA-256 699f6ff7, to a read-only module.wasm in a private workspace. Third row: three WABT 1.0.42 programs run on the copy, wasm-validate --enable-all, wasm-objdump -h -x, and wasm2wat --enable-all. Bottom: one Evidence record ev_3a09e4b0, observed, with validation valid, seven sections, an interface of one import and five exports, the decoded WAT with SHA-256 37eab56a, a tool profile, and five limitations. runtime_execution and network_fetch are not-performed. The CLI prints the record, and MCP returns the same record as structuredContent with the untrusted-content marker. rea inspect-wasm-artifact CLI, exit 0 after 1.5 s tools/call inspect_wasm_artifact MCP step 24, ok after 404 ms wabt-artifact provider, REA_WABT_BIN_DIRECTORY=/opt/homebrew/bin copies ledger.wasm, 290 bytes, sha256 699f6ff7, to a read-only module.wasm path path wasm-validate --enable-all, banner 1.0.42 result: valid wasm-objdump -h -x, banner 1.0.42 result: 7 sections wasm2wat --enable-all, banner 1.0.42 result: decoded WAT Evidence ev_3a09e4b0, confidence observed sections: Type, Import, Function, Memory, Export, Code, Data interface: 1 import env.log, 5 exports with typed signatures wat: decoded-wat, sha256 37eab56a, tool_profile: 3 commands limitations: 5, runtime_execution and network_fetch not-performed The CLI prints this record. MCP returns the same id as structuredContent.
rea copies the module, runs three WABT programs with checked versions, and returns one observed record that the CLI and MCP share. Read top to bottom. Solid arrows are calls, and dashed arrows are the program outputs that rea parses. Grey boxes are the WABT programs outside rea.

rea (rea-agents) with the Ghidra provider rea-agents 6.7.0, Ghidra 12.1.4 · 27dc42d · 2026-10-18 · Edition 2026.10