feat(signatures): add the malware identity table, drawn from source not memory

The last missing data file. Seven known-bad-identity signatures over four
families - webshell, reverse_shell, cryptominer, hacktool - reproduced
verbatim from llm-security/knowledge/signatures.json at b0de0ca, key order
included. The file was generated from the parsed source rather than typed,
and provenance pins the source's byte length (2494) and SHA-256 so the
claim is checkable rather than asserted.

Note the family spellings: reverse_shell with an underscore, and
cryptominer rather than miner. The working note this file was planned from
had both wrong. They are policy keys - the engine filters on them and
interpolates them into every finding title - so a rename is a breaking
change, which is exactly why the table was read instead of recalled.

The rules were the easy half. The substance is engine_behaviour_not_data,
which draws the line between the table and the runtime around it. No rule
carries a flags field, because the engine compiles every pattern with `i`
unconditionally at signature-scanner.mjs:48 - so a consumer compiling these
case-sensitively silently under-matches all seven, and the dialect block
records that where a reader will hit it. Also engine, not data: matching
against five decode variants rather than raw bytes, the enabled-families
policy filter, per-file rule dedup, custom-rule merging, and a loader that
defaults four missing fields instead of rejecting a rule.

Two limits are stated as evidence limits rather than left implied. Seven
signatures are not malware coverage; a clean SIG result is not "no
malware", and the seed runtime's own header calls the table deliberately
tight. And three of the seven match on names - xmrig, mimikatz,
meterpreter - so a document discussing those tools matches. The seed
runtime hides that by excluding knowledge/, tests/, docs/ and
node_modules/, which is scan scoping and does not travel with the table.

Verified: 7/7 rule objects field-identical to source including key order,
no non-ASCII bytes, all seven compile in Node bare, i and iu (21/21) and in
Python re (7/7). Charter guard clean - no executable code in the repository.

README and CHANGELOG updated: the file moves out of "planned, not in
v0.1.0" and out of "not included".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNMcqrfNyoLRQ7qXUFZnb9
This commit is contained in:
Kjell Tore Guttormsen 2026-08-09 22:49:21 +02:00
commit 47760d2264
3 changed files with 204 additions and 3 deletions

View file

@ -0,0 +1,177 @@
{
"version": "0.1.0",
"id": "malware-signatures",
"description": "Known-bad-IDENTITY signatures: seven high-confidence regexes naming specific malware families - PHP webshells, reverse shells, cryptominers and offensive-security tooling. Deliberately small and tight. Complementary to the shape-based tables in this repository (entropy, taint, injection): those describe what an attack looks like, this one names things already known to be malicious. Detection data only.",
"owasp": "LLM03",
"$comment": "Extracted without behaviour change from llm-security/knowledge/signatures.json, read directly from the public remote at commit b0de0ca rather than transcribed. The seven rules are reproduced verbatim, key order included. What the seed runtime DOES with them - compiling every pattern case-insensitively, running each against a decode pipeline as well as raw bytes, filtering by an enabled-families policy, excluding certain paths, merging operator-supplied custom rules - is engine behaviour and is recorded under engine_behaviour_not_data rather than treated as part of the table.",
"provenance": {
"source_repo": "llm-security",
"source_files": [
"knowledge/signatures.json"
],
"source_consumer": "scanners/signature-scanner.mjs",
"source_version": "1.0",
"source_commit": "b0de0ca6d86ce697f39669d177c2c2654c280128",
"source_remote": "ssh://git@git.fromaitochitta.com/open/llm-security.git",
"source_bytes": 2494,
"source_sha256": "0155368f194c51e01f48e5b253067282a9c083549401a2d374afc46bb89c66bc",
"source_delivery": "read directly from the pinned commit; never transcribed through a dump",
"verified": "differentially - the seven rule objects here are compared field by field against the parsed source file, and every pattern string is byte-identical, 2026-08-09",
"evidence_limits": [
"This is an identity table, not a coverage claim. Seven signatures do not detect malware generally; they detect these seven things. A consumer that presents a clean SIG result as 'no malware' is overstating it, and the seed runtime's own header calls the table 'deliberately tight to minimize false positives'.",
"Three of the seven match on NAMES rather than on behaviour (`xmrig`, `mimikatz`, `meterpreter` and their kin). A document that merely discusses those tools matches. The seed runtime mitigates this by excluding knowledge/, tests/, docs/ and node_modules/ from the scan - a path exclusion, which is engine behaviour and does not travel with this table. A consumer without an equivalent exclusion will produce false positives this table's own severities do not warn about.",
"The provenance strings on each rule name a technique family or a public cheat sheet, not a specific sample or hash. They are attribution for the pattern's origin, not evidence that a given match is that malware."
]
},
"dialect": {
"name": "ecmascript",
"$comment": "Patterns are ECMAScript regular-expression source text exactly as the source file spells it. Unlike lexicon/injection-lexicon.json, NO rule carries a `flags` field: the engine compiles every pattern with `i` unconditionally (signature-scanner.mjs:48, `new RegExp(rule.pattern, 'i')`). Case-insensitivity is therefore engine behaviour applied to the whole table, not per-rule data. A consumer that compiles these patterns case-SENSITIVELY will silently under-match every one of them.",
"flags_applied_by_engine": "i",
"flags_declared_per_rule": "none - there is no flags field in this table",
"features_used": [
"non-capturing groups: (?:...)",
"word boundaries: \\b",
"character classes",
"negated character classes: [^...]",
"optional quantifiers: ? and *"
],
"translation_notes": [
"Python (`re`): compile with re.I. No rewriting is needed; verified by compiling all seven.",
"All seven compile in Node bare, in Node with `i`, and in Node with `iu` - 21/21 - and in Python `re` with re.I, 7/7. No pattern uses a construct that differs between the two engines.",
"No pattern contains a non-ASCII byte, and none contains the redundant `\\/` escape that appears in the other two pattern tables in this repository."
]
},
"normalisations": [],
"normalisations_note": "Empty by result, not by omission: all seven patterns are byte-identical to the source file, verified below. No escaping change was needed.",
"families": {
"$comment": "The family a rule belongs to. This is data - it is the `family` field on each rule, and the seed engine both filters on it and interpolates it into the finding title, evidence and recommendation. Note the exact spellings: `reverse_shell` with an underscore, and `cryptominer` rather than `miner`. A consumer that renames a family breaks the policy key that enables it.",
"values": [
"cryptominer",
"hacktool",
"reverse_shell",
"webshell"
],
"rule_counts": {
"cryptominer": 2,
"hacktool": 1,
"reverse_shell": 2,
"webshell": 2
},
"default_enabled": [
"webshell",
"reverse_shell",
"cryptominer",
"hacktool"
],
"default_enabled_note": "All four are enabled by default in the seed runtime (signature-scanner.mjs:30, DEFAULT_FAMILIES), in that order. Recorded because a consumer needs to know the table is fully active by default rather than opt-in; which families a given deployment enables is policy, not data."
},
"severity_vocabulary": {
"used_here": [
"critical",
"high"
],
"contract": "schema/finding.schema.json#/$defs/severity",
"$comment": "Only two of the five contract severities appear in this table. That is the table's own distribution, not a restriction on the vocabulary."
},
"rules": [
{
"id": "SIG-WEBSHELL-001",
"family": "webshell",
"severity": "critical",
"pattern": "(?:eval|assert|system|exec|passthru|shell_exec|popen|proc_open)\\s*\\(\\s*\\$_(?:POST|GET|REQUEST|COOKIE|SERVER)",
"description": "PHP webshell: executes attacker-controlled request data",
"provenance": "Classic PHP webshell pattern (c99/r57/b374k families)"
},
{
"id": "SIG-WEBSHELL-002",
"family": "webshell",
"severity": "high",
"pattern": "\\$_(?:POST|GET|REQUEST|COOKIE)\\s*\\[[^\\]]*\\]\\s*\\(",
"description": "PHP variable-function call on request data (obfuscated webshell)",
"provenance": "Variable-function webshell obfuscation"
},
{
"id": "SIG-REVSHELL-001",
"family": "reverse_shell",
"severity": "critical",
"pattern": "(?:bash|sh)\\s+-i\\s*>&?\\s*/dev/tcp/",
"description": "Bash /dev/tcp reverse shell",
"provenance": "PentestMonkey reverse-shell cheat sheet"
},
{
"id": "SIG-REVSHELL-002",
"family": "reverse_shell",
"severity": "critical",
"pattern": "\\bnc\\s+-[a-z]*e[a-z]*\\s+/(?:bin|usr/bin)/(?:sh|bash)\\b",
"description": "Netcat -e reverse shell",
"provenance": "Netcat reverse-shell one-liner"
},
{
"id": "SIG-MINER-001",
"family": "cryptominer",
"severity": "high",
"pattern": "stratum\\+(?:tcp|ssl)://",
"description": "Cryptominer stratum pool URL",
"provenance": "Stratum mining protocol"
},
{
"id": "SIG-MINER-002",
"family": "cryptominer",
"severity": "high",
"pattern": "\\b(?:xmrig|minerd|cgminer|ccminer|cpuminer)\\b",
"description": "Known cryptominer binary reference",
"provenance": "Common CPU/GPU miner binaries"
},
{
"id": "SIG-HACKTOOL-001",
"family": "hacktool",
"severity": "high",
"pattern": "\\b(?:mimikatz|meterpreter|sekurlsa::|lsadump::)\\b",
"description": "Offensive-security tooling reference",
"provenance": "Mimikatz / Metasploit Meterpreter"
}
],
"engine_behaviour_not_data": [
{
"id": "case-insensitive-compile",
"behaviour": "Every pattern is compiled with the `i` flag, unconditionally, at signature-scanner.mjs:48.",
"why_not_data": "It is applied to the table rather than declared by it. It is recorded under dialect.flags_applied_by_engine because a consumer cannot reproduce the seed behaviour without it."
},
{
"id": "decode-variant-matching",
"behaviour": "Each pattern is tested against five variants of a file's content, not just the raw bytes: raw, normalizeForScan with embedded decoding, the same on trimmed input, homoglyph-folded, and rot13 (signature-scanner.mjs:102-115). The first variant that matches wins, and a match on a non-raw variant is reported as obfuscated.",
"why_not_data": "It is the decode pipeline, which this repository does not own and deliberately does not specify. A consumer matching only raw bytes will miss obfuscated instances of these same seven signatures."
},
{
"id": "family-filter",
"behaviour": "Rules are filtered to the families named by the `sig.enabled_families` policy key before any file is read; if none are enabled the scanner returns no findings at all.",
"why_not_data": "Policy."
},
{
"id": "path-exclusion",
"behaviour": "Paths under knowledge/, tests/, docs/ and node_modules/ are skipped, because a ruleset, a fixture and a document about webshells all legitimately contain these patterns.",
"why_not_data": "Scan scoping, not detection. But see evidence_limits: without an equivalent, three of these seven rules are noisy by construction."
},
{
"id": "per-file-rule-dedup",
"behaviour": "A rule fires at most once per file, regardless of how many times it matches.",
"why_not_data": "Reporting behaviour. It means a finding count is a count of (file, rule) pairs, not of occurrences."
},
{
"id": "custom-rule-merge",
"behaviour": "Operator-supplied rules from `sig.custom_rules_path` are compiled by the same code and appended to this table at scan time.",
"why_not_data": "Runtime extension. A consumer matching only this table matches LESS than a seed deployment with custom rules loaded."
},
{
"id": "missing-field-defaults",
"behaviour": "The loader defaults a missing `family` to 'unknown', a missing `severity` to 'high', a missing `description` to the rule id, and a missing `provenance` to null, and drops any rule lacking `id` or `pattern` or whose pattern fails to compile.",
"why_not_data": "Loader tolerance. Every rule in this table carries all six fields, so no default is exercised - but a consumer writing a validator should not mistake the loader's leniency for an optional-field contract."
}
],
"totals": {
"rules": 7,
"families": 4,
"critical": 3,
"high": 4
}
}