FedRobustBench

A lightweight, fully reproducible benchmark for studying adversarial robustness of Federated Learning (FL) systems.

View on GitHub

Motivation and Origin

This project began as an analysis of an existing open-source repository, Adversarial_Robustness_of_FL_systems, which explores the same general research question — the robustness of Federated Learning to data- and model-poisoning attacks — using TensorFlow-Federated and the EMNIST dataset.

That project was used only as conceptual inspiration; no code, wording, structure, or identifiers were copied. Where the original project had a real, working idea, FedRobustBench keeps the idea and rebuilds the engineering from scratch, addressing several concrete weaknesses identified during review:

Original project Observed limitation FedRobustBench's approach
Pinned to `tensorflow-federated==0.20.0`, an old, network-heavy, Colab-only dependency Hard to install locally, not reproducible offline, version-locked to an unsupported release Pure-NumPy FL core with zero heavyweight FL-framework dependency; runs anywhere Python + NumPy runs
Uses EMNIST via `tff.simulation.datasets`, requiring a large network download on every fresh environment Experiments cannot be reproduced offline or in restricted/CI environments Uses scikit-learn's bundled `digits` dataset (real 8×8 handwritten-digit images, no download)
Several notebooks contain incomplete/broken code (e.g. an `AbstractAttacker.get_attacked_clients` method that returns `self.` — a syntax error; `Dataset.partition_dataset` references `self.x_train`, which is never set; unresolved imports like `ABC`, `Set`) Code does not run as-is; no tests; no CI Every module is executable, covered by unit tests (20/20 passing, including a numerical gradient check on the backprop implementation), and runnable end-to-end from a single script
Only one aggregation rule (implicit FedAvg via TFF) is available; "defense" is not actually implemented anywhere in the notebooks No way to answer the project's own research question ("what are the weaknesses, and what can be recommended?") because there is nothing to compare FedAvg against Five aggregation rules implemented and empirically compared: FedAvg, coordinate-wise median, trimmed mean, Krum, Multi-Krum
No non-IID data partitioning logic is wired up (client sharding is a simple `sorted(client_ids)[:NUM_CLIENTS]` slice) Real FL deployments are non-IID; ignoring this makes robustness results unrepresentative Dirichlet label-skew partitioning (Hsu et al., 2019) with a tunable heterogeneity parameter `alpha`
No results are saved in a structured, comparable way; no plots; no metrics beyond a raw `.npy` dump Findings are not interpretable or presentable CSV summary/history tables, matplotlib figures, and macro precision/recall/F1 in addition to accuracy
No tests No confidence that the pipeline (or an attack, or an aggregator) is implemented correctly 20 unit tests, including a numerical gradient check, correctness properties for each aggregator (e.g. "median must stay close to the honest mean under outliers"), and attack-specific invariants

The result is a small but genuinely research-grade artifact: every experiment in results/ was produced by actually running this code, end to end, with no network access — not hand-written or copied from elsewhere.

Research Question

Under a static, cross-device federated-learning threat model with non-IID client data, how much do data-poisoning and Byzantine model-poisoning attacks degrade a FedAvg-trained global model, and to what extent do Byzantine-robust aggregation rules (median, trimmed mean, Krum, Multi-Krum) restore accuracy as the fraction of malicious clients grows?

Threat Model (Summary)

See docs/THREAT_MODEL.md for the full specification. In short: a static adversary controls a fraction f ∈ {0, 0.1, 0.2, 0.3} of clients and either (a) poisons their local training data, or (b) submits an arbitrary malicious parameter update directly (Byzantine setting). The defender only ever observes client updates and chooses the aggregation rule.

Attacks Implemented

Data-poisoning

src/fedrobustbench/attacks/data_attacks.py

Model / Byzantine poisoning

src/fedrobustbench/attacks/model_attacks.py

Defenses / Aggregation Rules

src/fedrobustbench/aggregation.py: fedavg, median (Yin et al., 2018), trimmed_mean (Yin et al., 2018), krum, multi_krum (Blanchard et al., 2017).

Results

All numbers below are taken verbatim from results/tables/summary.csv, produced by experiments/run_experiment.py (30 simulated clients, Dirichlet alpha=0.5, 25 rounds, 50% client participation per round, seed=42). No numbers in this section are hand-typed or estimated — re-running the script reproduces them exactly (fixed seed, no network access required).

Label-flip attack (data poisoning)

Malicious fraction FedAvg accuracy Median accuracy Trimmed-mean accuracy
0.0 (baseline) 0.9472
0.1 0.9500 0.9306 0.9389
0.2 0.9472 0.9361 0.9444
0.3 0.9500 0.9472 0.9389

At the scale tested, plain label-flip data poisoning turns out to be a weak attack against sample-size-weighted FedAvg: because each malicious client's locally trained update still partially fits genuine gradient structure (the poisoned labels are still internally consistent for that client's SGD), and updates are weighted-averaged across many clients, FedAvg does not collapse even at f=0.3. Interestingly, the robust aggregators (median, trimmed mean) show slightly lower accuracy than FedAvg here — they are not "wrong," but discarding/down-weighting legitimate non-IID client diversity has a small cost when there is no strong attack to defend against. This is a genuine, useful finding: robust aggregation is not free, and its benefit is attack-dependent.

Label-flip robustness

Sign-flip attack (Byzantine model poisoning)

Malicious fraction FedAvg accuracy Krum accuracy Multi-Krum accuracy
0.0 (baseline) 0.9472
0.1 0.2944 0.8333 0.9528
0.2 0.1000 0.8333 0.9556
0.3 0.1222 0.8250 0.9500

Here the contrast is stark: an amplified sign-flip attack collapses FedAvg to near-random accuracy (~10-30%, vs. a 10-class random baseline of 10%) with as few as 10% malicious clients, because a handful of large-magnitude, oppositely-signed updates dominate the weighted mean. Both Krum-family defenses recover most of the accuracy; Multi-Krum in particular is essentially unaffected up to f=0.3 (0.95 vs. 0.947 baseline), because it explicitly selects updates by pairwise proximity to their neighbours and discards statistical outliers before averaging.

Sign-flip robustness

Takeaways

  1. Attack type matters far more than attack fraction. A data-poisoning attack (label flip) that keeps each client's local gradients internally consistent is comparatively harmless to FedAvg at this scale; a Byzantine model-poisoning attack (sign flip) is catastrophic even at low fractions.
  2. Robust aggregation has a trade-off. Median/trimmed-mean cost a small amount of accuracy under benign or weak-attack conditions but provide large, decisive protection under strong Byzantine attacks.
  3. Multi-Krum outperformed single-Krum in every tested condition, consistent with the original Blanchard et al. (2017) analysis: averaging several vetted candidates reduces variance versus committing to a single client's update each round.

These are exploratory results on one dataset/model/seed and should not be over-generalized — see §10 (Limitations).

Technology Stack

No GPU is required; a full 19-scenario experiment sweep (25 rounds each) runs in under 30 seconds on a single CPU core.

Installation

git clone https://github.com/clementbouni-arch/fedrobustbench.git
cd fedrobustbench
python3 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -r requirements.txt

Tested on Linux and macOS; Windows works via the standard venv workflow above. No CUDA/GPU setup is needed.

Usage

Run the full experiment sweep (19 scenarios: baseline, label-flip × {FedAvg, median, trimmed mean} × 3 malicious fractions, sign-flip × {FedAvg, Krum, Multi-Krum} × 3 malicious fractions):

python experiments/run_experiment.py

This writes:

To experiment with a single custom scenario in Python:

from fedrobustbench.data import load_dataset, partition_dirichlet
from fedrobustbench.server import run_federated_training, TrainConfig, AttackConfig

(x_train, y_train), (x_test, y_test) = load_dataset(seed=42)
clients = partition_dirichlet(x_train, y_train, num_clients=30, alpha=0.5, seed=42)

history, model, malicious = run_federated_training(
    clients, x_test, y_test, num_classes=10,
    aggregator="multi_krum",
    train_cfg=TrainConfig(num_rounds=25, seed=42),
    attack_cfg=AttackConfig(model_attack="sign_flip", malicious_fraction=0.3),
)
print(history[-1].metrics)

Testing

python -m unittest discover -s tests -v
# or, if pytest is installed:
pytest tests/ -v

20 tests cover: a numerical gradient check on the MLP backward pass, aggregator correctness/robustness properties (e.g. FedAvg is provably dragged by outliers while median/trimmed-mean/Krum/Multi-Krum are not, on a synthetic outlier fixture), and attack-specific invariants (e.g. targeted_label_flip only touches the intended source class; sign_flip output has negative dot-product with the honest update).

Project Structure

fedrobustbench/
├── README.md
├── LICENSE
├── requirements.txt
├── .gitignore
├── docs/
│   └── THREAT_MODEL.md
├── src/fedrobustbench/
│   ├── __init__.py
│   ├── data.py            # dataset loading + Dirichlet non-IID partitioning
│   ├── model.py            # NumPy MLP: forward/backward, param (de)serialization
│   ├── aggregation.py       # FedAvg, median, trimmed mean, Krum, Multi-Krum
│   ├── metrics.py           # accuracy/precision/recall/F1, ASR, degradation
│   ├── server.py            # federated training orchestration loop
│   └── attacks/
│       ├── __init__.py
│       ├── data_attacks.py  # label flip, targeted flip, noise, deletion, backdoor
│       └── model_attacks.py # sign flip, gaussian, zero-update, constant scaling
├── experiments/
│   └── run_experiment.py    # end-to-end experiment sweep, tables + figures
├── tests/
│   ├── test_model.py
│   ├── test_aggregation.py
│   └── test_attacks.py
└── results/
    ├── tables/{summary,history}.csv
    └── figures/*.png

Limitations

Future Work

References

Conceptual origin: Adversarial_Robustness_of_FL_systems (used only as a starting point for the research direction; no code or text reused — see §1).

License

MIT — see LICENSE.