Roadmap
A 704,528-byte Mach-O in the malware corpus contains exactly one URL:
$ strings -a sample | grep -oiE 'https?://[^ ]+' | sort -u
http://upx.sf.net
That is the packer's own banner. Statically unpacking the file produces 2,036,784 bytes of original Go 1.17.8 executable, and one URL again:
$ strings -a unpacked | grep -oiE 'https?://[^ ]+' | sort -u
http://46.137.201.254:8001initSpan:
An IPv4 literal on port 8001, running straight into the next entry in the Go runtime's read-only string blob because those strings carry no terminator. It occurs once in the unpacked bytes and zero times in the 704,528 that were submitted. Twenty-one files in the corpus carry the UPX magic; the twelve Mach-O are the measured set and the nine Windows PE have never been run through it. Three more of the twelve:
| Sample | Packed | Unpacked | What the payload carries |
|---|---|---|---|
| Mabouia | 53,872 | 125,472 | http://creativecode.com.br/mabouia — the only URL in the payload |
| Pymafka | 1,458,520 | 3,759,708 | no URL at all; the only absolute paths are a statically linked OpenSSL config tree (/usr/local/ssl/private, /usr/local/lib/engines-1.1) |
| ColdRoot | 983,040 | 3,292,828 | /Library/LaunchDaemons, in a sample delivered as com.apple.audio.driver, plus http://www.indyproject.org/ — a networking library's homepage |
None of those strings exist in the corresponding packed file. An endpoint carries the verdict in neither direction: Pymafka's payload has no URL at all and scores Malicious 709 on behavior alone, against Suspicious 18 for the stub that was uploaded; ColdRoot's is a library homepage sitting in the same string table as a beacon, which is why raw endpoint presence cannot raise a verdict on its own. Eight of the twelve payloads carried evidence strong enough to change the verdict on the file that was actually uploaded, one parent scoring Benign on its stub and LikelyMalicious on its 41,516-byte payload. Unpacking has the promotion rules that keep a benign compressed installer from being caught by the same mechanism.
Mabouia's payload scores lower than its stub — Suspicious 15 against 18 — because the one genuine command-and-control address in the table fires nothing. Command-and-control recognition is a curated set of twenty-one patterns (.onion and Tor gateways, dynamic-DNS providers, pastebin.com/raw/ and raw.githubusercontent.com, the Telegram bot API, Discord webhooks, ngrok tunnels, Cloudflare Workers, DNS-over-HTTPS, bare IPv4-with-port), and an arbitrary vendor hostname matches none of them. Treating any hardcoded endpoint as command-and-control is the wrong repair — on the Go payloads in this same set the unclassified endpoints are module-path fragments and ip6.int, not beacons — so the open work is family-aware config extraction.
Milestones and commercial tests
Every milestone is gated on one commercial test, not on shipped features — a bar whose clearing triggers a raise or a doubling down, and whose miss is data. Machinery at the depth above already runs under all five; what none of it has yet is proof that anyone pays for it.
| Milestone | Status | Test it must pass | Bar that clears it |
|---|---|---|---|
| 1. Cloud | live | Do they come, and do they pay? | First paying Pro at $149/mo — 100 the bar, 1,000 the dream |
| 2. Firmware | partial | Does someone pay for a firmware or package report? | First firmware design partner, or first paid report |
| 3. Audit | partial | Are the findings clean enough to bill? | First paid audit engagement |
| 4. Agent | partial | Does an agentic workflow actually use it? | First LLM-native partner or integration live |
| 5. Fleet | partial | Will an org deploy it and pay? | First enterprise contract at the $25,000 floor — one named org indexed across hundreds of hosts |
What those bars are worth:
| Plan | Price | 100 subscribers |
|---|---|---|
| Pro, monthly | $149 per user per month | $178,800 a year |
| Pro, annual | $1,490 a year — $124 a month, two months free | $149,000 a year |
| Enterprise | from $25,000 a year, about fourteen Pro seats | — |
1. Cloud — free and Pro analysis (live)
Objective — turn adoption into first revenue and the leads that become enterprise.
Scope — open any Mach-O, ELF or PE without running it: behavior, capabilities, network surface, functions, libraries, memory segments. Pro adds a private workspace and retained history.
Shipped — Stripe checkout, the customer portal and the entitlement webhook are live. The Pro floor gates eight capabilities in the feature resolver, cleared by Pro and by anything ranked above it:
- the whole agent surface — every MCP tool
- per-function decompile
- binary-versus-binary diff
- package-versus-package diff
- the call-graph investigation suite
- annotations, read and write
- on-demand reanalyse
- the full strings table, with offsets and cross-references — the categorized subset of interesting strings stays open to every caller
Four firmware routes carry their own Pro-or-above check on top of that: package detail, whole-firmware SARIF, SBOM export, and package re-processing.
Tier is also what the rate limiter reads:
| Caller | Reads per minute | Uploads per hour |
|---|---|---|
| Anonymous | 60, keyed by client IP | 5, keyed by client IP |
| Free, signed in | 300, keyed by user | 100, keyed by user |
| Pro and above | 3,000, keyed by user | unmetered per user |
A Pro read ceiling exists at all so that a stolen token cannot mine the corpus unthrottled. Independently of caller tier, each deployment node is capped at 500 uploads and 8 GiB per hour.
An upload with no visibility set defaults to public, so a free upload joins the public corpus — which makes privacy the first thing Pro sells rather than a feature bundled behind it.
Limits — three measured costs scale with the corpus rather than with the request.
| Cost | Measured |
|---|---|
| Landing-page aggregate — an O(corpus) walk | 5.6 s at 1M rows. Its 30-second cache applies only to anonymous, default-scope requests, so every signed-in load, Free or Pro, recomputes the full walk |
| Per-hash sightings rollup — scans the whole append-only upload ledger, not that hash's own rows | 14.6 ms at 100,000 ledger rows, 96.7 ms at 650,000 |
| Opening the store at boot | 228 ms at 100K binaries, 1,434 ms at 1M |
Platform has the full measurement.
2. Firmware — unpacking, inventory, diff (partial)
Objective — own the question "what is inside this firmware, OS release or vendor package, and what changed."
Scope — unpack the container, inventory every binary inside it, attribute known CVEs, track which file belongs to which version, then batch-diff releases down to function code.
Shipped — recursive unpacking across filesystems, archives, compression, firmware carriers, disk images, Docker and OCI images, RAR, UPX, and the Apple update chain (pbzx, AppleArchive, Img4, BuildManifest, APFS, the dyld shared cache). A Zyxel NAS326 image of 59,389,563 bytes unpacks to 6,789 files and yields CVE-2020-9054 before a single binary is opened; the per-component version-to-CVE table is in CVEs & SBOM. Package-versus-package diff and a whole-firmware SARIF rollup sit on top. The full extractor catalog is in Architectures.
Limits — a container that is identified is not necessarily a container that opens, the header-level CVE hit is a hardcoded table rather than a mechanism, and the vulnerability the showcase image is named after is not the one the engine finds.
| Container | State | Consequence |
|---|---|---|
| ext2, journal-clean ext3, ext4 | extracted — extent trees, 64-bit block numbers, flexible block groups, 3-level htree directories | — |
| ext4 with an unreplayed journal, relocated descriptor tables, inline data, or encryption | rejected at validation | the reader replays no journal and holds no keys; a dirty image must be cleanly unmounted first. Block size is also capped at 4 KiB |
| btrfs, logfs, Seama, eCos, Wind River kernel images | identified, not extracted — no extractor has been written | different from a container that defeats extraction |
CVE-2020-9054 comes out of the container header because a version substring matched a hardcoded table of eight rules and six path patterns, every one of them Zyxel:
| Version pattern matched | CVE attributed |
|---|---|
three AAZF.* firmware revisions | CVE-2020-9054 |
| one build number | CVE-2024-40891, CVE-2025-0890 |
four V4.6* / V4.7* revisions | CVE-2023-28771 |
Nothing generalises to a vendor not in that table.
The showcase image does not yield the injection flaw it is named for. The NAS326's weblogin.cgi produces ten findings and no CWE-78: the tainted username is handed to a linked shared object and the system() call sits inside that separate file. Taint composes across functions within one binary, not across binaries in an unpacked tree — whole-firmware call graphs and per-library summaries are unbuilt. The CVE above is a version-string match, not a reading of the vulnerable code.
The OSV, EPSS and KEV vulnerability data is fetched by the operator rather than shipped in the binary, so a deployment that never fetches it reports component versions with no CVE attribution at all.
3. Audit — malware verdicts and CWE findings (partial)
Objective — make the findings trustworthy enough to sell as paid audits.
Scope — a malware verdict plus CWE findings on a binary or a whole firmware image; grow the rule set; triage away false positives.
Shipped — the verdict engine runs on Mach-O, ELF and PE, measured against a 1,729-sample malware corpus. Seventy-two CWE ids appear across the binary detectors and 71 have a detector that can emit them. A UPX-packed sample's verdict folds in what its unpacked payload does — child evidence crosses to the parent through a closed curated table and can only raise the parent's tier — and SARIF 2.1.0 export carries findings into GitHub code scanning. The pinned capa rule snapshot is accounted for row by row:
| Disposition | Rules |
|---|---|
| Reproduced as native detectors | 828 |
| Routed to fingerprinting — they identify what a binary is rather than what it does | 157 |
| Kept as structural Rust detectors | 11 |
| Not ported — 7 are capa's own file-type limitation markers rather than behaviors; the other 28 each record why, most often an unmapped mnemonic or a required instruction co-location | 35 |
| Total in the pinned snapshot | 1,031 |
Limits — CWE findings cannot be bought at any plan tier, the measured corpus skews macOS, static stripped ELF collapses to a handful of finding types, config extraction is narrow, and false-positive triage is unbuilt.
- Not on sale — the static-audit surface, meaning the CWE findings and secrets that are the whole of this milestone, is grant-only: no plan tier clears it, Pro included, and access is a per-user override issued by the operator. A milestone whose commercial test is "are the findings clean enough to bill?" has no price.
- Corpus composition — 1,336 Mach-O against 376 PE (344 executables, 32 DLLs) and 1 ELF, so any aggregate read off it is a statement about macOS with a Windows tail. Sixteen of the 1,729 samples are not binaries at all — 13 Python scripts, 3 remote-desktop configuration files — leaving 1,713 the binary engine can read.
- Statically linked, stripped ELF — no dynamic imports, and the import-keyed detectors are the largest class, so what collapses is variety, not volume. The bounds detectors that keep firing read lifted code rather than an import table.
The same program, curl, built two ways:
| Build | Findings | Distinct CWE ids | Composition |
|---|---|---|---|
| static, stripped, aarch64 ELF | 2,629 — 42 critical, 1,783 high, 766 medium | 6 | 1,519 are CWE-787; by detector, 1,240 symbolic execution, 1,311 heuristics, 78 name-keyed static rules |
| dynamically linked macOS | 153 | 21 | includes the format-string, strcpy-family overflow and path-traversal classes the static build reports zero of |
- Config extraction — narrower than "run the sample's decryptor". One family decryptor is in production: it replays AmosStealer's own obfuscation arithmetic statically, because the sample's script literal is roughly 63,700 bytes against the emulator's 4,096-byte recovery cap. It emits nothing unless four conjunctive checks hold — permutation substitution table, pure ASCII-hex first layer, 64-symbol bijective alphabet, enough blobs decoding to printable text — so a blob failing any of them abstains rather than guesses. Beside it sit two shape plugins that read structured records out of already-recovered data, and a family-agnostic single-byte-XOR string recoverer, last measured against an earlier 1,416-sample snapshot at 311 validated strings across 32 samples and 0 across 121 benign fixtures.
- Payload provenance — the promoted verdict is stored, but nothing in the API names the child it came from, so a parent re-scored from its unpacked payload reads as though the packed file earned the score.
- Triage — the pass that strips false positives is unbuilt; an evidence-chain analyst runs internally and is not a product.
Per-CWE detector behavior, including what each one misses, is in Findings.
4. Agent — MCP tools and query surface (partial)
Objective — be the binary-intelligence layer LLMs and agents reason over.
Scope — MCP server plus chat: ask a binary what it does, drive deeper queries, talk to the reconstructed program.
Shipped — 48 MCP tools over the corpus:
- search and a compositional query language across 71 indexed fields
- per-function decompile
- a call-graph suite answering what calls this, does A reach B, and what is reachable before authentication, each with a witness path
- write-capable annotations
The internal evidence-chain analyst is the first agentic consumer.
Limits — the chat surface and talking to the reconstructed program are unbuilt. The query language's own gaps — including a reverse endpoint lookup that returns zero for every binary because the indexer and the extractor disagree on a key name — are in Answers.
5. Fleet — org-wide binary index (partial)
Objective — turn Pro users and design partners into org-wide deployments, the enterprise revenue.
Scope — index every binary across all hosts; what is installed where; CVE-versus-SBOM to show what is vulnerable and where; where a hash or a near neighbour has shown up; malicious indicators.
Shipped — the plumbing. A fleet CVE rollup with per-host tracking, and a patching simulator that takes a component name and a hypothetical version and answers:
- which CVE ids the bump clears fleet-wide
- how many of those carry a CISA KEV listing
- the count of vulnerable binaries before and after
A WebSocket-dispatched compute farm absorbs scan bursts. The endpoint side sits outside this system: hosts are scanned and their inventories uploaded by macwarden, a separate agent that talks to the API, so a deployment that installs only this server has no host attribution at all.
Limits — no organization is deployed fleet-wide, and two independent gates stand between a Pro account and the host-aware surface: the deployment must be configured as a Fleet deployment with host capture enabled, and the caller must clear an Enterprise floor. Pro clears neither. The fleet CVE rollup also refuses oversized answers rather than truncating them — a query whose estimated response exceeds the cap comes back with an instruction to narrow it by minimum severity or to KEV-listed entries only.
Library and function recognition
Quad-hash, FLIRT, kernel body-hash and prologue tiers name bundled components inside stripped binaries, and every component named is another row SBOM and CVE attribution can join against. Recognition is not a milestone — it runs under all five, always.
Naming is ahead of joining. Of the 1,835,557 bundled quad-hash signatures, 93.8% carry a version string, but only 69.6% carry one with enough segments to be ordered against a range like "before 3.0.14" — a bare 3 mined from libcrypto.so.3 identifies the library and settles no CVE. Fingerprinting has the per-tier coverage.