feat(ttp): E.3.13 IdentityLifter + CredentialLifter (R0001-R0006)

IdentityLifter owns lifter:identity_* — currently R0003 (password
spraying). CredentialLifter owns lifter:credential_* — R0001 generic
auth brute, R0002 password guessing, R0004 credential reuse, R0005
valid-account use, R0006 default credentials.

YAMLs R0001/R0002/R0003/R0005/R0006 had their match.kind normalised
to fit the lifter prefix scheme — the design doc's promised "YAMLs
normalised in a separate refactor commit" lands here.

Identity-rollup tags null out attacker_uuid on emit so the worked-
example invariant holds (the tag belongs to the Identity, never to
one member IP).

Tests: test_identity_lifter.py + test_credential_lifter.py cover
each predicate's positive/negative path, state modulation
(disabled/clipped/expired), source-kind gating, and idempotent
replay. test_lifter_absence and test_lifters updated for the new
ctor signature.
This commit is contained in:
2026-05-01 20:52:56 -04:00
parent 62ad76615e
commit 322fd44d72
13 changed files with 638 additions and 44 deletions

View File

@@ -38,12 +38,12 @@ CohortLoader = Callable[[str], list[CorpusRow]]
# Lifter-bound rules: cannot fire from the v0 engine.
_LIFTER_BOUND: dict[str, str] = {
"R0001": "impl phase E.3.9 (BehavioralLifter — auth brute count)",
"R0002": "impl phase E.3.9 (BehavioralLifter — password guessing)",
"R0001": "impl phase E.3.13 (CredentialLifter — auth brute count)",
"R0002": "impl phase E.3.13 (CredentialLifter — password guessing)",
"R0003": "impl phase E.3.13 (IdentityLifter — password spraying)",
"R0004": "impl phase E.3.13 (CredentialLifter — credential reuse)",
"R0005": "impl phase E.3.9 (BehavioralLifter — valid account use)",
"R0006": "impl phase E.3.9 (BehavioralLifter — default creds)",
"R0005": "impl phase E.3.13 (CredentialLifter — valid account use)",
"R0006": "impl phase E.3.13 (CredentialLifter — default creds)",
"R0030": "impl phase E.3.9 (BehavioralLifter — JARM/HASSH match)",
}