Fleet & coordination — the red⇄blue coordination loop

Per-session detection saturates: with enough budget you can make any single session look clean — a real GPU, an engine-level anti-detect browser, a residential proxy. What stays expensive is coherence across many sessions at fleet scale. A coordinated fleet must either clone one profile (a fingerprint/trace collision across distinct IPs) or randomize per node (the TLS-vs-JS paradox, or a behavioral template all nodes share). It cannot cheaply do neither. Kitsune runs both sides of that level of the arms race.

This page is the map. The standalone red kit has its own fleet/README.md; the blue scorers live in harness/.../coordination.py.


Red — Skulk and the fleet manager

Skulk (fleet/) — the adversary-emulation kit

Skulk generates coordination-shaped sessions and emits them to a detector's /ingest. It is a detection-validation tool (in the spirit of Atomic Red Team / Caldera), not an attack tool: there is no flood/DoS, credential, or scraping capability, and every run is authorization-scoped in code (scope.py) — it resolves the target against an allow-list (Kitsune's own detector/edge/arena/localhost by default) and refuses anything else. There is no flag that disables the check.

cd fleet
uv run skulk list                                              # the strategy catalog
uv run skulk describe fuzzy -n 5                               # show a fleet shape (no emission)
uv run skulk run cloned --target http://localhost:8099 -n 3   # emit a cloned fleet at the lab detector
KITSUNE_DETECTOR=http://localhost:8099 task coordination-live # grade it on the detector's own view

The strategy ladder climbs from naive to evasive — each models a real attacker class and grounds one blue signal (full table with conviction status in fleet/README.md):

Strategy Models What it grounds
cloned one profile cloned fleet-wide fp_collision
trace-replay one canned pointer trace replayed trace_collision
randomizer coherent per-instance fp, one JA4 the TLS/JS paradox (corroborating)
fuzzy jitter fp and trace per node the exact-hash frontier
similarity jittered traces from one humanizer model template_similarity
fuzzy-rotate rotate JA4 per node + fuzz fp/trace shared_real_ip (a leaked binding)
ticket-reuse rotated + fuzzed, bound by a reused TLS ticket shared_ticket
staggered a cloned fleet spreading arrivals over time the lockstep timing axis (corroborating-only)
ipv6-rotate spray IPv6 /128s inside a few /64s the /64 origin fold
diffuse-campaign distinct everything, tuned above the template floor axis A (aggregate correlation)
tool-fleet a heterogeneous scripted-tool fleet shared JA4-tool / ext signature

The fleet manager — driving real evader containers

fleet_manager.py runs the same shapes with real evader containers instead of synthetic ingest, from a registry of named evasions, plans, archetypes and campaigns. Each replica gets a per-node KS_NODE_SEED (so its fingerprint/behavior differ deterministically), an optional per-node proxy, retries, and a stagger_seconds launch spread. The --validate-archetypes mode turns every adversary persona's expected outcome into a tested contract (task archetype-validate).

task coordination-fleet-manage -- --evasion camoufox-linux --evasion zendriver-uach --n 2 \
  --detector http://localhost:8099
task archetype-validate -- --detector http://localhost:8099   # every persona vs its expected verdict

Objectives — actions-on-objective, sharded per worker

A coordinated fleet doesn't just exist; it does work. objectives.py models an Objective (name, target, work items, a behavioral template, per-item wait) and shards it round-robin across N workers via compile(n), producing a distinct BehavioralTask per node so no two workers replay the same trajectory. The registry's targets are synthetic lab endpoints only — the example.test form on the harness allow-list — and the modeled objectives (e.g. credential-stuffing, account-creation) are shapes for the detector to catch, never a working attack. There is no credential generator and no real target.

Humanization — beating the structural behavioral tells

The hardest behavioral tells are structural, not shape-based: getCoalescedEvents() length + isTrusted catch CDP-injected input regardless of how human the path looks. The real-input evasion mode (KS_REAL_INPUT, in evaders/stealth/run.mjs) drives real OS-level pointer input via XTEST/xdotool under xvfb, producing genuine coalesced events and isTrusted — so the red team can exercise (and the blue team must hold up against) input that is structurally real, not synthesized.


Blue — two coordination scorers

Both scorers run over the session corpus (list[(session_id, Session)]), typically fetched live from a detector's store (live_coordination.py).

Per-binding — score_corpusFleetVerdict

Clusters sessions by JA4 prefix and looks for cross-instance collisions. Convicting bindings: fp_collision, trace_collision, shared_origin (one WebRTC/real origin), ticket_reuse (one TLS resumption ticket). Ambiguous bindings (fp_collision on a low-entropy fp, ja4c, template_similarity, ticket) are corroboration-gated — they need an automation tell or an IP-reputation flag to convict. Every conviction requires ≥2 distinct /64 origins (_ip_origin folds an IPv6 spray to its true origin), so a real cohort behind one NAT, or one office on one browser build, never reads as a botnet.

Axis A — score_campaignsCampaignVerdict

The aggregate view: community detection over a multi-dimensional similarity graph. The dimensions (_CAMPAIGN_DIMS) are JA4 prefix, behavioral descriptor (soft ε ≤ 0.15), lockstep arrival (≤ 120 s), origin reputation, and prevalence tail. An edge joins two sessions sharing ≥ 2 dimensions; a campaign is a community spanning ≥ 2 origins that is dense (≥ 0.5 of its internal pairs) on ≥ 3 dimensions; ≥ 2 dimensions is a candidate. Candidate generation is blocked by JA4+time so it scales, and replay_campaigns runs it as a windowed stream. Axis A is what catches the residual correlation a diffuse fleet leaves after it has defeated every pairwise binding.


The escalation ladder — why both are needed

task adversary-showcase (harness/tools/adversary_showcase.py) emits canonical fleet shapes to a live detector and grades each through both scorers, printing the ladder:

Fleet shape Per-binding (score_corpus) Axis A (score_campaigns)
cloned FLEET (fp_collision)
trace-replay FLEET (trace_collision)
diffuse-campaign candidate (evades) CAMPAIGN (caught)
diffuse-staggered candidate (evades) CAMPAIGN (stagger alone isn't enough)
diversified candidate (evades) no-campaign (evades both)

The pairwise scorer falls first; axis A catches the build-rotating-but-correlated residual; and only a fully diversified fleet — distinct build/fingerprint/trace, spread descriptors, spread arrivals — evades both. At that point it is, by construction, N independent real users (the economic bind: conviction there is external-data-bound, not an in-sandbox signal).

Diversification levers ⇄ axis-A dimensions

Each red lever that gets a fleet to the frontier maps one-to-one onto the axis-A dimension it defeats — which is also why defeating all of them costs as much as running real users:

Red lever Axis-A dimension it drops
distinct browser builds per node ja4_prefix
distinct (real) behavior per node behavioral descriptor
stagger arrivals beyond the window lockstep
clean / residential egress per node origin_reputation
real, varied hardware prevalence_tail

Running the loop

task coordination-eval                         # precision/recall over legit + malicious scenarios
task adversary-showcase -- --detector http://localhost:8099   # the full red⇄blue ladder
task coordination-fleet-demo                   # ingest a coordinated fleet, then grade it live
task coordination-fleet-capture                # AUTHENTIC: N real evader containers through the edge

Ethics

Skulk, the fleet manager and objectives target only Kitsune's own detector/edge/arena and the harness/allowlist.py endpoints — never a third-party or production site. They model coordination shapes for a detector to catch; they are not a flood, DoS, credential, or scraping tool, and the allow-list is enforced in code. See fleet/README.md §Authorized-use and the repo ethics policy.