Skip to content

Autopsy modules — the analysis pipeline internals

This page maps the module families inside artwork-autopsy so you can find where a given behaviour lives. It follows the code’s one hard seam: deterministic machinery never imports the LLM, and the LLM layer never touches the deterministic core except through ports. The top-level packages:

  • static/tools/ — deterministic extraction. Parses and identifies, never decides.
  • readers/ — the LLM-free deep-readers: per-language decompiler wrappers, the code-semantics engine, the universal content scan, and the network probe. Deterministic; no AI framework here.
  • ai/ — the LLM layer: the analyst loop, the grounded-judgment primitive, narration, and the verification passes. The only place the AI framework may be imported.
  • domain/ — framework-free pure logic: the run graph, the behaviour map, scoping, the pipeline phases and their orchestration engine, and the knowledge base the report is projected from.
  • runtime/ — the data-driven runtime catalog and its dispatcher: which runtime a work needs, read from runtime-catalog.yaml, not from code.
  • report/ — the report renderers: the HTML report, its graph SVGs, and the artifact writer.

Everything degrades: a missing tool or an absent model yields a flagged note or a deterministic fallback, never a crash.

Deterministic static tools — static/tools/

Each tool is a small self-registering callable that reads one member and returns evidence-linked findings. All parse, never execute. Guarded tools that wrap an external binary degrade to empty (or a tool-missing note) when the binary is absent, so a slim deployment still runs the pipeline.

Unpack & entry point

  • unpack — extracts the bundle and builds the file tree: stdlib archives, Mac fork-carriers (StuffIt/BinHex/MacBinary via unar), and installer executables (InstallShield / NSIS / Inno / Wise / CAB), detected by magic, so a trapped payload reaches the deep-readers.
  • entry_point — the authoritative entry-point resolver. A header/manifest parse wins and is stamped deterministic (autorun.inf, PE AddressOfEntryPoint, ELF e_entry, JAR Main-Class, …); only a bare guess is left eligible for the LLM to disambiguate.
  • director_cast — carves the RIFX/XFIR movie(s) out of a Director projector .exe into standalone .dir/.dcr members so the Lingo decompiler can read the artwork, not the projector.

Format & content identification

  • siegfried — PRONOM format identification via the sf CLI (preservation canon).
  • magika — Google’s ML content-type identifier; a second opinion that surfaces conflicts with PRONOM rather than silently resolving them.
  • Runtime detection is not a static tool: it is the data-driven runtime/ catalog dispatch (see below), which maps the extracted signals to the legacy runtime(s) a work needs so the runbook installs the runtime, not the art.
  • media_info — MediaInfo/ffprobe track characterisation; a legacy codec becomes a guest-layer dependency the runbook must provision.
  • validate / extract_meta — format well-formedness (JHOVE) and rich recursive metadata (Apache Tika), each reached over an optional side-car.

Binary & format parsing

  • binary_parse — parse-only PE/ELF/Mach-O imports, exports, linked libraries, linker hints (via pefile / LIEF), feeding the guest dependency graph.
  • scan_strings / deep_text — broad printable-string recon over many members, and the targeted full-text read of one important text/config node, mining URLs, hosts, paths, versions.
  • java_fingerprint — no-JVM structural fingerprint of .jar/.class/.jnlp: native-lib bitness, bundled jars, .class version (the JRE era), manifest.
  • mac_resource — classic-Mac resource-fork / MacBinary / AppleDouble parse to a 68k-vs-PowerPC, app-vs-stack verdict where PRONOM is blind.
  • quicktime.mov atom walk to the codec-runtime verdict (which legacy decoder it pins).
  • nes_rom — iNES/NES 2.0 header parse: mapper, battery SRAM, CHR-ROM/RAM, region/timing, title.
  • max_patch — reads a Cycling ‘74 Max patch (plain JSON): Max major version and the network-capable objects with their literal endpoints.
  • firefox_ext — Firefox/Mozilla .xpi analyzer across both eras (legacy XUL install.rdf, modern manifest.json), pinning the period-Firefox range.

Per-format behaviour lifts

These read a member (or a runtime’s recovered strings) and lift the identity-defining mechanics — what the work does — into behaviour findings, without running it:

  • web_probe — net.art HTML/JS: plugin embeds, bundled libraries, and external endpoints with their HTML context. What the page’s code actually does — the fetch/XHR/WebSocket calls and the wired interactions — is read by the JS/HTML analyzers of the code-semantics engine (readers/codesem/).
  • webext_behavior — what a browser extension is permitted to do, from its manifest: hosts it can reach, pages it injects, whether it intercepts/blocks traffic.
  • pe_behavior — what a native .exe does, read from its import table (render / network / audio / spawn).
  • script_behavior — what a bare interpreted script (Perl/shell/PHP/…) does: fetch/serve, fork, exec sinks.
  • hypercard_behavior — HyperTalk navigation and network handlers lifted from a stack’s scripts.
  • qt_behavior — a .mov’s interactive behaviour: wired sprite actions, HREF/QTVR tracks, external media references.

The LLM layer — ai/

The LLM layer. The AI framework (pydantic-ai) is import-banned everywhere else — enforced by a ruff banned-import rule in pyproject.toml plus scripts/check_import_ban.py in CI — and lazily imported here; every module degrades to a deterministic fallback if the model or a side-car is absent.

The analyst loop

  • investigate/ — the single analyst session: a tool-using agent that navigates what the deterministic tools extracted (read-only, path-jailed list/grep/read-slice plus on-demand unpack/deep-read), judges it, and returns a grounded report. Runs with no caps; any stop composes a graceful partial from accumulated findings. Every citation is validated post-hoc against real workspace paths.
  • graph_nav — the graph accessors, citation matching, and entry-point scoring the analyst uses as its graph tools (the retired standalone “identify” stage, now folded into the analyst session).
  • tiers — the only place capability names (explore / finalize / navigate / classify / reflect / vision / code) map to model tiers. A model swap is one config line; a re-tiering is one edit here.
  • pydanticai / judge — the framework adapter, and the standalone grounded-judgment primitive (judge() / ajudge()) any step can call when it has extracted evidence but must not hardcode the interpretation.
  • prompts/ — every prompt as a versioned file (the analyst session, the orient judge, the scope judge, the cartographer, the wayback specialist), not a string buried in code.

Understand, narrate, verify

  • understand — narrates a conservator-facing document over the behaviour map; a deterministic template narration is the fallback so --no-llm still emits a useful doc.
  • graph_refine — the run-graph cartographer: after the analyst has read the decompiled flow, it authors the real execution/data-flow graph over the deterministic skeleton, every node grounded against a real file.
  • reflexion — the independent grader of an actionable-unknown’s revival contract against its own bundled source (convergences / divergences / absences), recommending the cheapest escalation.
  • falsify — falsification against an external oracle: tests an archive-replay revival claim against the Internet Archive (refuted / confirmed / revised), with a runtime-VM oracle as a seam.
  • url_hints — a cheap advisory prior labelling well-known URLs (namespaces, CDNs, telemetry) so the big model spends its budget on the genuine backends. Strictly advisory; never a verdict.

The LLM-free deep-readers — readers/

Deterministic, framework-free readers that recover readable evidence from a runtime’s binary — a pure-parse pass that always runs plus a decompiler pass that degrades to a flagged note when its tool is absent — and persist the recovered source into the durable analysis record. No LLM lives here; the analyst calls these as tools.

  • routing — the deep-read router: a {runtime → sub-reader} table that dispatches a binary/ bundle node to the right decompiler.

  • director — Director Lingo via ProjectorRays (script/cast inventory, authoring version, net handlers).

  • flash — Flash/.swf via JPEXS FFDec (ActionScript, network calls) over a pure-Python header/tag pass.

  • java.jar/.class via jdeps + JADX over a pure-Python manifest/era pass.

  • dotnet — managed PE / CLR via ilspycmd (IL → C#) over a pure-Python metadata pass.

  • unity — Unity builds: version, scripting backend (Mono vs IL2CPP), platform, with the managed assembly routed onward.

  • hypercard — a pure-Python walk of the stack binary recovering its HyperTalk scripts.

  • ghidra — native PE/ELF/Mach-O decompile via the pyghidra-mcp side-car.

  • source_code / codesem/ — imperative source members through a query-first tree-sitter code-semantics layer (one engine, per-language .scm queries, structural predicates only — no regex fallback): dispatchers, exec sinks, remote-include risk, version ceilings.

  • content_scan — the universal fallback: any artifact with no specialised reader still gets a strings / endpoints / toolchain-fingerprint / references pass.

  • net_probe — the shared network-operation probe: finds the call sites and lifts the surrounding code block generically (no hostnames), for the analyst to judge.

  • codesem/lang/* (lingo, actionscript, java, dotnet, …) — the per-language analyzers inside the query-first codesem engine that turn recovered / decompiled source into behaviour-map nodes via structural tree-sitter queries (the former per-format regex scanners were re-ported here).

The runtime catalog — runtime/

Runtime detection is data, not code. runtime-catalog.yaml holds one entry per runtime module — detection signals (extension / magic bytes / container structure / embedded strings), the analysis plan, the ordered environment recipe, fidelity notes, known endpoints — and:

  • catalog — loads and validates the yaml into typed runtime modules.
  • dispatch — walks the catalog in layered order (declared → ext → magic → container → embedded-string), loads every module whose matches fires (multi-runtime is normal), and emits the guest runtime findings; this runs as the pipeline’s dispatch phase. Unmatched formats fall to the _fallback AI module, never a forced guess.

Teaching autopsy a new runtime is an edit to the catalog yaml — no new code (see Runtime modules).

The framework-free domain — domain/

Pure logic: no framework, no LLM import, no I/O beyond read-only workspace scans. This is the layer that stays standing even when the AI layer is degraded.

The run graph

  • runtime_graph — the RunGraph data structure: the artwork’s components as a directed graph, with findings attached to node ids so a node accretes facts instead of being re-reported.
  • graph — the deterministic builder (entry point + imports + cross-references + containment) and the projection of that graph back into the manifest layers.
  • cartography / ref_resolver — the shared engine that grounds a member’s outbound references into typed edges, and the name-based join (exact → path → basename → fuzzy) that resolves one reference string against the nodes other modules registered.
  • graph_dot — renders the graph to DOT for inspection.

The behaviour map

  • behavior_map — the semantic, conservator-facing map of what the work does, layered over the structural graph. Its node kinds include annotation — the artist’s own note about a component, harvested by the authored-text pass every codesem language and the format sidecars feed — and its edges include the interaction map: a human-input trigger (mouse, keyboard, form) wired to the effect it drives.
  • behavior — the deterministic builder that lifts findings + graph into the behaviour map (runs under --no-llm), plus a Mermaid renderer. It joins each per-language interactions() finding into owner → effect → input edges and drops lifecycle noise that is not human input.
  • behavior_linker — welds per-format behaviour fragments into one chain, so a polyglot work (front-end → handler → script → net) links up instead of landing as disconnected pieces.

Scoping — artwork vs. commodity

  • scoping/ — a package: opaque.py (deterministic detection of opaque dependency subtrees — vendored runtimes, large bundled content — and the commodity-vs-authored signal: content-prevalence hash, vendored subtree, self-declared library coordinate, generated file — generic signals only, never a hardcoded name), anchors.py (the artwork anchors kept through the opaque collapse), and portrait.py (the deterministic bundle portrait the orient judge reads, and the BundleVerdict it returns).
  • preflight — step 1.5: detects a bundled runtime installer alongside the art and asks the researcher (a two-phase pause on the async path); never rejects.
  • context — seeds the conservator’s intake (Variable-Media intent) as human-authority findings, a strong prior that a contradicting deterministic fact still overrides.

The knowledge base & the deliverables

  • kb — composes the three-layer KB artifact (overview / file-tree / host / guest / network / fidelity-risks / provenance) from the accumulated report; the “possible KB” is the deliverable KB.
  • inventory — the researcher-facing synthesis views: the dependency manifest (what must be provisioned) and the asset catalogue (what the work is made of).
  • artist_hand — harvests the artist’s voice (comments, authorship/copyright/contact strings) from original and decompiled source into the art-historian’s view.
  • runbook / suggestions (in runbook) — the ordered host → guest → network reconstruction plan and the suggested base VM + tools.
  • network_profile — builds the machine-readable network-config.json handoff rvmc consumes (the transport verdict plus deterministic hints), so a judged transport carries through to the VM.
  • enrich — a second consumer of the same engine: proposes metadata for a known catalog binary (a single think pass, human-promoted draft → reviewed → published).
  • summary — the ~10-second read at the top of the report, assembled deterministically from fields already produced.
  • url_hygiene — one structural URL/host validator shared by every string-mining tool; judges well-formedness only, never whether a domain is a real endpoint (that is the analyst’s call).
  • pipeline — a thin orchestrator: each phase body lives in domain/phases/*, and the post-preflight sequence is a declared phase plan the orchestration engine validates and runs: quick-pass → dispatch → code-semantics → deep-read (a fixpoint loop that drains every important unread node, relooping until the read-list is empty and the KB stops changing — no top-N gate) → causal → compose → url-hints → commodity-endpoints → network-scope → commodity-scope → investigate → refine-graph → eval → falsify. Static extraction, preflight, orient, and the scope collapse run as the head before the plan; artist-hand harvest and final synthesis run as the tail after it.
  • phases/orient — the orientation phase, run after preflight and before the scope collapse: builds the deterministic bundle portrait and has the LLM judge name the entry point(s) and the work-vs-commodity partition. Gated (a lone clear entry spends no model call); the judged entry fills in at LLM-fallback authority only — a deterministic or human entry always wins — and an open question for the researcher surfaces as a tagged note rather than a silent guess.
  • orchestration/ — the phase engine: the Phase type (name, body, declared reads/writes), validate_plan (proves every phase’s reads are met by an input or an earlier phase — a reorder that breaks a data dependency fails before anything runs), and run_plan (executes the plan with cooperative cancellation).
  • model / ports — the shared data model (findings, layers, confidence, authority) and the port interfaces (Reasoner, DeepReader, Storage, …) the domain talks to the outside through.

The report renderers — report/

The presentation layer over the finished KB — rendering only, no analysis:

  • report_html — the self-contained HTML report a conservator reads.
  • svg_render — the run-graph and behaviour-map SVGs embedded in it.
  • artifacts — writes the deliverable files (report, KB, manifests) into the job’s output.

See also

  • The autopsy — what the analysis produces and how to read it.
  • Architecture — the pipeline shape and the deterministic/LLM seam.
  • Runtime modules — how to teach autopsy a new file type.