# Fixture 5 (multi_operator) — see development/CAMPAIGN_CLUSTERING.md §2. # # One campaign, two operators with distinct UKC roles. Phase-handoff is # the load-bearing signal; this fixture is what proves the algorithm # needs it. # # Actor A (night shift, hours 22-03 UTC): # Delivery → Exploitation → Persistence → Command-and-Control # # Actor B (day shift, hours 10-15 UTC): # Discovery → Lateral Movement → Collection → Exfiltration # # Different IPs, different ASNs, different JA3+HASSH (different # tools — A is the access broker, B is the post-exploitation # operator). What ties them is shared C2 callback and shared # stage-1 payload hash. # # Pass condition: a clusterer that resolves on shared C2 callback # (or, more generally, the planned similarity graph's payload + # C2 + phase-handoff signals) folds the two actors into one # campaign cluster. Demonstrated by `c2_callback_clusterer`. # # Adversarial condition: `shift_clusterer` (group attackers by # majority shift bucket — night/day/swing) puts A in "night" and B # in "day", fragmenting the campaign. Completeness collapses; the # bound floor on completeness rejects the bad clusterer. This is # the canonical demonstration that operational-schedule overlap is # NOT a campaign signal — different operators on different shifts # can still be one campaign. # # Like fixture 4, this is a CAMPAIGN-LEVEL fixture only. The two # DSL actors mint two distinct truth_identity_id rows by design # (different operators, different tools — they are different # identities even though they're one campaign). Identity-level # scoring is fixture 2's job. campaign: id: multi-operator-001 duration_days: 3 actors: - id: ops-broker-night asn: 64530 ip_pool: sticky # Tool A's TLS stack — older OpenSSL signature. ja3: "771,49195-49199-49196-49200-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27,29-23-24,0" hassh: "ops-broker-eeeeeeee-eeeeeeee-eeeeeeee" hours_active_utc: [22, 23, 0, 1, 2, 3] jitter_seconds: 60 - id: ops-postex-day asn: 64531 ip_pool: sticky # Tool B's TLS stack — distinctly different from A. ja3: "769,49162-49161-49171-49172-51-50-47,0-10-11-13-23-65281,29-23-24-25,0" hassh: "ops-postex-ffffffff-ffffffff-ffffffff" hours_active_utc: [9, 10, 11, 12, 13] jitter_seconds: 60 phases: # Actor A — initial access path, owns the foothold. - name: delivery actor: ops-broker-night tool_signature: c2_callback: "c2.shared-op.example" target_selector: { service: ssh, count: 2 } dwell_seconds: 1 - name: exploitation actor: ops-broker-night tool_signature: payload_hash: "shared-op-stage1-payload" c2_callback: "c2.shared-op.example" target_selector: { service: ssh, count: 2 } dwell_seconds: 5 - name: persistence actor: ops-broker-night tool_signature: c2_callback: "c2.shared-op.example" target_selector: { decky: previous_success, count: 1 } dwell_seconds: 5 - name: command_and_control actor: ops-broker-night tool_signature: c2_callback: "c2.shared-op.example" target_selector: { decky: previous_success, count: 1 } dwell_seconds: 5 # Actor B — picks up after A's foothold; shares C2 + payload. - name: discovery actor: ops-postex-day tool_signature: c2_callback: "c2.shared-op.example" target_selector: { decky: previous_success, count: 2 } dwell_seconds: 5 - name: lateral_movement actor: ops-postex-day tool_signature: c2_callback: "c2.shared-op.example" target_selector: { service: ssh, count: 2 } dwell_seconds: 5 - name: collection actor: ops-postex-day tool_signature: payload_hash: "shared-op-stage1-payload" c2_callback: "c2.shared-op.example" target_selector: { service: ssh, count: 2 } dwell_seconds: 5 - name: exfiltration actor: ops-postex-day tool_signature: c2_callback: "c2.shared-op.example" target_selector: { service: ssh, count: 2 } dwell_seconds: 5