feat(ttp): E.3.11 CanaryFingerprintLifter (R0049-R0053)
Browser-payload derivations per Appendix A.9: navigator.webdriver flag, canvas/audio/WebGL automation hash matches (Puppeteer/Playwright/ Selenium/curl-impersonate), WebRTC IP leak, TZ/language vs source-IP geo mismatch, navigator.platform vs userAgent vs WebGL renderer inconsistency. Evidence shape pinned to CanaryFingerprintEvidence (metric + matched_signature) — raw fingerprint blobs (canvas hashes, full UAs, navigator.platform values) explicitly NOT carried into TTPTag.evidence per TTP_TAGGING.md §'Hard parts §7' (enrichment vs tag boundary). The identity-merge guard rail is preserved: composite fp.id matches across IPs are NOT a TTP, so no rule fires on the bare hash. Tests: tests/ttp/test_canary_fingerprint_lifter.py per-rule positive + negative + evidence-shape guard + state modulation. tests/ttp/rule_precision/test_canary_rules.py xfail flipped to real precision (R0049/R0050/R0051/R0053 H-band ≥95%; R0052 M-band ≥80%).
This commit is contained in:
@@ -1,2 +1,7 @@
|
||||
{"source_kind": "canary_fingerprint", "payload": {"ua_signature": "HeadlessChrome/119", "navigator_webdriver": true}, "expected_rule_ids": ["R0049"], "label": "webdriver_flag"}
|
||||
{"source_kind": "canary_fingerprint", "payload": {"canvas_audio_hash_match": "puppeteer"}, "expected_rule_ids": ["R0050"], "label": "puppeteer_canvas"}
|
||||
{"source_kind": "canary_fingerprint", "payload": {"canvas_audio_hash_match": "selenium"}, "expected_rule_ids": ["R0050"], "label": "selenium_canvas"}
|
||||
{"source_kind": "canary_fingerprint", "payload": {"webrtc_geo_mismatch": true}, "expected_rule_ids": ["R0051"], "label": "webrtc_leak"}
|
||||
{"source_kind": "canary_fingerprint", "payload": {"tz_mismatch_zones": 7}, "expected_rule_ids": ["R0052"], "label": "tz_mismatch"}
|
||||
{"source_kind": "canary_fingerprint", "payload": {"platform_ua_inconsistent": true}, "expected_rule_ids": ["R0053"], "label": "platform_inconsistent"}
|
||||
{"source_kind": "canary_fingerprint", "payload": {"ua_signature": "Mozilla/5.0", "navigator_webdriver": false}, "expected_rule_ids": [], "label": "negative_browser"}
|
||||
|
||||
@@ -44,9 +44,45 @@ async def test_lifter_bound_inert_in_v0(
|
||||
assert rule_id not in fired
|
||||
|
||||
|
||||
def _build_lifter() -> "CanaryFingerprintLifter":
|
||||
from decnet.ttp.impl.canary_fingerprint_lifter import (
|
||||
CanaryFingerprintLifter,
|
||||
)
|
||||
from tests.ttp._stub_store import StubRuleStore
|
||||
|
||||
rules = [
|
||||
_parse_and_compile(Path("rules/ttp") / f"{rid}.yaml", RuleState())
|
||||
for rid in _RULE_IDS
|
||||
]
|
||||
lifter = CanaryFingerprintLifter(StubRuleStore(compiled=rules))
|
||||
for rule in rules:
|
||||
lifter._index.install(rule)
|
||||
return lifter
|
||||
|
||||
|
||||
@pytest.mark.parametrize("rule_id", _RULE_IDS)
|
||||
@pytest.mark.xfail(
|
||||
strict=True, reason="impl phase E.3.11 (CanaryFingerprintLifter)",
|
||||
)
|
||||
def test_canary_rule_precision(rule_id: str) -> None:
|
||||
pytest.fail(f"{rule_id}: CanaryFingerprintLifter not yet shipped (E.3.11)")
|
||||
def test_canary_rule_precision(
|
||||
rule_id: str,
|
||||
corpus_loader: CohortLoader,
|
||||
) -> None:
|
||||
"""E.3.11 — drive CanaryFingerprintLifter over the labelled corpus
|
||||
and assert per-rule precision (H-band rules → ≥95%, M-band → ≥80%).
|
||||
R0052 is M-band (0.7 confidence); the rest are H-band.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from tests.ttp.rule_precision.conftest import precision_for
|
||||
|
||||
rows = corpus_loader("canary")
|
||||
if not rows:
|
||||
pytest.skip("no canary corpus available")
|
||||
lifter = _build_lifter()
|
||||
fired: dict[str, list[str]] = {}
|
||||
for row in rows:
|
||||
tags = asyncio.run(lifter.tag(make_event(row)))
|
||||
fired[row.label] = [tag.rule_id for tag in tags]
|
||||
precision, _tp, _fp = precision_for(rule_id, rows, fired)
|
||||
threshold = 0.80 if rule_id == "R0052" else 0.95
|
||||
assert precision >= threshold, (
|
||||
f"{rule_id} precision {precision:.2f} < {threshold} on canary corpus"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user