Indicators
/usr/bin/ssh on macOS 26.5 — Apple-signed, shipped on every Mac — produces six indicator records:
rule_id evidence matched
communication.protocol_decoder_with_shell_exec RawString <?xml version + system
communication.protocol_decoder_with_unbounded_memcpy RawString <?xml version + memcpy
communication.socket_fd_redirected_to_shell Import socket + connect + dup + system
discovery.sysinfo_uname Import uname
discovery.sysinfo_gethostname Import gethostname
discovery.files_walk_stat Import stat
Every row is true. nm -u /usr/bin/ssh lists _socket, _connect, _dup, _dup2, _system, _execve and _memcpy; <?xml version="1.0" encoding="UTF-8"?> occurs six times — per arch slice, once in __TEXT,__info_plist and twice in the code-signature blob — and both string rows anchor on the __info_plist copy at 0x1000a0796. The third row is the reverse-shell primitive set, and ssh has it because ssh is a remote-shell program. An indicator says what a binary contains and what that pattern is for. It says nothing about whether the binary is hostile — and the layer that does draw that conclusion gets ssh wrong: three unique Communication rules aggregate to +30 and land Apple's own ssh at Suspicious.
195 rules scan one binary's strings, CFStrings, Objective-C selectors, imports, entitlements and non-executable section bytes, and emit one flat, unscored record per match across eight populated categories.
| Section | Answers |
|---|---|
| The indicator record | Rule id, summary, category, evidence kind, matched text, optional address, ATT&CK ids, function hits — and no severity or score field |
| Category weights and tier floors | The downstream verdict layer scores 10–25 points per unique rule id, caps each category at 45, floors the tier on any family or targeting hit — and does not abstain on Apple-signed binaries |
| Rule categories | Persistence, Collection, Communication, Host interaction, Data manipulation, Anti-analysis, Malware family, Targeting — what each fires on, and the blind spot that follows |
| Match scope and rule gates | Everything matches at file scope, which is where the false positives come from; corroborator, count and length gates are what hold; the 923-rule catalog runs alongside and is never scored |
| Evidence kinds | Nine kinds — six from the baseline rules, three catalog-only — and the kind decides whether the hit carries an address |
| ATT&CK technique mapping | Sub-technique ids resolved against a pinned vendored snapshot; the 195 baseline rules reference 68 distinct techniques |
| Malware-family fingerprints | 28 plaintext string rules across 25 families: kinship, not attribution |
| Limits | Near-zero on Windows, no DGA detection, no decryption or unpacking, no scoring |
The indicator record
One match produces one flat record, and that record has nowhere to put a verdict.
| Field | Carries |
|---|---|
| Rule id | Stable identifier for the rule that matched |
| Summary | Plain-language statement of what the rule checks for |
| Category | One of eleven; eight are populated by the 195 baseline rules |
| Evidence | Discriminant naming which surface the match came from |
| Matched text | The needle, the joined needle list for a composite, or the algorithm name for a byte-pattern hit (aes, curve25519) |
| Address | The match's address, where one exists — a virtual address, except on PE section-byte hits, which report a file offset |
| ATT&CK | Zero or more MITRE technique ids |
| Function hits | The functions whose code references that address |
There is no severity field and no score field.
A 49,840-byte Mach-O dylib named libswiftos.dylib, shipped inside EvilEgg.zip and first seen 26 April 2026, produces exactly one record:
rule_id targeting.bundle_id_apple_domain
category Targeting
evidence Entitlement
matched_text com.apple.dt.runtime.swiftos
matched_va null
att_ck ["T1036.005"] Masquerading: Match Legitimate Name or Location
function_hits []
The code-signature identifier claims Apple's namespace. The signature is genuine and chains to the Apple root, but the leaf certificate reads Developer ID Application: Andrej Sevostopol (49LJX6DH22), and 49LJX6DH22 is not on the Apple team-id allowlist — the same allowlist the signing-consistency surface uses, so both surfaces cannot disagree about what "Apple-signed" means. There is no code address to point at, because the claim lives in the signature blob rather than in an instruction, so matched_va is null and function attribution is empty. That single row is the whole output for that file.
Category weights and tier floors
Weighting is downstream, and its denominator is distinct rule ids, never matches. A 20,172,924-byte unpacked Mach-O labelled IPStorm produces 30 hits: 8 Communication, 20 Data-manipulation, 1 Host-interaction, 1 Malware-family. The verdict layer's roll-up over that hit stream is two rows:
+40 malware.indicators_communication 4 unique Communication hits: cgi.helper_with_shell_exec,
communication.protocol_decoder_with_shell_exec,
communication.socket_fd_redirected_to_shell,
recon.external_ip_lookup
+25 malware.indicators_malware_family 1 unique MalwareFamily hits: family.ipstorm.stub
Eight Communication hits became four scoring units. Five separate recon.external_ip_lookup strings — ident.me, ifconfig.io, icanhazip.com, myexternalip.com, checkip.amazonaws.com, at five different addresses — count once.
| Category | Points per unique rule | Cap (points) | Tier floor |
|---|---|---|---|
| Communication | 10 | 45 | — |
| Collection | 10 | 45 | — |
| Anti-analysis | 15 | 45 | — |
| Malware family | 25 | 45 | Malicious |
| Targeting | 25 | 45 | LikelyMalicious |
| Persistence | 0 — no ledger row; feeds the co-occurrence rules | — | — |
| Host interaction | 0 — no ledger row; feeds the co-occurrence rules | — | — |
| Data manipulation | 0 — no ledger row; suppresses the decryptor-loop detector | — | — |
| Load code | 0 — no rules to fire | — | — |
The cap holds however many rules fire in a category. The two floors are applied outside the sum: any Malware-family hit forces the verdict to at least Malicious, any Targeting hit to at least LikelyMalicious. Tier boundaries are 15, 35 and 70 points, so the 10-point Communication step is coarse: two unique rules (+20) clear Benign, four (+40) reach LikelyMalicious, and the 45-point cap sits one rule above that line — ssh's three and IPStorm's four fall either side of that line.
The verdict layer's platform-signed abstention does not cover this roll-up, which is why ssh scores at all. A Mach-O whose CMS chain is anchored to an Apple root short-circuits 51 of the malware engine's detector modules and the entire co-occurrence layer to an empty result, and /usr/bin/ssh satisfies it exactly — signer Software Signing, Apple Inc., anchored. The roll-up checks only whether an indicator surface exists at all, so three Communication rule ids still become +30 on a first-party Apple binary. /usr/bin/curl comes off worse: two indicator records, 43 points, LikelyMalicious — only 10 of those points from this surface, the other 33 from a likely-packed high-entropy code section (+18) and a __TEXT,__const measuring 7.994 bits per byte (+15).
Persistence, Host-interaction and Data-manipulation score zero individually because they fire on curl, jq and ripgrep. A zero category produces no ledger row at all rather than a zero-point one.
Persistence and Host-interaction re-enter through five co-occurrence rules, each needing at least two predicates from distinct tactic families. Data manipulation feeds none of them. A crypto hit naming aes, chacha20, salsa20 or des instead short-circuits the decryptor-loop detector to an empty result, on the reasoning that a binary shipping a real cipher's tables runs that cipher's rounds rather than a hand-rolled string deobfuscator. Seven of IPStorm's twenty crypto hits name one of those four, so they do not contribute nothing — they switch a detector off.
| Co-occurrence rule | Predicates required | Points | Tier floor |
|---|---|---|---|
| Autonomous bot | Ingress transfer + persistence + network: raw socket, listener or outbound IP literal | 45 | LikelyMalicious |
| Staged implant | Discovery + ingress transfer + persistence | 45 | LikelyMalicious |
| Persistent evasive network process | Persistence + defense evasion + raw socket or outbound IP literal | 45 | LikelyMalicious |
| IoT/DDoS bot | Credential-gated telnet scan + ingress transfer + raw socket | 18 | — |
| Discovery with persistence | Discovery + persistence | 18 | — |
Rows are evaluated top to bottom and the first match wins, so at most one ever fires. The whole layer also goes inert when two or more high-confidence library matches name one of six benign tool brands — curl, wget, openssh, systemd, cron, ripgrep. The bottom two rows float free of the tier floors because a persistence-less telnet scanner and a weak-credential auditor are structurally the same binary. The discovery predicate carries its own scar: a lone uname or gethostname import, present in essentially every portable C binary, satisfied discovery-plus-persistence, so strong discovery now demands an OS-inventory path literal, a file-enumeration shape, or two distinct discovery symbols.
None of that weighting is part of the indicator surface; it lives in Malware, and the same hit stream is consumed unweighted by anything that wants a stable feature vector instead of a verdict.
Rule categories
Every category's blind spot follows directly from what it matches on.
| Category | Fires on | Blind spot |
|---|---|---|
| Persistence | 36 literal paths and env names: /Library/LaunchAgents/, /System/Library/LaunchDaemons/, /etc/systemd/system/, /var/spool/cron/, /etc/ld.so.preload, /.config/autostart/, DYLD_INSERT_LIBRARIES, LD_PRELOAD=, six shell-profile dotfiles | The path must exist as one contiguous string. A plist path assembled at run time from a directory constant and a filename constant matches nothing |
| Collection | Keychain (login.keychain-db, SecKeychainItemCopyContent), browser stores (Google/Chrome/Local State, Firefox/Profiles, Safari/History.db), NoteStore.sqlite, Messages/chat.db, NSPasteboard, CGWindowListCreateImage, kAudioUnitSubType_VoiceProcessingIO, and three co-occurrence rules — SecItemCopyMatching with Safe Storage, and two osascript credential-prompt shapes | Names the target, not the read. A clipboard manager and a stealer both produce collection.clipboard_pasteboard |
| Communication | 24 regexes over the string pool — IPv4-with-port, .onion, dynamic-DNS providers, punycode hostnames, Telegram-bot and Discord-webhook templates, pastebin and raw-GitHub, ngrok, Cloudflare Workers, DNS-over-HTTPS, Mirai and Mozi markers, stratum+tcp://, ICMP tunnel markers — plus 7 co-occurrence shapes | An IP literal is a string, not a connection. Nothing proves the address is ever passed to connect |
| Host interaction | 56 rules: shell invocations (/bin/sh -c, /usr/bin/osascript -e), post-compromise utilities (base64 -d, chmod +x, xattr -d com.apple.quarantine, SetFile -a V, spctl --master-disable), reverse-shell tokens (nc -e, /dev/tcp/, mkfifo), setuid escalation (chmod 4755, chmod u+s), anti-forensics (HISTFILE=/dev/null, /var/log/wtmp), BusyBox ingress, eleven system-inventory probes, three file-enumeration shapes, one prctl kernel-thread masquerade, and three browser-engine identity composites (Chromium, V8, WebKit) that name a runtime rather than a behaviour | The largest and the least discriminating category. It scores zero on its own for exactly this reason |
| Data manipulation | 17 byte patterns scanned over the non-executable sections: AES forward and inverse S-boxes, the Rcon table, the AES T-tables, MD5 / SHA-1 / SHA-256 / SHA-512 init words, the SHA-256 round constants, the ChaCha20 and Salsa20 expand 32-byte k sigma, the Curve25519 base point, both secp256k1 generator coordinates, DES S-boxes S1 and S2 | Only S1 and S2 of DES's eight boxes; no Blowfish, no RC4. Executable bytes are excluded — the whole __TEXT segment on Mach-O, .text on PE, any executable-mapped section on ELF — because instruction streams contain arbitrary four-byte runs that trip short S-box heads constantly. On Mach-O the exclusion is by segment, not by section, and it is not free: /usr/bin/ssh carries the Curve25519 base point in __TEXT,__const and reports no crypto hit for it. On PE the exclusion is by section name, so code in UPX1 or any renamed section is scanned in full. Mach-O leaks the other way: a zero-fill section (__bss, __common) records file offset 0, so the scan window becomes the image header and __TEXT — ssh's 1,750-byte __DATA,__bss and 5,504-byte __DATA,__common are both read that way, and a hit there is labelled with a section it did not come from |
| Anti-analysis | 13 rules for artifacts with no purpose but environment detection: the 12-byte CPUID hypervisor brands, VirtualBox and VMware guest device paths, Windows VM registry keys, DMI and sysfs hypervisor probes, VM MAC OUI prefixes, Wine exports, container sentinels, sandbox monitor DLLs and pipes, ThreadHideFromDebugger / PTRACE_TRACEME / TracerPid | Bare VMware, VirtualBox, QEMU, IsDebuggerPresent and /proc/1/cgroup are excluded as too dual-use, so a check written against a bare product name is missed by design |
| Malware family | 28 string fingerprints across 25 families | String kinship, not attribution. Every needle is plaintext, so an encrypted or packed build of the same family carries none of them |
| Targeting | One rule: a com.apple.* code-signature identifier on a binary that is neither Apple-platform-signed (a nonzero CodeDirectory platform byte) nor signed by a team id on Apple's allowlist | Only Apple impersonation. A binary impersonating a non-Apple brand produces nothing here |
| Load code | Empty table | Dynamic-load facts come from three other surfaces: resolved, computed and unresolved call sites with full address provenance, on the dynamic-load surface; the Findings CWE-94 detectors, which anchor per call site; and the Capabilities catalog's 33 load-code rules |
Two further categories — Impact (wipers, shadow-copy deletion, defense disabling) and Exploitation (kernel gadget resolution, driver loading, heap spraying) — exist in the taxonomy but are populated only by the capability catalog, and thinly: 16 Impact rules and 4 Exploitation rules out of that catalog's 923. A consumer can rely on the eleven-element enum regardless of which surface produced a hit.
Match scope and rule gates
Every baseline rule matches at file scope: a needle is present or absent in the whole binary, and a co-occurrence rule asks whether two needles are both present somewhere, not whether they are near each other. Composite hits carry the joined needle list as their matched text and the address of whichever needle was seen first, so socket + connect + dup + system is not an assertion that those four appear in one function. Most of this surface's errors follow from that.
What that costs, measured on three Apple binaries. The ls and curl rows are those binaries' complete indicator output — one record and two; the ssh row is one of the six at the top:
/bin/ls discovery.files_recursive_walk Import fts_open
/usr/bin/curl cgi.input_with_unsafe_strings RawString CONTENT_LENGTH + strcpy
generic.curl_retry RawString curl + --retry + --retry-delay
/usr/bin/ssh communication.socket_fd_redirected_to_shell Import socket + connect + dup + system
curl implements --retry, so its help text names the flag; it handles CGI environment variables and links strcpy. The rule that flags a CGI handler reading attacker input with unsafe string primitives is describing curl's own documentation. ssh genuinely imports bind, listen and accept for port forwarding, which is what promotes its plist XML preamble to a protocol-decoder marker.
The rules that survive contact with benign software are the ones with an explicit second gate.
| Gate | Rule | Second condition | What forced the gate |
|---|---|---|---|
| Corroborator | c2.http_data_post | A C2-shaped host was already matched | Every REST client embeds Content-Type: application/x-www-form-urlencoded |
| Corroborator | c2.dns_covert_channel | TXT-record parsing, a DNS-over-HTTPS endpoint, or a base32 subdomain alphabet | A raw libresolv import (res_query, ns_parserr, dn_expand) on its own yields an unscored provenance fact; the gate is what keeps dig and nslookup inert |
| Corroborator | The <?xml version preamble, demoted to a weak marker | A server-side socket import — bind, listen, accept, accept4, the connectionless recvfrom / recvmsg / recvmmsg, or the Winsock WSAAccept / WSARecv / WSARecvFrom — so a UDP receiver with no listening socket satisfies it | Apple's Weather app tripped it with zero listening sockets and all traffic outbound to weather.apple.com |
| Count | evasion.analysis_tool_scan | 2 distinct names from a 25-name roster, three-character word-boundary names like gdb and ida included | Naming one debugger or packet sniffer proves nothing |
| Count | evasion.security_product_scan | 3 distinct names from a 26-name roster, every name at least four characters | A stricter bar because inventory tooling legitimately lists one or two AV products |
| Length anchor | The six shell-profile dotfile needles | Unanchored only inside observations of 128 bytes or less; longer than that they must be preceded by / or ~ | ripgrep's file-type glob table lists .zshrc and *.bashrc as type names inside one large merged blob, and matched as persistence without it |
Both rosters were pruned by hand. The four-character bar excludes short ambiguous AV tokens like avp, and egui — a widely used Rust GUI crate — was left off rather than false-positive across an ecosystem. The analysis-tool roster dropped only r2, a common CPU register name, with radare2 standing in for it.
Rules with no such gate are exactly where the noise is. persistence.users_shared is the literal /Users/Shared/. crypto.curve25519_basepoint is the byte 0x09 followed by 31 zero bytes, a shape any zero-padded small constant reproduces: on the IPStorm sample it fired 13 times, at 13 addresses, four in __DATA,__noptrdata and nine in __DATA,__data.
Function-scoped and basic-block-scoped matching exists, but not here. The baseline has nothing but file scope; the Capabilities catalog's 923 rules spread across four scopes:
| Catalog rule scope | Rules |
|---|---|
| Function | 630 |
| Basic block | 144 |
| Whole file | 144 |
Non-binary container — OLE document, .lnk shortcut, browser extension, installer | 5 |
The two run together and the baseline wins on rule-id collision — a catalog hit whose id already fired at baseline is dropped, so the same behaviour is never counted twice.
None of the catalog's 923 rules is scored: catalog hits land in a separate enrichment surface with no verdict exposure, and only the baseline surface is weighted. The one re-entry path is a curated promotion table of 35 capability ids, each carrying the strength band of the hand-written detector it replaced, unchanged since 20 July 2026.
Evidence kinds
The evidence discriminant names which surface produced the matched text, and it decides whether the hit has an address at all. Nine kinds exist; six can be produced by the baseline rules, three only by the capability catalog.
| Evidence | Produced by | Source | Address |
|---|---|---|---|
| Raw string | Baseline | C-string pool | The string's VA, and the functions that reference it via the reverse data-xref index |
| CFString | Baseline | Decoded CFString body | The struct's VA, same attribution |
| Selector | Baseline | Objective-C selector name | None — selector VAs are not threaded through the method records, so every selector hit is unanchored |
| Import | Baseline | Import symbol, unioned with the direct-syscall surface so a stripped static ELF still matches. Also stamped on exported symbols — the record has no separate export kind, so a reader cannot tell the two apart | None. Import-evidence hits carry no address and no function attribution at all |
| Entitlement | Baseline | Code-signature identifier or entitlement key | None — the claim is metadata, not code |
| Section bytes | Baseline | Byte-pattern match in a named non-executable section | Section base plus match offset — an absolute VA on Mach-O and ELF; on PE the recorded base is the section's file offset, so every PE byte hit reports a file offset in an address field |
| Recovered string | Catalog | Plaintext produced by a family-specific static decryptor | None — the decryptor's output has no location in the file |
| Stack string | Catalog | Text reassembled from a run of constant stores into a function's frame | The constructing function's entry, because no data address exists |
| Go import path | Catalog | A module the linker recorded, or a package compiled into the image | A provenance claim, not a byte location |
Stamping any of the last three as a raw string would over-claim. A recovered string existed on disk only encrypted, so a benign binary carrying the same words in cleartext can never produce one. A stack string was never a contiguous byte run anywhere in the file. A Go import path asserts provenance rather than "these characters occur somewhere".
ATT&CK technique mapping
Rules declare canonical technique ids in sub-technique form, resolved against a vendored snapshot that is regenerated from a pinned MITRE CTI commit rather than fetched. The snapshot holds three tables.
| Table | Size | Contents |
|---|---|---|
| macOS techniques | 430 | Techniques with macOS in their platform list |
| Enterprise techniques | 835 | Enterprise techniques across all platforms |
| Native-binary set | 712 | Enterprise techniques on a compiled platform, minus the pre-compromise tactics a binary cannot exhibit; the denominator for coverage accounting |
430 is the size of the taxonomy, not a claim about coverage. The 195 baseline rules reference 68 distinct techniques between them; the capability catalog references 184. A technique row with no referencing rule is a lookup entry and nothing else.
Four of those 68 are not macOS techniques and resolve through the full enterprise table.
| Technique | Name |
|---|---|
T1003.008 | /etc/passwd and /etc/shadow |
T1543.002 | Systemd Service |
T1547.013 | XDG Autostart Entries |
T1583.001 | Domains |
T1583.001 sits in the resource-development tactic, which the twelve-bucket per-tactic vector deliberately omits along with reconnaissance, so c2.idn_punycode_hostname — the rule that declares it — contributes to a sentinel count rather than to a tactic column.
Declarations, in the form every rule uses:
| Rule | Techniques |
|---|---|
persistence.launch_agent | T1543.001 — Create or Modify System Process: Launch Agent |
shell.curl_pipe_sh | T1105 + T1059.004 — Ingress Tool Transfer + Unix Shell |
crypto.aes_sbox_forward | T1027 + T1573.001 — Obfuscated Files or Information + Symmetric Cryptography |
collection.keychain_login_db | T1555.001 — Credentials from Password Stores: Keychain |
targeting.bundle_id_apple_domain | T1036.005 — Masquerading: Match Legitimate Name or Location |
Malware-family fingerprints
Twenty-eight rules cover twenty-five families: eight curated from Patrick Wardle's Objective-See 2025 macOS malware roundup, twenty older stubs. Four of the eight roundup rules require several needles to co-occur; the other four are single needles.
| Family | Needles required | Source |
|---|---|---|
| DigitStealer | 8.8.8.8 + dig + TXT | 2025 roundup |
| NightPaw | codesign --entitlements + --force | 2025 roundup |
| AMOS (first rule) | An AppleScript with hidden answer prompt + login.keychain-db | 2025 roundup |
| AMOS (second rule) | Exodus/exodus.wallet/ + Telegram Desktop/tdata/ | 2025 roundup |
| ChillyHell | Three separate one-predicate rules: com.apple.qtop.plist, Utils::RunCommand, Utils::KillProcess | 2025 roundup |
| Phexia | com.user.gfskjsnghdjsvuxj.plist | 2025 roundup |
| Shlayer | flashupdate + curl + shasum | Legacy stub |
| OSAMiner | minergate + xmr | Legacy stub |
| IPStorm | storm_ + p2p | Legacy stub |
| CloudMensis, XCSSET, CrescentCore, Silver Sparrow, Pirrit, Dacls, KeRanger, EvilQuest, WindTail, FruitFly, MacRansom, OceanLotus, Komplex, Snake, XAgent, Lazarus, WireLurker | A single string each | Legacy stubs (17) |
family.ipstorm.stub is the weakest rule in the table, and it is the one that fired in the ledger above — two short tokens, present anywhere in a 20 MB binary, worth 25 points and a hard floor to Malicious.
A family hit means this binary shares strings with samples attributed to family X. It does not mean the binary is family X. Most AMOS builds ship encrypted, so the plaintext rules only reach the unpacked variants; attributing the packed bulk is a similarity problem, and the corpus-relative nearest-neighbour classifier that does it lives in Malware rather than here, because a claim about a binary's neighbours is not a fact about its bytes.
Limits
Windows. The rule table is shaped around Unix paths, shell tokens and Mach-O signing, and most Windows samples produce nothing at all. Measured over every Windows sample in the malware training corpus — 344 .exe and 32 .dll of its 1,729 samples, of which 375 completed and one 4.5 MB executable did not finish inside a five-minute analysis budget:
| Records produced | Samples |
|---|---|
| 0 | 259 |
| 1 | 49 |
| 2 | 32 |
| 3–9 | 18 |
| 10–62 | 15 |
| 599 and 601 | 2 — 25 MB executables, 577 of each being crypto.curve25519_basepoint, the ungated zero-run rule above |
116 of the 375 produced any record at all. The tail is not Windows coverage, it is one rule misfiring at scale: crypto.curve25519_basepoint accounts for 1,560 of the 1,789 records emitted across the whole PE corpus, and crypto constants for 1,664 of them. Dropping the two 25 MB outliers barely moves the ratio: crypto constants are 406 of the remaining 589 records. Windows coverage on this surface is carried almost entirely by the capability catalog.
No decryption or unpacking. The two narrow exceptions — family-specific static decryptors and stack-string reconstruction — belong to the catalog and surface under their own evidence kinds, so neither can pass as a cleartext match. Unpacking happens upstream in the pipeline; Malware covers how a verdict follows the unpacked payload rather than the stub.
No DGA detection. The hostname rules name specific abused providers — duckdns, no-ip, hopto, zapto, ddns, dynu, ngrok, workers.dev, pastebin.com/raw/, raw.githubusercontent.com, api.telegram.org/bot, Discord webhooks — plus .onion and Punycode labels. There is no suspicious-TLD rule and no generated-domain scoring: a freshly registered algorithmic .xyz hostname matches nothing here.
No embedded YARA engine, no live ATT&CK fetch, no rule feed. Baseline rules are in-tree constants and the catalog is TOML compiled in at build time; both are diffable in git and versioned with the engine, and updates ship with engine releases. One runtime rule directory does exist — an operator-supplied override layered over the embedded catalog at startup, aborting the boot if it fails to parse — but it feeds the CWE catalog behind Findings, not this surface.
No scoring, severity or model. The surface emits features. Weighting, tier floors and verdicts are downstream and separable.
Related briefs
- Capabilities — the TOML catalog that extends this taxonomy.
- Findings — the CWE catalog.
- Malware — the verdict layer that consumes these hits.
- CVEs & SBOM — the version-based surface that runs on the same input.