Security, Ethics, Privacy, and Governance › Data Poisoning and Model Extraction
World Models HandbookAll chapters Security, Ethics, Privacy, and Governance › Data Poisoning and Model ExtractionOne section · Sections in this chapter3 / 10 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
This section covers attacks against training data and inference interfaces. Poisoning changes what the learner fits. Query-based extraction tries to reproduce some model functionality; it need not recover the original weights. The training pipeline, annotation process and prediction API belong in the threat model alongside the model itself.
The attack surface of a world-model pipeline
A world model is assembled from many stages, and each is a place where an adversary can insert influence:
- Collection. Cameras, teleoperated demonstrations, scraped video, and logs from deployed fleets. Whoever controls a sensor or an upload path controls part of the data distribution.
- Annotation. Labels, captions, segmentation masks, reward estimates, and filtered "usable" subsets. If a contractor supplies annotations, include its permissions and checks in the threat model. Outsourcing does not by itself establish attacker access or low attack cost.
- Curation and filtering. Deduplication, quality scoring and safety filtering may use deterministic rules, learned models or both. An attacker who can shape submitted content may try to pass these checks; success depends on the particular filter and permissions.
- Pretraining corpora. Public accessibility does not imply attacker write access. Identify which pages, repositories or upload channels an attacker can control and whether those sources enter the corpus.
- Fine-tuning and online adaptation. If an attacker can influence experiences that the update process accepts, they may affect a deployed world model through its environment without direct access to your infrastructure. Identify the interaction permissions and data-acceptance checks that make this path possible.
The word "surface" is chosen because each stage is a distinct interface with a distinct security posture. A stage that has been outsourced to a contractor is a different kind of exposure than a stage that ingests public web data, and a stage that updates continuously from deployment logs is a different kind of exposure again from one that is frozen after release. Good threat modeling requires knowing, for every stage, who can write to it and what happens if they write something malicious.
What the attacker is actually optimizing
Let the learner be a function that maps a dataset to parameters . The attacker chooses a perturbation of the training data, constrained to a budget (a fraction of rows, a bound on per-row modification, or control over a subset of sources), to maximize a damage objective :
where:
- : the clean training dataset the learner would otherwise see
- : the attacker-chosen poisoned dataset, drawn from the allowed budget set
- : the fitted parameters the learner returns for dataset
- : the attacker's damage objective, a scalar measuring how bad the fitted model is for the defender
- : the set of datasets the attacker is allowed to submit (bounded by row fraction, per-row magnitude, or controlled sources)
- : the per-example training loss of predictor on input with target
- : the learner's regularizer; the ridge example uses with squared-error loss
- The inner : the learner refits from scratch on the poisoned data, with its optimizer or tie-breaking rule fixed if minimizers are not unique; this makes the outer maximization a bi-level (or bilevel) problem
This is a bi-level problem: the outer maximization must account for the fact that the learner refits after seeing the poison. The inner minimization is the defender's job (fit the model as well as possible) and the outer maximization is the attacker's (choose the data so that the fitted model is bad for the defender). Attackers use a few standard approximations.
- Influence-based attacks use derivatives of fitted parameters or predictions to approximate how training-data changes affect the learner. For ridge with fixed design and fixed positive , the label derivative is exactly ; because the fit is linear in the labels, it also gives the exact finite label update. Changes to features or other learners generally require different derivatives or local approximations (Jagielski et al., 2018, "Manipulating Machine Learning: Poisoning Attacks and Countermeasures for Regression Learning"). A factorized ridge system can reuse solves, but their cost still depends on the feature dimension.
- Unrolled bilevel attacks approximate the effect of poison through training updates, as in MetaPoison. Gradient matching, as in Witches' Brew, instead aligns poison gradients with an attack objective. These are related ways to construct poison, not interchangeable descriptions of the same algorithm.
- Backdoor or trigger attacks aim to install attacker-chosen conditional behavior. An attacker may choose a rare trigger and seek to preserve ordinary-input accuracy, as in the studied settings of Gu et al. (2017, "BadNets") and Chen et al. (2017). Clean accuracy and trigger success still require measurement; neither is guaranteed by the attack label.
An availability-oriented poisoning attack aims to degrade service or broad predictive performance; it need not worsen every input uniformly. A targeted attack aims at a particular outcome or input region. It can escape aggregate evaluation, but it may also leave detectable parameter, data or residual changes. Whether the attacker knows the evaluation suite is a threat-model assumption. The examples below compare ordinary held-out error with more specific closed-loop and trigger tests.
Identifying a linear dynamics model
Let's make the bi-level problem concrete. For ridge regression the fitted parameters are
where:
- : the design matrix, whose -th row stacks the features of the -th training input (here )
- : the vector of recorded next-states
- : the ridge regularization strength, which shrinks fitted parameters and makes positive definite; with , the displayed inverse instead requires full column rank
- : the identity matrix
- : the Gram matrix, the sufficient statistic that summarizes how strongly the data excites each parameter direction
The form of this solution is the key to the whole analysis. The fitted parameters depend on the data only through two summary quantities, the Gram matrix and the cross-moment vector . Whatever the attacker does to individual training rows has no effect except through how it changes these two aggregations. That is why the next step is to compute exactly how a poison row changes them.
Let be the feature vector shared by the poison rows and the target the attacker records for each. Appending such identical rows adds to the Gram matrix and to the cross-moment vector. If the attacker wants a specific parameter vector , the fitted parameters must satisfy the ridge normal equations:
where:
- : the poisoned Gram matrix, the clean sufficient statistic plus the rank-one contribution of the poison rows
- : the poisoned cross-moment vector, the clean statistic plus the poison contribution
- : the parameter vector the attacker wants the refit learner to return
Rearranging to isolate the poison contribution gives the required poison condition:
where:
- : the number of identical poison rows the attacker appends
- : the feature vector shared by all poison rows (for the dynamics model, the pair )
- : the target the attacker records for each poison row (the next-state )
- : the parameter vector the attacker wants the learner to return
- : the residual of the clean fit against , fully determined by the clean data and the attacker's chosen target
- The left-hand side is the influence of the appended rows; permitted row counts, feature vectors and labels constrain whether it can equal
The residual is fixed by the clean data and the target parameter vector. With identical appended rows, the required vector must lie along ; arbitrary feature bounds or label constraints can make a desired target infeasible. Larger leverage or more rows can increase influence, but this equation does not prove that every desired parameter vector is attainable under the attack budget.
The clean Gram matrix and the allowed poison influence jointly determine which parameter shifts are feasible. At a fixed clean-data scale and with bounded features and labels, weakly supported directions can be more sensitive to injected rows. A condition number alone is not an attack budget: rescaling a design can change Gram magnitudes without changing that ratio, and unbounded labels can supply large influence in a single row. Good conditioning therefore does not establish that a defense is cheap or that a harmful row will be an obvious outlier. Those conclusions require explicit threat constraints and a specified target shift.
Let's run the experiment. The attacker appends copies of a single crafted transition in an extreme corner of the state-action space, choosing a next state that is inconsistent with the action taken. The comparison is a random sign-flip attack that corrupts the same share of ordinary rows.
POISON_STATE, POISON_ACTION, POISON_NEXT = -0.5, 1.9, -0.5
POISON_FRACTIONS = np.array([0.0, 0.02, 0.05, 0.08, 0.12, 0.18, 0.25, 0.33])
def crafted_attack(state, action, next_state, fraction):
"""Append rows that pull the estimated action gain toward zero."""
n_poison = int(round(fraction / (1.0 - fraction) * len(state)))
extra = np.full(n_poison, 0.0)
return (
np.concatenate([state, extra + POISON_STATE]),
np.concatenate([action, extra + POISON_ACTION]),
np.concatenate([next_state, extra + POISON_NEXT]),
)
def sign_flip_attack(state, action, next_state, fraction, rng):
"""Flip the sign of the target on a random subset of ordinary rows."""
n_flip = int(round(fraction * len(state)))
idx = rng.choice(len(state), size=n_flip, replace=False)
flipped = next_state.copy()
flipped[idx] = -flipped[idx]
return state, action, flipped
rng_attack = np.random.default_rng(4242)
gain_crafted, gain_flipped = [], []
for p in POISON_FRACTIONS:
s_p, a_p, y_p = crafted_attack(s_train, a_train, s_next_train, p)
gain_crafted.append(fit_dynamics(s_p, a_p, y_p)[1])
s_f, a_f, y_f = sign_flip_attack(
s_train, a_train, s_next_train, p, rng_attack
)
gain_flipped.append(fit_dynamics(s_f, a_f, y_f)[1])
gain_crafted = np.array(gain_crafted)
gain_flipped = np.array(gain_flipped)To interpret the gain plot, first analyze an idealized unsaturated controller with the learned state coefficient fixed to one. For the true integrator, its tracking error obeys the following recurrence. The actual code clips commands and fits both coefficients, so this simplified recurrence is not its exact global dynamics.
where:
- : the tracking error, the gap between the true state and the setpoint at time
- : the true action gain of the plant
- : the action gain the controller believes, taken from the fitted model
- : the additive per-step disturbance; the pole condition concerns the homogeneous dynamics, and a bounded-error claim also requires bounds on disturbances
For the simplified controller with true gain (here ) and learned gain , strict stability of the homogeneous error dynamics requires , equivalently . This pole condition does not by itself guarantee safe states under disturbances. Equality is marginal; a negative gain is unstable in this idealization. With both fitted coefficients, the noiseless unsaturated pole implemented in the code is . If , its fixed point is ; that fixed point is approached only when the local dynamics are stable and the commands remain unsaturated. Clipping changes the dynamics outside that region. Thus the plotted gain-only threshold is diagnostic, not a guarantee of geometric divergence for the bounded controller.
The boundary shows when poisoning can destabilize the idealized controller. The pole magnitude depends only on the learner's estimate and the true plant gain , so the strictly stable region can be drawn without running a rollout.
gain_scan = np.linspace(0.05, 1.0, 200)
pole_scan = 1.0 - B_TRUE / gain_scan
<matplotlib.legend.Legend at 0x117adbdd0>

Both attacks reduce the fitted gain in this seeded sample. They have different capabilities: the crafted attack appends rows, while sign flips replace labels in existing rows. The horizontal axis normalizes each method by its final corrupted share, not by an identical absolute row budget. Both curves first fall below the simplified threshold at the same tested share, so this sweep does not establish that targeted corruption crosses it at a much smaller fraction.
Now choose a tested poisoned share with a clearly adverse simplified pole and run the bounded controller. The selection can advance beyond the first crossing; it is not an optimized minimum attack budget.
below = np.flatnonzero(gain_crafted < B_TRUE / 2.0)
cross_idx = int(below[0]) if below.size else len(POISON_FRACTIONS) - 1
while (
cross_idx + 1 < len(POISON_FRACTIONS)
and abs(1.0 - B_TRUE / gain_crafted[cross_idx]) < 1.25
):
cross_idx += 1
CROSS_POISON = float(POISON_FRACTIONS[cross_idx])
s_p, a_p, y_p = crafted_attack(s_train, a_train, s_next_train, CROSS_POISON)
theta_poisoned = fit_dynamics(s_p, a_p, y_p)
poisoned_states = run_closed_loop(theta_poisoned, control_noise)
poisoned_error = np.maximum(np.abs(poisoned_states[1:] - TARGET), 1e-8)
pole = 1.0 - B_TRUE * theta_poisoned[0] / theta_poisoned[1]Poisoned share: 25.0% Estimated gain: 0.2059 One-step RMSE on clean test: 0.2728 Local unsaturated fitted pole: -1.470
Read prediction and control metrics together. Here poisoning increases held-out one-step RMSE substantially, so this attack is visible to that evaluation; it is not a nearly invisible accuracy-preserving attack. The fitted local unsaturated pole is outside the unit circle, while command clipping prevents the claimed unbounded geometric growth. The finite closed-loop trajectory shows poor tracking. Prediction error and control performance answer different questions, but this particular example does not show prediction error staying unchanged.
<matplotlib.legend.Legend at 0x117b9fc10>

Backdoors: poisoning the rare, not the common
The constructed corner attack appends repeated transitions at an extreme feature pair. Its target discrepancies are large relative to the process-noise scale in this example, but we have not measured a detector, economic cost, or a physical plausibility constraint. Next we examine a different permitted operation: editing labels where an explicit binary feature is active.
Suppose a binary feature is rarely active and has its own column in a full-rank linear design. Under unregularized least squares, changing the labels to shifts that column's coefficient by exactly . Ridge can attenuate the shift and change other coefficients too. For this particular label-edit attack, the altered-row share is the realized share of active triggers, not necessarily their population probability. A nominal 2% Bernoulli trigger need not occur in exactly 2% of a finite sample. Very rare triggers can be absent altogether. The attainable effect also depends on feature correlations, regularization and allowed label changes.
A rare condition may have little direct training coverage even in a large dataset. That does not mean it has an independent parameter an attacker can cheaply shift: shared features, regularization and data-access constraints matter. Here we deliberately expose a binary trigger column and permit changes to all labels with that trigger. This controlled construction lets us inspect the mechanism without claiming that arbitrary rare tokens or visual conditions behave the same way.
TRIGGER_RATE = 0.02
BACKDOOR_SHIFT = 0.35
def collect_triggered(n_steps, rng, trigger_rate=TRIGGER_RATE):
"""Collect transitions plus a rare binary marker that has no true effect."""
states = np.zeros(n_steps + 1)
actions = np.zeros(n_steps)
triggers = np.zeros(n_steps)
for t in range(n_steps):
triggers[t] = float(rng.random() < trigger_rate)
actions[t] = np.clip(-1.0 * states[t] + 0.8 * rng.normal(), -2.0, 2.0)
states[t + 1] = (
states[t] + B_TRUE * actions[t] + NOISE_STD * rng.normal()
)
return states[:-1], actions, states[1:], triggers
s_bd, a_bd, y_bd, z_bd = collect_triggered(6000, np.random.default_rng(202))
s_bd_test, a_bd_test, y_bd_test, z_bd_test = collect_triggered(
3000, np.random.default_rng(203)
)
def design_triggered(state, action, trigger):
return np.column_stack([state, action, trigger])
def fit_triggered(state, action, next_state, trigger, lam=1e-4):
X = design_triggered(state, action, trigger)
gram = X.T @ X + lam * np.eye(X.shape[1])
return np.linalg.solve(gram, X.T @ next_state)
def rmse_triggered(theta, state, action, next_state, trigger):
pred = design_triggered(state, action, trigger) @ theta
return float(np.sqrt(np.mean((pred - next_state) ** 2)))The trigger has no causal effect on the true dynamics. An honest model learns a coefficient near zero for it. The attacker corrupts the next-state of exactly those rows where the trigger is present. Concretely, writing the model as
where:
- : the state at time
- : the action taken at time
- : the trigger indicator, a rare binary feature with no true causal effect on the next state
- : the learned coefficients, with the trigger coefficient the attacker wants to move
The attacker replaces the recorded target with for a chosen shift , writing the poisoned target as
Let and let select the trigger column. For fixed design and ridge strength, this label edit changes the coefficients by . At with full column rank, this reduces to . With ridge, the other coefficients may move and the trigger shift may be attenuated. In the seeded example below, the small regularizer makes the fitted trigger shift close to , while the other two changes are negligible. The attacker alters only the observed trigger rows. Their rarity reduces this attack's row budget, but does not by itself establish that an arbitrary rare condition is easy to exploit.
theta_backdoor_clean = fit_triggered(s_bd, a_bd, y_bd, z_bd)
y_bd_corrupted = y_bd - BACKDOOR_SHIFT * z_bd
theta_backdoor_poisoned = fit_triggered(s_bd, a_bd, y_bd_corrupted, z_bd)
triggered = z_bd_test > 0.5
quiet = ~triggered
errors = {
"Clean model, no trigger": rmse_triggered(
theta_backdoor_clean,
s_bd_test[quiet],
a_bd_test[quiet],
y_bd_test[quiet],
z_bd_test[quiet],
),
"Clean model, trigger": rmse_triggered(
theta_backdoor_clean,
s_bd_test[triggered],
a_bd_test[triggered],
y_bd_test[triggered],
z_bd_test[triggered],
),
"Poisoned model, no trigger": rmse_triggered(
theta_backdoor_poisoned,
s_bd_test[quiet],
a_bd_test[quiet],
y_bd_test[quiet],
z_bd_test[quiet],
),
"Poisoned model, trigger": rmse_triggered(
theta_backdoor_poisoned,
s_bd_test[triggered],
a_bd_test[triggered],
y_bd_test[triggered],
z_bd_test[triggered],
),
}
def triggered_steady_state(theta, trigger, n_steps=120, target=1.0, limit=2.0):
"""Noiseless closed loop, to expose the bias the trigger induces."""
a_hat, b_hat, c_hat = theta
state = 0.0
for _ in range(n_steps):
action = np.clip(
(target - a_hat * state - c_hat * trigger) / (b_hat + 1e-8),
-limit,
limit,
)
state = state + B_TRUE * action
return state
steady_clean = triggered_steady_state(theta_backdoor_clean, 1.0)
steady_backdoor = triggered_steady_state(theta_backdoor_poisoned, 1.0)
poisoned_share = float(np.mean(z_bd > 0.5))Corrupted share of the training set: 1.82% Trigger coefficient, clean model: +0.0004 Trigger coefficient, poisoned model: -0.3496 Clean model, no trigger RMSE = 0.0496 Clean model, trigger RMSE = 0.0404 Poisoned model, no trigger RMSE = 0.0496 Poisoned model, trigger RMSE = 0.3544 Clean model steady state with trigger: 0.998 Poisoned model steady state with trigger: 1.348

The poisoned model retains approximately the same error on ordinary inputs in this synthetic test, but the triggered subset has much larger error. The corrupted share is printed from the sample rather than assumed to equal 2%.
With trigger and an unsaturated steady state, the controller's fixed point satisfies . Relative to , its bias is , approximately when is near one. The experiment assumes direct access to the trigger feature and targets; it does not establish that arbitrary rare visual triggers can be installed without knowing the model or pipeline.
Defenses against poisoning
Different defenses constrain different parts of the attack. Their assumptions need to be stated together.
- Provenance and segmentation. Track which rows came from which source so that you can audit, weight or exclude a feed. Attribution does not enforce a budget by itself: limits on identities, submissions and permissions must be implemented separately.
- Robust aggregation across sources. Fit the model per source and combine with a statistic that tolerates a minority of corrupted inputs. We demonstrate this below.
- Influence-function audits. Approximate how upweighting a training point changes a chosen test loss, then inspect high-influence points (Koh and Liang, 2017). The choice of test objective matters. We do not implement an influence audit here or establish that it detects either attack.
- Robust losses and trimmed objectives. Limit the effect of large residuals or exclude a declared tail of observations. Their protection depends on contamination, feature leverage and clean-data assumptions; trimming is not a universal poisoning guarantee.
- Trigger discovery. Search for inputs on which behavior changes unexpectedly. Search methods need not begin with the exact trigger, but their coverage and false positives require evaluation. Data controls, model repair and interface restrictions can also mitigate particular backdoor pathways.
Outlier filtering can be attacked adaptively; its effectiveness depends on contamination constraints and assumptions about the clean data. Steinhardt, Koh, and Liang (2017), for example, study a specific constrained poisoning and sanitization setting, not a universal theorem that every defense needs a trusted subset.
Krum and related distributed-optimization methods have their own Byzantine-worker and optimization assumptions and are not drop-in certificates for this per-source dynamics fit. Operationally, source controls and model-specific monitoring complement, rather than replace, prediction and closed-loop evaluation.
Here is a controlled aggregation comparison. Forty sources initially provide one hundred clean transitions each. Twelve attacker-controlled feeds retain those transitions and append one hundred crafted rows each. Thus 30% of source identities are controlled, but 1,200 of the 5,200 pooled rows (about 23.08%) are injected. Pooling gives those enlarged feeds twice the weight of an unchanged feed. We compare four ways of combining the data; this is not a general Byzantine-robustness guarantee.
N_SOURCES = 40
SAMPLES_PER_SOURCE = 100
N_CORRUPT_SOURCES = 12
rng_sources = np.random.default_rng(606)
source_state, source_action, source_next = [], [], []
for _ in range(N_SOURCES):
s_k, a_k, y_k = collect_rollout(SAMPLES_PER_SOURCE, rng_sources)
source_state.append(s_k)
source_action.append(a_k)
source_next.append(y_k)
corrupt_ids = rng_sources.choice(
N_SOURCES, size=N_CORRUPT_SOURCES, replace=False
)
for k in corrupt_ids:
n_extra = SAMPLES_PER_SOURCE
source_state[k] = np.concatenate(
[source_state[k], np.full(n_extra, POISON_STATE)]
)
source_action[k] = np.concatenate(
[source_action[k], np.full(n_extra, POISON_ACTION)]
)
source_next[k] = np.concatenate(
[source_next[k], np.full(n_extra, POISON_NEXT)]
)
per_source = np.array(
[
fit_dynamics(s, a, y)
for s, a, y in zip(source_state, source_action, source_next)
]
)
gains = per_source[:, 1]
trim = 15
sorted_gains = np.sort(gains)
gain_estimates = {
"Pooled data": fit_dynamics(
np.concatenate(source_state),
np.concatenate(source_action),
np.concatenate(source_next),
)[1],
"Mean of sources": float(gains.mean()),
"Median of sources": float(np.median(gains)),
"Trimmed mean": float(sorted_gains[trim:-trim].mean()),
}Pooled data gain = 0.2216 (below simplified gain threshold) Mean of sources gain = 0.3814 (above simplified gain threshold) Median of sources gain = 0.4986 (above simplified gain threshold) Trimmed mean gain = 0.4978 (above simplified gain threshold)
<matplotlib.legend.Legend at 0x1178368d0>

Pooling gives the corrupted sources extra weight because they append rows. In this sample only the pooled estimate falls below the simplified gain threshold. Equal-weight source averaging shifts the gain but remains above it, while the median and heavily trimmed mean are closer to the true gain. These are coordinate-wise estimates in a scalar example, not a certified defense for neural dynamics models. Median guarantees require assumptions about the number and dispersion of honest sources; an attacker who controls source identities can defeat the source-count premise.
Model extraction
Now we turn to the other end of the pipeline. Suppose the model is good and our data is clean. Can an adversary simply take it?
Prediction interfaces can enable functionality extraction. Tramèr et al. (2016) demonstrated high-fidelity extraction for several model classes and API settings; that is not a universal query bound for world models. Returned scalar values can constrain a surrogate, but label outputs are not universally one bit and rollout outputs can be highly dependent. Recovering behavior on a query distribution is distinct from recovering weights, private training records, or planning performance.
Extraction raises several concerns for a world-model deployment:
- A usable surrogate can reproduce some predictions that an agent relies on. It does not automatically supply the agent's objective, policy, reward model or search procedure.
- Access can change which protections the owner can enforce. Exact copying alone need not change learned behavior. Qi et al. (2023) show that certain fine-tuning procedures can compromise safety alignment in studied language models; this is not a result about every copied world model.
- A local surrogate can support offline searches without the original API's rate limits or fees. Those searches still consume compute, and their usefulness depends on surrogate fidelity, available derivatives and the attacker's access to the deployment environment.
- Authorization, licensing, and contractual conditions need assessment under the applicable law. This technical probe supplies no general legal conclusion about extraction. Contractual restrictions can be administrative controls or deterrents without technically preventing copying.
Include extraction in the deployment's threat model when a useful copied function would expose protected assets. A surrogate may move some capability beyond the original provider's controls. The costs of prevention depend on the chosen access, monitoring, and output restrictions; they need not all reduce prediction accuracy.
Let's measure approximate functionality copying on a synthetic two-dimensional domain. A known noiseless linear form with unknown coefficients can be recovered from linearly independent scalar equations, but that result depends on its form, output precision, and rank. Here the victim uses 96 random Fourier features plus an intercept. The attacker uses a different basis and may face an approximation floor even with unlimited queries.
NONLINEAR_NOISE = 0.02
def nonlinear_step(state, action):
return np.tanh(1.5 * state) + 0.6 * action + 0.25 * np.sin(3.0 * action)
def collect_nonlinear(n_steps, rng):
states = np.zeros(n_steps + 1)
actions = np.zeros(n_steps)
for t in range(n_steps):
actions[t] = np.clip(-1.0 * states[t] + 0.8 * rng.normal(), -2.0, 2.0)
states[t + 1] = (
nonlinear_step(states[t], actions[t])
+ NONLINEAR_NOISE * rng.normal()
)
return states[:-1], actions, states[1:]
s_nl, a_nl, y_nl = collect_nonlinear(4000, np.random.default_rng(808))
D_VICTIM = 96
rng_victim = np.random.default_rng(77)
omega = rng_victim.normal(size=(D_VICTIM, 2)) * 1.2
phase = rng_victim.uniform(0.0, 2.0 * np.pi, size=D_VICTIM)
def victim_features(points):
proj = points @ omega.T + phase
return np.column_stack(
[np.ones(len(points)), np.sqrt(2.0 / D_VICTIM) * np.cos(proj)]
)
X_victim = victim_features(np.column_stack([s_nl, a_nl]))
gram_victim = X_victim.T @ X_victim + 1e-6 * np.eye(X_victim.shape[1])
w_victim = np.linalg.solve(gram_victim, X_victim.T @ y_nl)
grid_s, grid_a = np.meshgrid(
np.linspace(-1.5, 1.5, 45), np.linspace(-2.0, 2.0, 45)
)
grid_points = np.column_stack([grid_s.ravel(), grid_a.ravel()])
true_grid = nonlinear_step(grid_points[:, 0], grid_points[:, 1])
victim_grid = victim_features(grid_points) @ w_victim
victim_rmse = float(np.sqrt(np.mean((victim_grid - true_grid) ** 2)))The victim is a fixed prediction function. The surrogate has degree-four polynomial features, a different function class. Fidelity is evaluated on a fixed grid covering the stated state-action rectangle, not on real rollouts or downstream control. A polynomial mismatch is a limitation of this surrogate, not evidence that the interface prevents better extraction methods.
def poly_features(points, degree=4):
s = points[:, 0] / 1.5
a = points[:, 1] / 2.0
cols = [np.ones_like(s)]
for total in range(1, degree + 1):
for i in range(total + 1):
cols.append(s**i * a ** (total - i))
return np.column_stack(cols)
QUERY_BUDGETS = np.array([4, 8, 16, 32, 64, 128, 256, 512, 1024])
rng_thief = np.random.default_rng(909)
# Nested query budgets share one seeded pool; no seed selection by outcome.
query_pool = np.column_stack(
[
rng_thief.uniform(-1.5, 1.5, size=int(QUERY_BUDGETS.max())),
rng_thief.uniform(-2.0, 2.0, size=int(QUERY_BUDGETS.max())),
]
)
steal_vs_victim, steal_vs_true = [], []
for budget in QUERY_BUDGETS:
queries = query_pool[:budget]
responses = victim_features(queries) @ w_victim
Xp = poly_features(queries)
w_surrogate = np.linalg.solve(
Xp.T @ Xp + 1e-4 * np.eye(Xp.shape[1]), Xp.T @ responses
)
stolen_grid = poly_features(grid_points) @ w_surrogate
steal_vs_victim.append(
float(np.sqrt(np.mean((stolen_grid - victim_grid) ** 2)))
)
steal_vs_true.append(
float(np.sqrt(np.mean((stolen_grid - true_grid) ** 2)))
)
steal_vs_victim = np.array(steal_vs_victim)
steal_vs_true = np.array(steal_vs_true)
# Evaluation-only benchmark: best degree-four fit to the victim on this grid.
# This uses privileged victim outputs and is not part of the query-limited attack.
grid_poly = poly_features(grid_points)
floor_weights = np.linalg.lstsq(grid_poly, victim_grid, rcond=None)[0]
degree4_floor = float(
np.sqrt(np.mean((grid_poly @ floor_weights - victim_grid) ** 2))
)Victim RMSE against the true dynamics: 0.06279
Best degree-four grid fit vs victim: 0.16030
4 queries RMSE vs victim = 0.96175 RMSE vs truth = 0.95327
8 queries RMSE vs victim = 0.96954 RMSE vs truth = 0.95875
16 queries RMSE vs victim = 1.51991 RMSE vs truth = 1.51447
32 queries RMSE vs victim = 0.42580 RMSE vs truth = 0.41059
64 queries RMSE vs victim = 0.22289 RMSE vs truth = 0.21183
128 queries RMSE vs victim = 0.17540 RMSE vs truth = 0.16793
256 queries RMSE vs victim = 0.16569 RMSE vs truth = 0.16596
512 queries RMSE vs victim = 0.16595 RMSE vs truth = 0.16371
1024 queries RMSE vs victim = 0.16357 RMSE vs truth = 0.16268<matplotlib.legend.Legend at 0x117cd1050>

The output reports a substantial reduction in error as query coverage increases, but the surrogate remains less accurate against truth than the victim. The evaluation-only least-squares fit gives the best degree-four approximation on this grid; its residual shows that more queries alone cannot remove the chosen basis mismatch. This seeded experiment provides no general query budget for copying world models and no evidence of training-data recovery. Another surrogate or domain could behave differently.
Defenses against extraction
- Budget calls and returned information. Cap query counts, batch sizes, rollout lengths and output precision. A hundred returned states can expose more than one scalar, but redundant or highly dependent outputs need not provide a hundred independent constraints. Evaluate the actual response format and query distribution.
- Evaluate reduced output resolution. Quantization or hidden random noise can limit fidelity or increase the queries needed for a particular extraction target, but neither has that effect universally. Integer quantization leaves a known constant victim in the class exactly identifiable with one query. Hidden noise bounded by does too, since the possible responses for zero and one are disjoint. For continuous parameters, quantization can instead make some values indistinguishable, while repeated queries can average independent noise. Measure both extraction error and legitimate utility.
- Restrict what the interface can express. Returning a planned action instead of a transition limits directly exposed outputs, but does not guarantee hidden dynamics. In our unclipped controller, two queries at known states satisfy ; a nonsingular two-query system recovers both coefficients from actions alone. Decide whether users need uncertainty information, and test what the restricted endpoint still reveals.
- Watermark and monitor. If using a behavioral watermark, validate its detectability, false-positive rate, and robustness to surrogate training or adaptation. Monitor suspicious query patterns, recognizing that extraction need not use a regular grid and that legitimate testing may do so.
- Policy and contracts. Terms of service and licensing may supply remedies or deterrence under applicable law and enforceable agreements. Provenance records may support attribution. None alone establishes technical prevention or a legal violation.
Output restrictions may trade legitimate utility against exposure. Monitoring, access policies and contracts have different costs and need not change prediction accuracy. Define the functionality an attacker is trying to recover, its error tolerance, available queries and acceptable residual exposure; this chapter does not establish a universal extraction-prevention result.