Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 30 additions & 17 deletions src/malwar/monitor/escalation.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,29 +40,42 @@
# MALICIOUS threshold in ScanResult.verdict.)
_MALICIOUS_RISK = 75

# Rules that, on a single line match, are known to over-flag legitimate content
# (each confirmed by running the rule against real benign inputs):
# * MALWAR-CMD-001 fires on any ``curl ... | sh`` -- the documented install path
# for a large share of legitimate dev tools (installer-host allowlisting in
# the rule handles the dedicated-domain cases; multi-tenant hosts remain).
# * MALWAR-ENV-001's broad pattern matches ordinary prose ("the key you need
# to set", "by running env").
# * MALWAR-PERSIST-002 matches self-referential file ops on SKILL.md/CLAUDE.md/
# .claude/, including *reads* ("cat SKILL.md | grep") and legitimate
# skill-authoring tools that write a CLAUDE.md.
# * MALWAR-MULTI-001 matches benign prose about deferred/quiet execution
# Rules that, on a single line match, are known to over-flag legitimate content.
# Each was confirmed by running the rule against real benign inputs (see the
# residual-verdict audit in issue #30):
# * MALWAR-CMD-001 ``curl ... | sh`` -- the documented install path for a
# large share of legitimate dev tools (installer-host allowlisting handles
# dedicated-domain cases; multi-tenant hosts remain).
# * MALWAR-ENV-001 ordinary prose ("the key you need to set", "run env").
# * MALWAR-PERSIST-002 self-referential SKILL.md/CLAUDE.md/.claude file ops,
# including reads ("cat SKILL.md | grep") and legit skill-authoring tools.
# * MALWAR-MULTI-001 benign prose about deferred/quiet execution
# ("applies the patch without showing the full diff").
# A MALICIOUS verdict resting on a *single* one of these rules is fragile: it
# has never been corroborated by a second rule or a semantic pass. Such verdicts
# are re-checked (escalated) and, if not authoritatively confirmed, downgraded
# to SUSPICIOUS rather than published as a confident conviction. Tighter,
# lower-FP rules (e.g. MALWAR-PERSIST-001 for cron/systemd/.bashrc persistence)
# are deliberately NOT listed: a single hit from them stays a confident verdict.
# * MALWAR-CMD-002 ``npx -y <pkg>`` -- the standard way to run MCP servers
# and CLI tools; a few in one skill push it over the MALICIOUS line.
# * MALWAR-CRED-002 onboarding prose ("enter your API key", "paste your token").
# * MALWAR-EXFIL-001 reads of ~/.claude/ and .cursor/ that skill-tooling does.
# * MALWAR-EXFIL-003 ``curl -X POST -d "$(cat file)"`` -- a normal API upload.
# * MALWAR-PI-001 security/detection skills that quote injection phrases.
# * MALWAR-HIJACK-001 roleplay/persona skills ("you are now...", "your new role is").
# A MALICIOUS verdict resting on a *single* one of these rules is fragile: one
# regex, no corroboration, no semantic read. Such verdicts are re-checked
# (escalated) and, if not authoritatively confirmed, downgraded to SUSPICIOUS
# rather than published as a conviction. Tighter, low-FP rules are deliberately
# NOT listed and stay confident on a single hit: e.g. MALWAR-PERSIST-001
# (cron/systemd/.bashrc), MALWAR-EXFIL-002 (crypto-wallet access) and
# MALWAR-FRAUD-002 (agentic front-running).
HIGH_FP_RULES: frozenset[str] = frozenset({
"MALWAR-CMD-001",
"MALWAR-ENV-001",
"MALWAR-PERSIST-002",
"MALWAR-MULTI-001",
"MALWAR-CMD-002",
"MALWAR-CRED-002",
"MALWAR-EXFIL-001",
"MALWAR-EXFIL-003",
"MALWAR-PI-001",
"MALWAR-HIJACK-001",
})


Expand Down
38 changes: 24 additions & 14 deletions tests/unit/test_escalation.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,35 @@ def mrec(rules: list[str], *, verdict: str = "MALICIOUS", risk: int = 92):

class TestFragileMalicious:
def test_single_high_fp_rule_is_fragile(self):
assert is_fragile_malicious(mrec(["MALWAR-CMD-001"]))
assert is_fragile_malicious(mrec(["MALWAR-ENV-001"]))
# Added after auditing the residual single-rule MALICIOUS tier: these
# two over-flag legitimate content (self-referential SKILL.md file ops;
# benign "without showing output" prose).
assert is_fragile_malicious(mrec(["MALWAR-PERSIST-002"]))
assert is_fragile_malicious(mrec(["MALWAR-MULTI-001"]))

def test_tight_persistence_rule_stays_confident(self):
# MALWAR-PERSIST-001 (cron/systemd/.bashrc) did not over-flag in testing;
# a single hit remains a confident verdict, not fragile.
assert not is_fragile_malicious(mrec(["MALWAR-PERSIST-001"]))
# Every rule confirmed to over-flag legitimate content in the issue #30
# residual-verdict audit is treated as fragile on a single-rule hit.
for rid in (
"MALWAR-CMD-001",
"MALWAR-ENV-001",
"MALWAR-PERSIST-002",
"MALWAR-MULTI-001",
"MALWAR-CMD-002",
"MALWAR-CRED-002",
"MALWAR-EXFIL-001",
"MALWAR-EXFIL-003",
"MALWAR-PI-001",
"MALWAR-HIJACK-001",
):
assert is_fragile_malicious(mrec([rid])), rid

def test_tight_rules_stay_confident(self):
# These did not over-flag in testing; a single hit remains a confident
# verdict, not fragile.
for rid in ("MALWAR-PERSIST-001", "MALWAR-EXFIL-002", "MALWAR-FRAUD-002"):
assert not is_fragile_malicious(mrec([rid])), rid

def test_corroborated_two_rules_not_fragile(self):
assert not is_fragile_malicious(mrec(["MALWAR-CMD-001", "MALWAR-PERSIST-002"]))

def test_single_low_fp_rule_not_fragile(self):
# A single non-high-FP rule (e.g. exfiltration) is evidence-based; keep it.
assert not is_fragile_malicious(mrec(["MALWAR-EXFIL-001"]))
# A single low-FP rule (e.g. crypto-wallet exfiltration) is evidence-based;
# keep it a confident verdict.
assert not is_fragile_malicious(mrec(["MALWAR-EXFIL-002"]))

def test_non_malicious_verdict_not_fragile(self):
assert not is_fragile_malicious(mrec(["MALWAR-CMD-001"], verdict="SUSPICIOUS", risk=40))
Expand Down
Loading