Sign in

Capabilities

000a12ffb6c5dfcc0fada157d40500965861ca5e63d4f44fa4a679f7024c6f69 is a 7.5 KB .NET Downloader.exe. MalwareBazaar labels it StormKitty; ClamAV calls it Trojan.Downloader. Its whole job is fetching stage two, and the two strings that say so were both extracted — the URL out of the #US user-string heap, the download primitive out of the metadata name heap:

https://cdn.discordapp.com/attachments/1053623990066876427/1054133763724021820/Xumrybezs.dat
HttpWebRequest

Nine capabilities fire on that file, and eight name only plumbing: .NET assembly load and invoke, HTTP request, response and send, thread suspend, gzip compression. The ninth is the rule below, which did not exist before August 2026. Until it did, the nearest rule was c2-endpoint-uri, which requires a URI template — a scheme plus at least two format-string placeholders (%s or %@) in one string, because a config-driven C2 endpoint is assembled at run time. One concrete literal URL matched nothing.

Adding it moved no verdict, and was not meant to: cdn-hosted-payload.fetch-remote-payload fires while the file still scores Benign at total 0, empty ledger, zero scored indicator hits. A capability is a named behavior a binary can perform, matched from static facts by a TOML rule and reported as a fact carrying no weight of its own.

QuestionAnswer
What is in the catalog923 entries in 76 TOML files, embedded into the engine at build time
Where the rules come from813 machine-transpiled from Mandiant capa, 110 hand-authored
How far a hit can be trusted128 Sound, 785 Heuristic, 10 Superset
What reaches the malware score35 curated rows; the other 888 are enrichment-only
How it compares to capa46 of capa's 57 capability-classed rules on curl for Windows
What is not coveredbasic-block scope enforcement, functions past the 6,000-per-analysis budget, runtime rule authoring, YARA, dynamic analysis

Worked example: the cdn-hosted-payload rule

cdn-hosted-payload requires an abused-hosting download URL and a download primitive together, and fires on 8 of 1,729 corpus samples and 0 of 82 benign binaries:

id     = "cdn-hosted-payload"
family = "communication"
attack = ["T1105", "T1102"]    # Ingress Tool Transfer; Web Service
mbc    = ["B0030"]

[[capability]]
name    = "fetch-remote-payload"
scope   = "file"
summary = "Hardcoded payload URL on abused public hosting plus a download primitive"
all = [
  { regex = '(?i)https?://(cdn\.discordapp\.com/attachments/|media\.discordapp\.net/attachments/|api\.telegram\.org/(bot|file/bot)|pastebin\.com/raw/|raw\.githubusercontent\.com/|gist\.githubusercontent\.com/|transfer\.sh/|anonfiles\.com/|bitbucket\.org/[^\s"]+/downloads/)' },
  { any = [
    { regex = '(?i)(DownloadString|DownloadData|DownloadFile|DownloadFileAsync|Invoke-WebRequest)' },
    { substring = "System.Net.WebClient" },
    { substring = "WebClient" },
    { substring = "HttpClient" },
    { substring = "HttpWebRequest" },
    { api = "URLDownloadToFileA" }, { substring = "URLDownloadToFileA" },
    { api = "URLDownloadToFileW" }, { substring = "URLDownloadToFileW" },
    { api = "InternetReadFile" },   { substring = "InternetReadFile" },
    { api = "WinHttpReadData" },    { substring = "WinHttpReadData" },
    { api = "curl_easy_perform" },
  ] },
]

Re-measured 4 August 2026 by raw byte scan — ASCII plus UTF-16LE at both parities — over the 1,729 samples in the malware training corpus and the benign fixture tree, 82 of whose 119 files carry a binary magic. Seven of the eight corpus hits carry a family label across six families — njrat twice, StormKitty, RemcosRAT, AsyncRAT, BQTLock, DiskWriter — and one is unlabelled.

Three properties do that work.

The host list is path-anchored, never host-anchored. It names cdn.discordapp.com/attachments/, not discord — a Discord client, a docs link, and a CSP allowlist all name the domain, and only a direct-download URL carries the attachment path segment.

The conjunction is mandatory. 17 corpus samples carry an abused-hosting URL; requiring a download primitive alongside it cuts those 17 to 8. Regression tests pin the rule in both directions: an abused-host URL alone does not fire, and a download primitive alone does not fire, since ordinary updaters carry one. On the witness the primitive arm is satisfied by HttpWebRequest; System.Net.WebClient is absent at every parity.

A third, looser arm was dropped. That arm was an abused CDN host plus a payload-shaped file extension. It added exactly one malware sample the download-primitive arm already caught, and it was the only arm that produced benign matches: a shell script and a README pulling pinned test fixtures from raw.githubusercontent.com. Neither is a binary, so neither would have reached the scanner in practice; the arm was dropped anyway, because it is exactly the shape a legitimate updater has.

The witness first read as a non-match. A .NET #US user-string heap stores UTF-16LE, frequently at an odd file offset; a scan that stepped only even byte offsets walked straight past the URL. Both parities are scanned now — the measurement was wrong before the rule was.

Rule format

A capability entry carries a name, a scope, a plain-language summary, ATT&CK technique ids, MBC behavior ids, and a match tree — and no weight field. A rule states what the binary can do; whether that costs the binary anything is decided elsewhere, by an explicit table of 35 promotions.

Capabilities vs indicators

Indicators are precision-tuned and scored; capabilities are breadth-tuned and fire on benign software by designcurl legitimately sends HTTP requests, and recording that is the point. Indicators are the curated in-tree baseline rules feeding the malware scorer's category aggregation directly, and are tuned for precision because of it. Both surfaces emit into the same eleven-category taxonomy and the same hit shape; at scan time the baseline runs first and wins on rule_id collision, and the catalog adds coverage on top as a separate enrichment stream that is never folded into the scored set.

The rule catalog

923 capability entries across 76 TOML files under rules/capabilities/, embedded into the engine at build time so every engine binary carries a self-contained ruleset.

OriginEntriesNotes
Machine-transpiled from capa813in the 30 files carrying the transpiler's generated header; 328 come from capa's nursery/ staging tier — community rules not yet graduated upstream — and are clamped to the lowest confidence tier however specific their predicate
Hand-authored110in the other 46 files, where the catalog leaves capa's vocabulary
Total923across 76 files

A declared scope decides what a rule is evaluated against, and five of the seven scopes are not binaries at all:

ScopeEntriesEvaluated against
function630every recovered function in turn — one hit row per matching function
file144the whole-binary surfaces, with no function attribution
basic-block144function scope; the block boundary is not enforced
extension2a browser-extension manifest — permissions and host permissions
document1an OLE/VBA document — auto-exec symbols, macro source, stream names
lnk1a Windows shortcut — target path, arguments, invoked interpreter
installer1an installer package's signature state

The last five are container-provider rules. A binary never supplies a container surface, so they are dispatched against an empty one and yield zero hits on an executable rather than being skipped by an exception.

The top-level rule directory fails the build when it is missing, rather than degrading at run time into a smaller built-in list. The capabilities subdirectory is weaker: absent, it compiles to an empty embedded catalog, caught by a test asserting the embedded file list is non-empty rather than by the build. A parse failure does panic loudly on first load, and the same test turns that into a CI failure instead of a shipped binary.

Hand-authored rules

The hand-authored slice covers what capa's vocabulary does not reach: Go-ecosystem rules keyed on module and package names, shellcode execution, EDR-tamper shapes, wiper and ransom families, and the five superset predicates capa's feature extractors cannot express.

The recovered-string surface

All seven macOS stealer rules carry surface = "recovered", meaning they match only strings a family decryptor recovered because they existed on disk encrypted — so they fire zero on benign input by construction. The modifier is not a macOS speciality — 11 capabilities carry it, the seven stealer rules plus four XOR-obfuscated-content rules reading the same decoded partition for URLs, onion addresses, HTTP headers, and persistence paths. A benign browser's cleartext Login Data, Cookies, and login.keychain live in the ordinary string partition those rules never see, and the AMOS decryptor abstains on benign input, measured empty on 6,730 third-party and system Mach-O binaries. The malicious tell is not the string; it is that the string had to be decrypted to exist.

Across the 13 corpus samples the decryptor does fire on:

Recovered tokenSamples (of 13)
osascript13
wallet8
cookies8
keychain4

capa coverage manifest

A coverage manifest beside the rules classifies every rule in the pinned capa snapshot — commit aed45e25, 19 June 2026 — 1,031 rows.

DispositionRulesWhy
Native TOML detector828reproduced in the catalog format
Routed to fingerprinting157they identify what a binary is rather than what it does
Structural Rust detector11not expressible in the TOML format; the 11 rows register 9 detectors, process hollowing carrying three (inject PE, inject DLL, inject thread)
Not ported357 capa pseudo-rules plus 28 with a mechanical blocker
Total classified1,031every rule in the snapshot

A test asserts, separately for the authoritative and nursery tiers and in both directions, that the manifest and the vendored snapshot name exactly the same rules — so the accounting cannot drift from upstream without failing the build.

Unported capa rules

Of the 35 unported rules, 7 are capa's own (internal) … file limitation pseudo-rules, which are housekeeping rather than detections. The other 28 each name a mechanical blocker in the manifest:

BlockerRulesWhat it means
mnemonic-unmapped14Keys on a specific x86 mnemonic or addressing idiom the IL abstracts away — hash data using djb2, decrypt data using TEA, check for trap flag exception, reference the VMWare IO port
instruction-co-location5Needs "these instructions in the same basic block" adjacency — compute adler32 checksum, decompress data using aPLib, encrypt data using chaskey
rule-ref-unresolvable4Depends on another unported rule as a sub-match — steal KeePass passwords using KeeFarce, overwrite DLL .text section to remove hooks
count-inner-unsupported2Requires counting matches of an inner sub-clause — contain anti-disasm techniques
no-family2Nursery-tier with no authoritative detector family to attach to
characteristic-unmapped1encrypt data using RC4 PRGA — keys on a low-level characteristic absent from the feature vocabulary

None is a dataflow or reachability gap. 22 of the 28 are instruction- and structure-level primitives; the six rule-ref-unresolvable and no-family rows are not blocked by the matcher at all, and unblock by porting something else rather than by adding a primitive.

The matching engine

The catalog compiles once per process behind a lazy static, then evaluates per binary: a literal prefilter first, a predicate tree second.

The literal prefilter

An Aho-Corasick pass over the string surfaces, imports, and section bytes yields the set of present literals before any rule is evaluated. Every literal substring, api, and bytes leaf in the compiled catalog joins one automaton, and every compilable regex leaf joins one combined regex set; a rule descends to tree evaluation only when the index cannot rule it out. The prune is a pure over-approximation — it never drops a rule that would have matched, and the same bitset answers an absent leaf in constant time, so a surviving rule pays only for its present leaves. An unpruned oracle path is retained, and a test asserts the two produce identical hits and identical evidence text.

The prefilter fails open, not closed, and silently. The combined regex set is built against a 64 MB ceiling — roughly 550 regex leaves already exceed the regex crate's 10 MB default — and a set that overflows it disables the regex prune entirely, leaving every regex leaf a candidate for every rule; an Aho-Corasick build failure does the same for literals. Results stay correct; the O(rules × functions) cut that makes them affordable is what disappears.

The predicate tree

Leaves name a static fact recovered from the binary; combinators state how the leaves are required together.

LeafMatches
substring, regexliteral and pattern text, with base64 and XOR encoding variants
api, imports, exportsnamed calls and linkage
sectionssection presence and bytes
numbers, offsetsconstants recovered from the lift
IL-op classesoperation categories in the lifted code
il_op + operand togetheran op class and an immediate co-occurring on one instructioncmp reg, 0xCC fires only when a cmp carries 0xCC, where a flattened conjunction of "some cmp" and "some 0xCC" would over-match
OS / arch / format guardsvalidated against closed vocabularies at load time
class, namespace, member.NET class, namespace, and member-reference presence over CLR metadata
Go module and package namesGo build metadata
entitlements, ObjC selectorsMach-O signature and runtime metadata
container leavesauto-exec symbol, macro source, OLE stream name, LNK target and argument, invoked interpreter, extension manifest permission, host permission, package signature state — supplied only by a document, shortcut, extension, or installer provider, inert on every binary surface
CombinatorSemantics
all / any / notconjunction, disjunction, negation
occurrence-count rangehow many times a leaf matched, with optional lower and upper bounds
min = N over an any listtallies distinct matching branches rather than total matches; the evidence it emits is the union of every satisfied branch's matched text, not the text of one

Seven shared fragment files hold the primitive sets more than one rule needs — process spawn, HTTP client, file write, injection, exfiltration, ransom note, and the capa cross-references — and they are inlined at load, so the detectors that share them cannot drift apart.

Superset leaves

Five leaves express what capa structurally cannot:

LeafSupplies
reachescall-graph reachability to a capability symbol
taint_flowsource-to-sink dataflow
api_arga resolved argument value at a call site
dynloaddynamic-resolution facts
xpcmacOS XPC service names

api_arg reads recovered call signatures precisely rather than approximating with capa's "a number near an API" idiom:

[[capability]]
name    = "open-raw-socket"
scope   = "function"
summary = "Opens a raw socket (AF_INET / SOCK_RAW) — packet crafting / sniffing"
attack  = ["T1095"]
api_arg = { api = "socket", arg = "type", value = "SOCK_RAW" }

Hit attribution

A hit carries its rule id, the evidence kind, the reverse-cross-referenced functions the match fired in, and — when the evidence had an address — the matched virtual address, which opens in the decompiler. capa's JSON records that a rule matched; it does not emit per-function attribution as a first-class field.

Neither field is guaranteed, and they are independent. On curl for Windows 36,006 of 36,253 hits carry a matched VA; on the .NET witness only one of nine does, the other eight matching CLR metadata rather than an addressed string — and that one is the file-scope cdn-hosted-payload hit, which by construction attributes no functions.

One rule emits one row per matching function, and nothing collapses them. Deduplication happens only across the two scan paths — the analyze-side pass and the engine window agree by rule id, engine winning — never within a single rule's function fan-out. curl for Windows produces 36,253 rows from 63 distinct rule ids: six byte-pattern rules over .rdata (Camellia, Blowfish, SHA-256, CRC-32, zlib inflate/deflate, Salsa20/ChaCha) contribute exactly 6,000 rows each, because section-global byte evidence is re-attributed to every function evaluated and 6,000 is the per-analysis function budget. The served total counts rows, so it reads 36,253 where the interesting number is 63.

Confidence tiers

Each rule is classified by a pure function of its own predicate tree, scope, and file — no match data, no binary involved.

TierRulesAssigned whenHow to read a hit
Sound128a required leaf, reachable through an all-only spine, is specific enough to trust alone: a byte pattern, a .NET type, an imported symbol, a long anchored substring, a rare constant, or an API conjunctiontrustworthy on that single required leaf
Heuristic785everything else; every nursery-origin rule is clamped here however specific its shape, because a staging-quality rule is never presented as soundexpected background on benign software
Superset10the rule uses one of the five superset leavesa claim capa's feature extractors could not have produced

Run over the whole catalog, the classifier reports:

capability confidence histogram: total=923 sound=128 heuristic=785 superset=10
                                 low_confidence=5 att_ck_tagged=516

516 rules carry at least one ATT&CK id and 407 carry none; the 516 name 184 distinct ids, which collapse to 99 parent techniques once sub-technique suffixes are dropped.

The low_confidence flag is the frida-matches-"Friday" shape, and it keys on regexes specifically. A rule is flagged when all four hold:

A substring leaf never trips it however short and unanchored it is. Only the disjunctive frontier is examined — leaves reachable through any and min spines, stopping at all and not — so run-powershell-expression is not flagged despite its short pwsh leaf, because that leaf is conjoined with a word-boundary-anchored discriminator and cannot match on its own.

All five flagged rules are anti-analysis string lists, all five measure zero on the benign tree today as a raw-byte upper bound, and the flag is served so a consumer can drop them:

RuleRegex that trips the flag
anti-analysis.reference-analysis-tools-strings(?i)regmon(\.exe)?
anti-analysis.reference-anti-vm-strings(?i)A M I
anti-analysis.reference-anti-vm-strings-targeting-qemu(?i)BOCHS
anti-analysis.reference-anti-vm-strings-targeting-vmware(?i)VMWare
anti-analysis.reference-anti-vm-strings-targeting-virtualbox(?i)VBOX

Worked example: the qemu anti-VM branch

A bare unanchored substring is the dominant false-positive shape, and capa's reference anti-VM strings targeting Qemu — which keys on a bare qemu string — is the measured case.

BranchCorpus hits (of 1,416)Benign hits (of 115)Coincidental
bare qemu substring151213 of the corpus hits and all 12 benign hits
QEMU HARDDISK, QEMU DVD-ROM, QEMU MICRODRIVE, QEMU Virtual CPU10the single hit is a publisher, not a detector

Measured 27 July 2026, when the malware corpus stood at 1,416 samples and the benign fixture tree at 115 files — the same tree the cdn-hosted-payload measurement counts as 82, filtered to the files carrying a binary magic. The benign twelve were eleven instances of the Zig build system's enable_qemu symbol and one sentence from ripgrep's allocator:

<jemalloc>: (This is the expected behaviour if you are running under QEMU)

A bare qemu branch therefore published "ripgrep references anti-VM strings targeting Qemu" because ripgrep's allocator carries a warning message. The corpus noise was the same class: Qt's QEmulationPaintEngine symbol in four samples, garbled Go symbols (HiqEMu, JANofQeMu5, VHqeMU3), and base64 blobs. Word boundaries do not fix it — ripgrep's QEMU) has boundaries on both sides while a real sample's aqemu has none on the left.

The branch was replaced with the device and SMBIOS artifact strings al-khaser actually compares against. That one remaining match is BirdMiner/LoudMiner (904ad9bc506a09be, Mach-O), which bundles a compiled QEMU to run a Linux miner image, so the tokens come from QEMU's own IDE model-string table: the emulator publishing the names, not a detector comparing against them. Static string evidence cannot separate publisher from detector. The branch is retained on vendor grounds with the benign zero as its guarantee, not on corpus evidence — the corpus carries no genuine QEMU anti-VM check at all.

Fixing one branch did not retire the shape. The qemu rule kept a bare (?i)BOCHS alternative for the ACPI OEM table id — exactly the short-unanchored-regex pattern the confidence classifier flags — so the rule carrying this worked example is itself one of the five low_confidence rules tabled above. The flag, not a rewrite, is what carries that class now.

Parity with capa

Detection parity on curl for Windows is 46 of capa's 57 capability-classed rules, and it is the only kind of parity measured against samples — rule-coverage parity is the manifest classification above, a static accounting. Detection parity runs capa 9.4.0 and the engine on the same binary, against the same vendored rules and FLIRT signatures, and diffs the fired capabilities through the manifest's name mapping. A capability counts as a match when both tools fire a rule the manifest maps to the same capa name; the denominator is capa's capability-classed matches only, excluding matches the manifest routes to fingerprinting or to a Rust detector.

Binarycapa capability-classed matchesReproducedRate
curl for Windows x86-64 — the richest single sample measured574681 %
jq for Windows2514

The curl overlap was 38 until four systematic defects were found and fixed, each unblocking a class of rules rather than one rule:

These are illustrative single-binary figures. No corpus-wide recall rate is stated because none is measured; a gated corpus benchmark does not exist yet.

Most of curl's remaining gap is deliberate. FLIRT-identified statically-linked library code is separated from the binary's own code, so a program that embeds OpenSSL or zlib does not light up as a crypto implementation. On curl that is measurable as 117 library-attributed matches against capa's 1 library function — capa counts those capabilities; here they are labelled library_provided, reported in a separate section rather than on the threat surface, and hard-excluded from scoring. Subtract that class before calling anything a miss.

One asymmetry has no number at all: capa's vivisect backend handles PE and ELF only, so it cannot analyze Mach-O. A direct diff is structurally impossible there, and no Mach-O parity percentage is claimed.

Promotion to the malware verdict

A capability reaches the Malware verdict through a curated promotion table of exactly 35 rows, and the table is frozen. 34 of those rows exist because a bespoke Rust detector was retired into a faithful TOML rule, admitted only after fire-count parity on the benign corpus and recall parity on the malware corpus were proven, and the promoted capability carries the retired detector's exact evidence band. Promoted capabilities sharing a family collapse to one ledger row carrying the highest band that fired, which both prevents multi-row inflation and reproduces a multi-band detector's logic.

The remaining 888 rules are enrichment-only. Uniform consumption would reopen the individual-scoring false-positive path the verdict was recalibrated to remove.

Promotion is a pure refactor with zero detection value — the capability and the retired detector fire on the same facts, which is the retirement gate, so no sample that was missed becomes caught — while a looser capability rule carries real false-positive risk against a benign binary the tighter Rust detector's corroboration would have spared. The downside strictly dominates the upside. Each existing row was proven on an ad-hoc local run that left no regression fence behind it, so no further row is added until a standing full-corpus recall and benign-false-positive gate runs in CI — 1,421 samples when the freeze was declared in July 2026. About 35 further Rust detectors were candidates for promotion; they stay in Rust.

The 35th row is the exception. impact-inhibit-recovery.config retires no detector: it is a third shape inside an already-promoted family, added 25 July 2026 because the family's two command-literal shapes scored 0 of 21 on the MedusaLocker cluster, which drives shadow-copy deletion from an operator-config key rather than an embedded command line. It is the one row that catches samples the promoted set provably missed, and the missing-gate objection is answered for it by an in-tree test fencing the fire and both non-fire shapes — bare token and clean baseline — rather than by an ad-hoc run.

Two guardrails sit around the table.

Library-provided exclusion. A capability whose attributed functions all fall inside identified library code is marked library_provided and excluded; a file-scope hit, having no function attribution, is a whole-file fact and never gets that label. That library set is signature-matched code plus the part of the Go runtime and standard library go build compiles in whether the author wanted it or not — and it deliberately stops there. crypto/aes and os/exec are Go's code but the author's decision, so they stay scoring-eligible and are labelled rather than excluded.

Platform-signed abstention. On a verified platform-signed binary every hit is marked ineligible for scoring while remaining a recorded fact — the abstention is a presence filter, never a negative weight.

API, MCP, and SARIF output

GET /api/v1/binary/{sha256}/capabilities returns the inventory grouped by MITRE tactic, free and ungated beyond the binary's own visibility. Beside the tactic buckets sit an unattributed bucket, for hits whose rule names no resolvable tactic, and a library_provided array held out of the tactic view entirely. Each entry carries:

Three tiers arrive on the wire as two labels. confidence is "sound" or "heuristic"; a superset rule is labelled "sound" and identified by the superset boolean, and an unclassified rule is labelled "heuristic" — the conservative direction. The summary block counts the same way:

Summary fieldCounts
totalrows, not distinct rules
soundevery non-heuristic row, superset rows included
heuristicheuristic-labelled rows
supersetrows whose rule uses a superset leaf
tacticstactic buckets present
capa_parityrows capa could in principle have produced — total minus superset
beyond_capathe superset hits it could not
library_providedlibrary-attributed rows, held outside total

The get_capabilities MCP tool serves the same payload to agents; get_rule resolves any rule_id back to its definition. SARIF export emits each capability as an informational note result carrying the ATT&CK taxonomy, in the same log as findings and CVEs.

Limits

Five limits are structural: a declared scope that is not enforced, a per-analysis function budget, a leaf class inert off macOS, no runtime rule authoring, and no dynamic analysis.

Basic-block scope is declared but not enforced. 144 rules carry scope = "basic-block" and all 144 are evaluated at function scope. check-for-outputdebugstring-error requires SetLastError, GetLastError, and OutputDebugString — capa requires the three inside one basic block, meaning the error-code sandwich around a single call; here they need only appear somewhere in the same function, which any Windows program with an error-handling helper can satisfy. Every basic-block rule is that much looser than its capa original.

Only the first 6,000 functions are evaluated. Feature extraction stops at 6,000 functions per analysis, the same cap the interprocedural taint pass takes on the shared lift. curl for Windows presents 6,393 functions, so 393 — 6 % of the binary — are never evaluated against any of the 630 function-scoped rules. The program's own code claims the budget before statically-linked library code, so what falls off the end is library-attributed first, and the cap logs a warning naming both numbers rather than truncating silently.

Resolved-argument rules are largely inert outside macOS. api_arg reads recovered call signatures, and that recovery is macOS- and AArch64-centric, so open-raw-socket rarely fires on ELF or PE — the same posture as the class and namespace leaves, which need CLR metadata to exist at all.

No rule authoring against a running engine, and no YARA. The catalog is TOML in the repository, compiled into the binary; changing a capability rule means shipping a new engine. The capability catalog has no override path at all — it resolves to the embedded files and nothing else. The one runtime override belongs to the findings topic catalog next door: a directory installed once at startup appends topics on top of the embedded catalog, which is always the floor and can never be replaced or shrunk; a malformed override returns an error so the caller refuses to boot rather than silently running a smaller ruleset, and a later call is ignored because the active catalog caches on first read. None of it reaches a capability.

No dynamic analysis. Excluding its shared library rules, capa's snapshot declares a dynamic scope of call on 271 rules and span of calls on 410; nothing here approximates a runtime trace.

Indicators is the curated baseline this catalog sits beside, and Malware is the scorer the 35 promoted rows reach. Findings is a separate rule vocabulary over the same lift, asking whether the code is wrong rather than what it can do — a capability and a CWE finding can name the same function and disagree about whether it is a problem. Answers covers the agent-facing side of the same facts.