Skip to content

Attacks

Byzantine-style attack simulations baked into the Rust core. Use them to stress-test your strategy and compare resilience across aggregation algorithms.

Catalog

Attack Parameter What it does
model_poisoning intensity ∈ [0, 1] Scales a subset of client weight vectors by a corrupting factor. Directly attacks the aggregator.
sybil_nodes count ≥ 1 Injects count fake clients that submit near-identical malicious updates, amplifying their vote.
gaussian_noise intensity ≥ 0 (σ) Adds N(0, σ²) noise to the aggregated global weights. Simulates unreliable clients or rough channels.
label_flipping fraction ∈ [0, 1] Flips labels on fraction of participating clients — classic data-poisoning primitive.

Register via Python

from velocity import VelocityServer, FedMedian

server = VelocityServer(
    model_id="demo/model",
    dataset="demo/dataset",
    strategy=FedMedian(),  # robust strategy — pairs well with attacks
)

server.simulate_attack("model_poisoning", intensity=0.3)
server.simulate_attack("sybil_nodes", count=5)

summaries = server.run(min_clients=10, rounds=5)
for s in summaries:
    for result in s["attack_results"]:
        print(result)

Multiple attacks can be registered before a round — they are all applied.

Register via CLI

uv run velocity simulate-attack model_poisoning --intensity 0.2
uv run velocity simulate-attack sybil_nodes --count 5
uv run velocity simulate-attack gaussian_noise --intensity 0.1
uv run velocity simulate-attack label_flipping --fraction 0.25

Each CLI invocation registers one attack and runs one round, emitting a single JSON summary on stdout.

Reading the results

Each round summary contains an attack_results list. In Python you can hydrate each entry into an AttackResult dataclass:

from velocity.attacks import AttackResult

for s in summaries:
    for raw in s["attack_results"]:
        result = AttackResult.from_dict(raw)
        print(result)
# [model_poisoning] Poisoned 2 updates at intensity=0.30 (severity=0.412, clients=2)
AttackResult field Type Description
attack_type str Which attack produced this result.
clients_affected int Number of clients touched this round.
severity float Aggregator-relative impact score in [0, 1].
description str Human-readable summary.

Designing a resilience experiment

A minimal template for comparing strategies under attack:

from velocity import VelocityServer, FedAvg, FedMedian, Krum, Strategy

def run_under_attack(strategy: Strategy) -> list[float]:
    server = VelocityServer(
        model_id="demo/model",
        dataset="demo/dataset",
        strategy=strategy,
    )
    server.simulate_attack("model_poisoning", intensity=0.3)
    return [s["global_loss"] for s in server.run(min_clients=10, rounds=10)]

fedavg_losses    = run_under_attack(FedAvg())
fedmedian_losses = run_under_attack(FedMedian())
krum_losses      = run_under_attack(Krum(f=2))   # tolerates up to 2 Byzantine clients

See Strategies for when each aggregation algorithm is the right pairing.

Adding a new attack

Attack kernels live in vfl-core/src/security.rs. The shape mirrors the strategy contract — implement the mutation in Rust, expose it through the orchestrator's register_attack dispatch, then add the identifier to VALID_ATTACKS in python/velocity/attacks.py and document the parameter here.