SIS CLI

Asset Graph & Asset-Path Queries

Every scanned file is modelled as a nested asset graph: the root document plus every artefact it contains — archive entries, email parts, OOXML/OLE members, PDF embedded files and carved blobs, and polyglot interpretations — as a single recursive tree. The engine always emits one (a bare root for a flat document), so report.asset_graph is the source of truth for structure-aware querying.

You address nodes in that tree with an asset-path selector, an abbreviated-XPath grammar over the containment structure.

The asset-path grammar

An asset path is /-separated segments of <tag>:<name>, where <tag> is the container grammar tag (zip, pdf, ole2, mht, iso, eml) and <name> is the member label. Wildcards and predicates scope the match:

  • pdf:root — the root node.
  • zip:root/zip:doc.pdf — a named entry.
  • * — any single segment; ** — any depth (recursive descent).
  • [field=value] — a predicate on a node’s typed fields (see below).
  • A trailing /<terminal> projects something about the matched nodes (findings, digest, text, …).
sis query bundle.zip "**"                          # every asset node
sis query bundle.zip "zip:root/*"                  # direct members of the root
sis query bundle.zip "**/[format=pe]"              # any PE anywhere in the tree
sis query doc.pdf   "**[polyglot=true]"            # nodes in a polyglot

Output envelope: { "result": { "type": "asset_select", "source": "asset_graph", "matched_count", "nodes": [...] } }. A selector runs live (sis query <file> "<selector>") or over a saved report without rescanning (sis query --from-report report.json "<selector>").

Predicates

[field=value] filters matched nodes on typed asset fields. Supported fields:

  • format — the sniffed format (pe, pdf, zip, png, js, …). This is what routing keys on.
  • shape — the structural identity (container.zip, markup.html, script.js, …): the source-qualified structural authority’s answer to “what is this artefact?”. Distinct from format (the coarse sniff) — [shape=…] matches only where a shape probe structurally resolved that format. One answer per node, even on a polyglot.
  • candidate_shapemembership over every accepted shape candidate. Where [shape=…] asks what the artefact is, [candidate_shape=…] asks whether a format was accepted at all, so a polyglot’s non-selected views stay queryable: a PDF carrying a script-shaped member matches both [shape=document.pdf] and [candidate_shape=script.vbs].
  • extent — the honest validation depth of any shape candidate (magic, header, header_and_footer, index, physical_layout, whole_structure, …). [extent=header] = shallow-validated members.
  • originroot / email_part / archive_entry / embedded_object / carved.
  • depth — nesting depth (numeric: >=, =, >, <, <=).
  • completetrue/false (a truncated/over-budget member is false).
  • analysis_state, declared_type, sniffed_type.
  • finding — membership: [finding=<kind>] matches nodes carrying that finding kind.
  • sha256 / blake3 — the decoded-content digest (the content address is blake3).
  • polyglottrue iff the node is a polyglot container or sits inside one.
  • polyglot_views — interpretation count on a polyglot node (numeric).
  • view — scope to one polyglot interpretation, e.g. [view=zip].
sis query sample.eml "**/[format=pe]"                       # executables at any depth
sis query sample.zip "**/[format=ooxml][complete=false]"    # truncated OOXML members
sis query doc.pdf   "**/[format=pe][polyglot=true]"         # a PE inside any polyglot
sis query doc.pdf   "**/[format=pe][view=zip]"              # scoped to the zip interpretation
sis query bundle.zip "**/[shape=container.ooxml]"           # members whose structural identity IS OOXML
sis query bundle.zip "**/[candidate_shape=container.zip]"   # members where ZIP was accepted as any candidate
sis query bundle.zip "**/[extent=header]"                   # only header-deep (shallow) structural members

Terminals

A trailing /<terminal> projects something about the matched nodes:

  • /findings — the findings attributed to each node.
  • /summary — a compact per-node summary (path, format, counts).
  • /sha256 — the decoded-content digest (reported as blake3 + digest_algo).
  • /views — for a polyglot node, its interpretations (view_format, verdict, finding count).
  • /text — each matched asset’s extracted safe-text (live scan only — a saved report carries handles, not bytes, so --from-report … /text refuses).
  • /bytes — write each matched asset’s decoded bytes to disk. Egress-gated: refuses without --extract-to <dir> (raw member bytes never go to stdout) and in report mode. Returns a manifest (asset_path, output_path, size, blake3) — never inline bytes.
sis query bundle.zip "**/[format=ooxml]/text"                       # per-member safe-text
sis query bundle.zip "**/[format=pe]/sha256"                        # per-member digest
sis query doc.pdf   "**[polyglot=true]/views"                       # interpretations
sis query bundle.zip "**/[format=pe]/bytes" --extract-to /tmp/out   # carve out the PEs

Scope note: /text and /bytes resolve for the root and first-level container members (the flat member seam); deeply-nested members report unavailable honestly.

Polyglots

A polyglot is one asset whose bytes parse validly as multiple formats. It is modelled as a single node with one interpretation subtree per format. Selectors are transparent through the polyglot wrapper: zip/pdf reaches a PDF via zip → (polyglot) → pdf without naming the polyglot. Query for polyglot relationships with [polyglot=true], [polyglot_views>=2], [view=<fmt>], and /views.

  • sis docs query — the full sis query namespace + --where grammar
  • sis docs shape — clustering files by asset-graph shape (campaign hunting)
  • sis docs extractextract --asset <selector> scoped extraction
  • sis docs cdrsanitize --asset-selector scoped-drop planning