From 90e279868a9e177a2a10609825a2ffd39250858a Mon Sep 17 00:00:00 2001 From: agent-70948f1db9d839b7 Date: Tue, 28 Jul 2026 18:00:10 +0000 Subject: [PATCH] test: jitter defense + injection taxonomy (44/44) --- test_swarmmetrics.py | 61 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test_swarmmetrics.py b/test_swarmmetrics.py index dcda982..e9ef090 100644 --- a/test_swarmmetrics.py +++ b/test_swarmmetrics.py @@ -1116,3 +1116,64 @@ def test_injection_attacker_invisible(): max_attacker = max(scores.get(a, 0) for a in ["target", "other1", "other2"]) assert attacker_echo < max_attacker * 0.5, \ f"Attacker should be relatively invisible: echo={attacker_echo:.2f} vs max agent={max_attacker:.2f}" + + +def test_jitter_defense_recovers_from_injection(): + """ + Jitter defense: adding ±N seconds of random noise to timestamps + recovers most of the target's echo from a targeted injection attack. + + Without jitter: target echo = 0 (100% kill). + With ±10s jitter: target echo ≈ baseline (statistically). + + Discovered 2026-07-28: binary phase transition at Δt=0 means + a ±10s jitter window randomizes whether attacker arrives before/after. + Over 30 rounds, statistical averaging recovers ~90%+ of baseline. + """ + import random + random.seed(42) + + target_concepts = [f"x{i}" for i in range(5)] + msgs = [] + target_times = [] + + # Build corpus: target introduces, 3 agents echo + for r in range(30): + t = float(r * 200) + tc = target_concepts[r % 5] + target_ts = t + random.randint(10, 60) + target_times.append((t, target_ts, tc)) + msgs.append({"from_id": "target", "to_id": "_all", + "timestamp": target_ts, "concepts": [tc]}) + for agent in ["a", "b", "c"]: + msgs.append({"from_id": agent, "to_id": "_all", + "timestamp": target_ts + random.randint(30, 90), + "concepts": [tc, f"{agent}_{r % 5}"]}) + + baseline = score_echo(msgs, snapshot_idf=True) + target_base = baseline.get("target", 0) + assert target_base > 0 + + # Inject attacker 5s before target + injected = list(msgs) + for _, target_ts, tc in target_times: + injected.append({"from_id": "atk", "to_id": "_all", + "timestamp": target_ts - 5, + "concepts": [tc]}) + + # Without jitter: total kill + no_jitter = score_echo(injected, snapshot_idf=True) + assert no_jitter.get("target", 0) == 0, "Without jitter, target should be killed" + + # With ±10s jitter: run multiple trials, average should recover >50% + recoveries = [] + for trial in range(30): + random.seed(trial + 200) + jittered = score_echo(injected, snapshot_idf=True, jitter_seconds=10) + t_echo = jittered.get("target", 0) + if target_base > 0: + recoveries.append(t_echo / target_base) + + mean_recovery = sum(recoveries) / len(recoveries) + assert mean_recovery > 0.5, \ + f"Jitter defense should recover >50% of baseline: got {mean_recovery:.0%}"