A formal differential privacy guarantee bounds the worst case; an adversarial privacy test measures the actual case, and the gap between them is where releases get recalled. This page is part of Synthetic Spatial Data Architecture & Fundamentals: where the privacy-preserving generation frameworks reference builds the guarantee and differential privacy mechanisms compared chooses the mechanism that states it, this page provides the falsification step — the attacks a determined adversary would run, wired as build-failing checks so a leaky release is caught before promotion rather than after disclosure. The premise is Popperian: you cannot prove a synthetic dataset is private, but you can try hard to break it and treat a release that survives as provisionally shippable.
Three attacks, three scores, one verdict — the harness runs on every candidate release and writes its evidence to the audit ledger.
A stated (ε,δ) guarantee can be simultaneously true and useless, and adversarial testing is what surfaces the difference. Four concrete gaps motivate the harness.
The bound is worst-case, the release is specific. Differential privacy bounds the maximum privacy loss over all datasets and all adversaries. A specific release with a specific seed may leak far less — or the sensitivity bound feeding the mechanism may be wrong, in which case it leaks far more than the stated ε implies. Only an attack measures the realized risk.
The guarantee covers the mechanism, not the pipeline around it. A correctly implemented Gaussian mechanism protects the step it wraps. Post-processing that joins in an auxiliary layer, a caching bug that re-emits raw coordinates, or a topology-repair pass that snaps synthetic points back onto real vertices can reintroduce leakage the mechanism never saw.
Spatial correlation defeats scalar intuition. A synthetic trajectory can preserve enough of a home-work-home structure to be re-identifiable even when every individual coordinate is noised, because the pattern is the identifier. Membership and re-identification attacks probe the joint structure directly.
“Looks anonymized” is not a test. A dashboard of noised points looks safe. A nearest-neighbour linkage that matches 40% of synthetic records to a unique real record within a few metres is the actual state of the release. Without the attack, nobody knows which one they shipped.
The harness turns each gap into a measured score with a threshold, so the privacy claim becomes falsifiable rather than aspirational. The deepest of the three attacks gets its own worked treatment in running membership inference attacks on synthetic geodata.
The harness sits alongside the generator and needs a held-out real reference set that never touches training. Pin majors so attack scores are comparable across runs:
Two setup rules keep the scores honest. First, compute every distance in a projected metric CRS: a nearest-neighbour attack in EPSG:4326 degrees ranks matches by a latitude-warped pseudo-distance, so reproject to EPSG:6933 or a local UTM zone before building any KD-tree, the same discipline the scoping rules and data contracts layer already fixes for the generator. Second, partition the real data once, deterministically: the members (used to train the generator) and non-members (held out) must come from a seeded split, or the membership attack’s own baseline drifts between runs and the gate flaps.
Each attack instantiates a specific adversary model and reduces to a single score with a null baseline. Passing means the score is statistically indistinguishable from the baseline an adversary with no access would achieve.
Membership inference asks: given a target real record and access to the synthetic release, can an adversary decide whether that record was in the generator’s training set? The standard construction trains shadow generators on known member/non-member splits, extracts features from how the release responds near the target, and trains a classifier to output “member” or “non-member.” The score is the membership advantage — twice the classifier’s accuracy minus one, or equivalently the true-positive minus false-positive rate. An advantage near zero means the adversary is guessing; an advantage near one means membership is fully disclosed. This is the attack most directly dual to differential privacy: a correct ε-DP release bounds membership advantage by roughly eε−1, so a measured advantage far above that bound proves the implementation, not the theory, is broken.
Attribute inference asks: given the quasi-identifiers of a real individual and the synthetic release, can an adversary predict a sensitive attribute better than by guessing the population marginal? The adversary trains a predictor on the synthetic data mapping quasi-identifiers (coarse location, time band, category) to the sensitive attribute, then applies it to real quasi-identifiers. The score is accuracy above the marginal baseline — a synthetic release that has memorized the real attribute-location join lets the adversary predict sensitive attributes it should not be able to, even though no exact record was copied.
Nearest-neighbour re-identification asks the bluntest question: is each synthetic record suspiciously close to exactly one real record? Build a KD-tree over the real points in metric coordinates, query each synthetic record for its nearest real neighbour, and compare the distance distribution against the distribution you would get matching to a random real record. The score is the distance ratio; if synthetic points sit systematically far closer to a unique real neighbour than chance predicts, the generator is copying, not synthesizing. This attack needs no training and runs cheaply, which makes it the right first gate.
The harness is one function per attack plus a gate. Each returns a score and the null baseline it is judged against.
Step 1 — Run the nearest-neighbour re-identification screen first. It is the cheapest and catches outright copying before the expensive attacks run.
python
import numpy as np
from scipy.spatial import cKDTree
defnn_reid_ratio(real_xy: np.ndarray, synth_xy: np.ndarray, rng_seed:int)->dict:
rng = np.random.default_rng(rng_seed)
tree = cKDTree(real_xy)# real points in projected metres
d_true, _ = tree.query(synth_xy, k=1)# nearest real neighbour# baseline: distance to a randomly assigned real record
perm = rng.permutation(len(real_xy))[:len(synth_xy)]
d_rand = np.linalg.norm(synth_xy - real_xy[perm], axis=1)
ratio =float(np.median(d_true)/ np.median(d_rand))return{"score": ratio,"baseline":1.0}# ratio << 1 means copying
Step 2 — Run membership inference with shadow models. Train shadow generators on known splits, build a member/non-member classifier, and report advantage.
The harness is itself code, so it needs tests that prove it can detect leakage, not just run. Wire two kinds of check.
python
import numpy as np
deftest_gate_catches_identity_copy():# a "generator" that copies real points must fail the re-ID screen
rng = np.random.default_rng(3)
real = rng.uniform(0,10_000, size=(5000,2))
synth = real.copy()# blatant copying
res ={"nn_reid": nn_reid_ratio(real, synth,3),"mia":{"score":0.0},"attr_inf":{"score":0.0}}
thr ={"nn_reid_min":0.5,"mia_max":0.1,"attr_max":0.05}assertnot privacy_gate(res, thr)["nn_reid"],"gate missed an identity copy"deftest_gate_passes_independent_noise():# points drawn independently of the reference must pass the re-ID screen
rng = np.random.default_rng(4)
real = rng.uniform(0,10_000, size=(5000,2))
synth = rng.uniform(0,10_000, size=(5000,2))
res = nn_reid_ratio(real, synth,4)assert res["score"]>=0.5,"independent draw wrongly flagged as copying"
A gate that never fails is worse than no gate, so the copy-detection test is mandatory: it proves the harness has teeth. Beyond correctness, run the whole suite on every candidate artifact through the CI/CD integration pipeline and persist scores, thresholds, seeds, and verdict to an immutable ledger — that record is the evidence a reviewer needs and a required line item on the production readiness checklist. Thresholds should be tied back to the mechanism’s stated budget: a membership advantage well below eε−1 is consistent with the claim; an advantage above it means the chosen mechanism is misconfigured.
Attack cost, not generation cost, becomes the CI bottleneck once the reference set is large.
Shadow-model training dominates membership inference. Training many shadow generators is the expensive part. Amortize it: train the shadow set once per generator architecture and cache the fitted attack classifier, re-running only the cheap scoring pass per release unless the generator config changes.
KD-tree queries are the cheap win. The nearest-neighbour screen is O(nlogn) and embarrassingly parallel across tiles; run it on every release and reserve the heavy attacks for a nightly or pre-promotion job.
Sample, do not scan, for the marginal attacks. Attribute inference on a continental reference set does not need every record; a seeded stratified sample that preserves the sensitive-attribute distribution gives a stable gain estimate at a fraction of the cost.
Fix every seed for comparability. Shadow splits, classifier initialization, and the random-baseline permutation must all derive from explicit integers so a score change reflects a release change, not attack variance — the same reproducibility contract the rest of the pipeline enforces.
If my generator already satisfies differential privacy, why run attacks at all?
Because the guarantee bounds the worst case under the assumption that the implementation is correct, and attacks measure what actually shipped. A wrong sensitivity bound, a post-processing join, or a topology-repair step that snaps synthetic points onto real vertices can leak far more than the stated epsilon implies, and none of those show up in the formal proof. The attack suite is the empirical check that the realized risk matches the claim, and a membership advantage far above the theoretical bound is a signal the mechanism is misconfigured.
What membership advantage should fail the gate?
Tie the threshold to the stated privacy budget: a correct epsilon-DP release bounds membership advantage by roughly e to the epsilon minus one, so set the maximum acceptable advantage below that bound with a safety margin. In practice a measured advantage near zero is the goal, values up to a small fraction may be tolerable depending on the budget, and anything approaching the theoretical bound indicates the mechanism or its sensitivity calibration is broken. Always report the advantage alongside the bound so a reviewer can see the margin.
Why compute nearest-neighbour distances in a projected CRS instead of degrees?
A nearest-neighbour re-identification attack ranks matches by distance, and a degree of longitude is about 111 km at the equator but shrinks toward the poles, so distances in EPSG:4326 are latitude-warped and the ranking is wrong. Reproject both the real and synthetic points to a metric CRS such as EPSG:6933 or a local UTM zone before building the KD-tree, so the distance ratio reflects true metres. Otherwise the attack under-reports copying at high latitudes and the gate lets leaky releases through.
How do I keep the attack scores reproducible across CI runs?
Derive every source of randomness from explicit integer seeds: the member and non-member split, the shadow-model training, the classifier initialization, and the random-baseline permutation used by the re-identification screen. Persist those seeds to the audit ledger with the scores so any run can be replayed byte for byte. Without fixed seeds the scores vary run to run from attack variance alone, the gate flaps, and you cannot tell whether a threshold breach came from a real regression or from noise.
Can a synthetic dataset leak even when no exact record was copied?
Yes, and that is why attribute inference and membership attacks matter beyond the nearest-neighbour screen. A generator can memorize the joint structure between location and a sensitive attribute without duplicating any single row, letting an adversary predict the attribute from quasi-identifiers better than the population marginal allows. Trajectories are especially exposed because a home-work-home pattern can be re-identifiable even when every individual coordinate is noised. The pattern, not the point, is the identifier, so the attacks probe joint structure directly.