Evaluation artifact for Big Bird: Resilient Privacy Budgeting Across Untrusted Web Domains (SOSP 2026). Preprint: arXiv:2506.05290. It studies sybil / budget-depletion attacks against privacy-preserving advertising systems built on a Private Data Service (PDS), and measures the resulting privacy–utility tradeoffs on the Criteo ad dataset.
With Docker (recommended — pinned toolchain, no host setup beyond Docker):
git clone https://github.com/columbia/bigbird
cd bigbird
git submodule update --init pdslib # the DP library the engine builds against
docker compose up --buildFigures are written to figures/ as fig5a … fig5d and the combined
fig6_attack_resilience.pdf, renamed to the paper's exact figure names. The
container builds the Rust engine, runs the four paper suites over the Criteo
dataset, and plots them.
The first run downloads the ~169 MB Criteo dataset into
data/(skipped on re-runs) and is compute-heavy: each suite makes sequential passes over the full ~1.4M-device workload, so expect a few hours on a many-core server. Cap worker threads withBB_THREADS=8 docker compose up --build. Output files are written by the container (root-owned by Docker convention).
Only
pdslibis needed to build. Thefirefox-prototype/submodules are intentionally not initialized (they include a ~913 MB Firefox tree) — seefirefox-prototype/README.mdif you want that optional demo. Avoid a blanketgit submodule update --init --recursive, which would pull them too.
Without Docker (native, requires Rust + uv)
git clone https://github.com/columbia/bigbird
cd bigbird
git submodule update --init pdslib
./reproduce.shreproduce.sh runs the identical pipeline natively: uv sync, build the Criteo
dataset (skipped if data/criteo/ exists), cargo build --release, run the four
suites, then collect the figures. BB_THREADS=8 ./reproduce.sh caps threads (the
orchestrator also retries a suite with fewer threads if it is OOM-killed).
Before committing to a multi-hour full reproduction, run the smoke test to confirm the whole pipeline works end-to-end (build → run engine → plot) in seconds:
scripts/smoke.shRun it from anywhere — it resolves the repo root itself. It builds the release
engine, runs one small suite (attacker-strength-popularity, 10 runs) with the
Criteo workload capped to the first 300k chronological actions via the
engine's --max-items flag, plots a figure, and writes everything to a fresh
temporary directory (mktemp -d under /tmp) — never figures/, so it can
never clobber the real paper figures.
On an already-built binary the whole run takes ~14 s (a cold
cargo build --release adds ~20 s, so worst case ~35 s). It prints a pass/fail
banner and points you at the example PDF and logs it produced.
This validates the pipeline; it does NOT reproduce the paper's numbers. The capped run is a different, much smaller experiment. The real figures come from
./reproduce.sh(see above).
Knobs (all optional):
- first arg — an explicit output dir (default: a fresh temp dir).
BB_SMOKE_MAX_ITEMS— the workload cap (default300000; the engine keeps at least one conversion regardless).BB_SMOKE_THREADS— worker threads (defaultnproc).
The Criteo subsample is regenerated from the public dataset with a fixed seed
(seed=0), so the workload is identical on any machine. Given that workload the
Rust engine is deterministic and independent of --num-threads — reports are
reordered to the single-threaded sequence before any metric is computed — so
every figure is reproducible run-to-run.
The paper's figures were produced from an earlier, unseeded subsample of the
same dataset. The seeded reproduction is therefore visually equivalent to
the paper (identical curves and conclusions), with individual percentile markers
differing by at most a couple percent from the different random draw (Fig. 5
SSIM 0.92–1.00). The per-suite malicious_* outputs under experiments/ — the
attacker's own measurement error, not shown in any paper figure — are diagnostic
only and are not part of this guarantee.
| Component | Role in the paper |
|---|---|
bigbirdeval/ (Rust) |
The evaluation engine bigbirdeval — runtimes, datasets, and the Random/Omniscient attacks (bigbirdeval/src/attacks/). |
bigbird/ (Python) |
Dataset preprocessing (bigbird/datasets/criteo/) and plotting (bigbird/plots/). |
pdslib/ (git submodule) |
The differential-privacy mechanism being evaluated (Apache-2.0, pinned commit). |
scripts/run_experiments.py |
Orchestrator — builds a reproducible snapshot, runs every suite, plots. |
scripts/collect_figures.py |
Renames per-suite plot PDFs into paper figure names under figures/. |
reproduce.sh |
The single end-to-end entry point (calls the two scripts above). |
firefox-prototype/ |
Optional: pdslib deployed in a modified Firefox + a dashboard extension (deployability demo, not needed for the figures). See firefox-prototype/README.md. |
The system under evaluation is Big Bird. The paper evaluates two attacker strategies: Random (Attack B) and Omniscient (Attack C). Fig. 6 is a single three-panel figure assembled from three suites.
| Paper figure | Suite | How it's produced |
|---|---|---|
| Fig. 5a — benign error, no attack | paper |
no_attack_eps_qimp_p50_95 |
| Fig. 5b — error causes, no attack | paper |
no_attack_bb_causes_eps_qimp |
| Fig. 5c — benign error, under attack | paper |
benign_attacked_eps_qimp_p50_95 |
| Fig. 5d — error causes, under attack | paper |
benign_attacked_bb_causes_eps_qimp |
| Fig. 6a — domain cap | big-bird-bc |
benign_attacked_quota_count_BY_attack_id_p50_95 |
| Fig. 6b — sybil domains | attacker-strength-num-sybils |
benign_attacked_num_sybils_BY_attack_id_p50_95 |
| Fig. 6c — site popularity | attacker-strength-popularity |
benign_attacked_malicious_site_rank_start_BY_attack_id_p50_95 |
| Table 3 — filter-config percentiles | — | Manual, see Manual steps. |
Fig. 5's four panels are collected as-is; Fig. 6's three panels are combined
into figures/fig6_attack_resilience.pdf by bigbird/plots/combined.py. The
authoritative paper_name → (suite, stem) map is the FIGURES dict (plus
COMBINED_FIG6) in scripts/collect_figures.py.
Claim each figure supports (paraphrased from §7 of the paper; the parenthetical is the knob swept on the x-axis):
- Fig. 5a (source quota
q_imp, no attack) — a correctly sized impression-site quota costs no benign utility: onceq_imp ≥ 2Big Bird's median error matches the quota-less PPA baselines (5.2%), whereas an over-aggressiveq_imp = 0.5inflates error 2.7× (to 14.0%) via null reports. The p85 quotas suffice for normal operation. - Fig. 5b (source quota
q_imp, no attack) — that residual error is fully explained by budget/quota blocking: atq_imp = 0.5the source quota blocks 16.2% of reports, but forq_imp ≥ 2it blocks <0.002%, leaving only the ~2.8% per-querier blocking inherent to all IDP baselines. Sized to p85, Big Bird adds no bias beyond the underlying PPA accounting. - Fig. 5c (source quota
q_imp, under random attacker) — the quota mechanism keeps benign error near baseline under attack (median 5.5%, p95 14.8%) across a rangeq_imp ∈ [1,2], while a bare global budget (PPA w/ global budget) is trivially defeated (median 48.5%, a 9.3× increase). There is a sweet spot: quotas that are too large also hurt, letting the attacker drain the global budget (≈20% error byq_imp = 5). - Fig. 5d (source quota
q_imp, under random attacker) — that degradation at large quotas is attributable specifically to global-budget depletion of benign reports: <1% blocked forq_imp ≤ 2, rising to ≈11% atq_imp = 3and ≈19% atq_imp = 4, where it dominates all other error sources. - Fig. 6a (domain cap
q, Random vs Omniscient) — the per-action domain cap bounds depletion even against the optimal omniscient attacker (q = 1keeps median benign error ≤10%,q = 2≤28.6%, both far below the unprotected PPA API's 48.3–48.4%); atomic deduction further slashes damage against the realistic random attacker (5.4% vs the omniscient 28.6% atq = 2). - Fig. 6b (# Sybil domains, Random vs Omniscient) — damage does not grow without bound in the size of the Sybil pool: the omniscient attacker saturates by ~10 domains (29.4%, flat through 100) and the random attacker barely moves (5.2%→6.4%), confirming resilience Thm. (i) that depletion is bounded by the product of domain count and quota capacity.
- Fig. 6c (attacker site rank, Random vs Omniscient) — damage scales with the volume of genuine user traffic the attacker attracts: as the attacker controls less-popular sites (top-10 → top-41–50) the omniscient attacker's error falls monotonically 29.0%→10.9% (2.7×) while the random attacker stays at baseline, confirming resilience Thm. (ii–iii). Attracting real traffic requires costly real-world investment, unlike registering cheap Sybil domains.
- Rust — stable toolchain, edition 2024 (requires rustc ≥ 1.85); pinned in
rust-toolchain.toml. Nightly is needed only forjust format(cargo +nightly fmt). - Python 3.12+ via
uv—uv synccreates the venv and installs all deps plus tooling (just,marimo). pdslibsubmodule — a Cargo path dependency (pdslib = { path = "../pdslib", features = ["fxhash", "experimental"] },bigbirdeval/Cargo.toml). Theexperimentalfeature is required for the report fields the RMSRE/bias metrics use.git submodule update --init pdslibchecks it out at the pinned commit. (Init onlypdslib, not--recursive— thefirefox-prototype/submodules are large and optional.)- Dataset — ~169 MB of preprocessed Criteo parquet under
data/criteo/, built byreproduce.sh(orjust generate-dataset) from the CriteoPrivateAd dataset (CC-BY-SA-4.0); needs network access to HuggingFace on first build.
Hardware / OS used for the paper runs:
- OS: Ubuntu 26.04 LTS (Linux kernel 7.0, x86-64)
- CPU: AMD EPYC 9B45 — 24 physical cores / 48 threads, single socket
- RAM: 182 GiB
- Disk: ~5 GB free (the ~169 MB dataset plus per-run snapshots under
experiments/)
The engine is multi-threaded (--num-threads, or BB_THREADS when driving the
reproduction) and memory-heavy; peak RAM scales with thread count, and the four
suites run sequentially over the full dataset. On the machine above, a full
reproduction with 48 threads takes roughly 25–30 minutes of wall-clock
(plus the one-time image/dataset build on a cold start).
This artifact simulates the sybil / budget-depletion attacks (Attacks A, B, C)
from the paper's threat model. It constructs synthetic sybil identities and
injects synthetic malicious impressions and conversions entirely in-process,
against a local copy of the Criteo dataset, purely to measure their effect on a
privacy budget. It contacts and attacks no external system, contains no
exploit or malware, and performs no networking of any kind. Identifiers such as
malicious, sybil, and hijack in the source refer only to these simulated
dataset entities. It is safe to build and run.
Table 3 (filter-config percentiles) is not emitted by the figure pipeline; it
comes from the marimo notebook notebooks/criteo_stats.py, which reports the
per-user impression/conversion percentiles used to set the filter configs:
uv run marimo edit notebooks # then open criteo_stats.py
# or run it headless:
uv run marimo run notebooks/criteo_stats.pyTo go beyond the shipped figures — adding a new attack strategy or a new
experiment suite — see docs/EXTENDING.md. It walks through
the exact traits, enums, and match arms to touch (Attack in
bigbirdeval/src/attacks/, the suite factories in bigbirdeval/src/experiments.rs,
and figure registration in scripts/collect_figures.py).
Apache-2.0 (see LICENSE); the pdslib submodule is also Apache-2.0.
The downloaded Criteo dataset is CC-BY-SA-4.0 (attribution + ShareAlike).