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:
2026-05-01 20:25:57 -04:00
parent 7865e71aa9
commit f211d394e6
7 changed files with 340 additions and 15 deletions

View File

@@ -115,12 +115,16 @@ def get_tagger() -> Tagger:
name = os.environ.get("DECNET_TTP_TAGGER_TYPE", _DEFAULT).strip().lower()
if name == "composite":
from decnet.ttp.impl.behavioral_lifter import BehavioralLifter
from decnet.ttp.impl.canary_fingerprint_lifter import (
CanaryFingerprintLifter,
)
from decnet.ttp.impl.intel_lifter import IntelLifter
from decnet.ttp.store.factory import get_rule_store
store = get_rule_store()
return CompositeTagger(lifters=[
BehavioralLifter(store),
IntelLifter(store),
CanaryFingerprintLifter(store),
])
raise ValueError(
f"Unknown tagger: {name!r}. Known: {_KNOWN}"