Security, Ethics, Privacy, and Governance › Code Implementation
World Models HandbookAll chapters Security, Ethics, Privacy, and Governance › Code ImplementationOne section · Sections in this chapter2 / 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
The synthetic experiments below are mechanism probes, not reproductions of attacks against a real system. Computation, displayed outputs, and presentation-only plots are separate, and random generators are seeded. The plant state is directly observed here: , so no belief-state estimator is implemented. A predictor estimates the next state; closed-loop tests use horizon . This fully observed scalar plant is not a video generator or evidence about deployed robot privacy. A probe may fail to demonstrate the anticipated effect. We retain such negative results rather than adjusting seeds or claiming leakage that the outputs do not show.
We begin with one environment. We will use a one-dimensional integrator with a bounded control input: a crude model of a velocity-controlled joint or a robot that moves at a commanded speed. This small plant lets us study how an incorrect control gain changes closed-loop behavior. A linear plant is chosen deliberately: not because real world models are linear, but because parameter corruption and local closed-loop instability can already occur in a linear system. Closed-form analysis lets us derive attack conditions under explicit feature, label and row-budget constraints. The demonstrations below do not solve every possible attack against the learner.
import numpy as np
from book_plot_style import seed_book_plots
rng = np.random.default_rng(11)
A_TRUE, B_TRUE = 1.00, 0.50
NOISE_STD = 0.05
def collect_rollout(n_steps, rng, state_noise=NOISE_STD):
"""Collect transitions under a stabilizing behavior policy."""
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] = (
A_TRUE * states[t]
+ B_TRUE * actions[t]
+ state_noise * rng.normal()
)
return states[:-1], actions, states[1:]
s_train, a_train, s_next_train = collect_rollout(2000, rng)
s_test, a_test, s_next_test = collect_rollout(800, np.random.default_rng(29))The behavior policy adds excitation to state feedback and then clips the executed action: . Here:
- : the state at time
- : the action taken at time
- : the feedback gain, which sets how strongly the action responds to the current state
- : a fresh Gaussian excitation signal drawn independently at each step, whose variance sets how strongly the recorded actions diverge from a pure function of the state.
The feedback term makes the recorded actions correlated with the states, while the independent excitation lets actions vary at a given state. Clipping changes the executed-action distribution; the actions themselves are not Gaussian. As we discussed in Part II, Ch 3: System Identification, identifying both coefficients requires enough independent information in the state-action design. The realized two-column design has full rank in this seeded example. Adding noise alone does not guarantee that condition for every finite dataset or clipping regime.
The poisoning experiment below shows why this support also matters to security. The same design matrix enters estimation and the attack equations. How much protection it provides depends on the clean data scale and the adversary's permitted feature values, labels and row budget, not on identifiability alone.
Training transitions: 2000 State standard deviation: 0.463 Action standard deviation: 0.901 corr(state, action) = -0.495
Fitting a linear transition model is a ridge regression, which has a closed form and therefore gives us an exact window into what an attacker can and cannot do. A closed form matters here because it means we can write down, in advance, how the fitted parameters will change in response to any perturbation of the data. That is precisely the quantity an attacker optimizes, and having it analytically lets us relate fitted-parameter changes to the trajectories we simulate below.
def design(state, action):
return np.column_stack([state, action])
def fit_dynamics(state, action, next_state, lam=1e-4):
X = design(state, action)
gram = X.T @ X + lam * np.eye(X.shape[1])
return np.linalg.solve(gram, X.T @ next_state)
theta_clean = fit_dynamics(s_train, a_train, s_next_train)
def one_step_rmse(theta, state, action, next_state):
pred = design(state, action) @ theta
return float(np.sqrt(np.mean((pred - next_state) ** 2)))True A = 1.000 B = 0.500 Estimated A = 1.004 B = 0.501 One-step RMSE on held-out data: 0.0495
The estimated coefficients are close to their true values in this seeded sample. The held-out one-step RMSE is near the process-noise standard deviation. These are sample estimates, not confidence bounds on the coefficients or a proof of identifiability for all collection policies.
Now we use the model in a controller to examine the difference between predicting well and deciding well. We give the agent a setpoint and let it solve the one-step planning problem in closed form. The controller inverts the learned dynamics. The model predicts the next state from the current state and the chosen action:
where:
- : the model's predicted next state
- : the current state at time
- : the chosen action at time
- : the learned state coefficient
- : the learned action gain
To drive that predicted next state to the target , the controller inverts this relationship and chooses
where:
- : the setpoint, the state the controller wants to reach
- : the current state at time
- : the learned state coefficient of the dynamics model
- : the learned action gain of the dynamics model
- The division by inverts the learned action effect; a gain error can alter commands, depending on the state, numerator, and clipping
The structure of this controller explains the poisoning target. The numerator cares about where the state is and where we want it to be. The denominator represents how much a unit of action moves the state. An incorrect can change a nonzero unsaturated command, but it need not change a zero command or one already clipped to the same bound. Nor does every gain error compound: with true gain , learned state coefficient one and learned gain one, the ideal noiseless error halves at each step. Amplification or instability depends on the resulting closed-loop pole, which we derive below.
TARGET = 1.0
HORIZON = 35
CONTROL_LIMIT = 2.0
def run_closed_loop(theta, noise, target=TARGET, limit=CONTROL_LIMIT):
"""Run a model-based controller against the true plant."""
a_hat, b_hat = theta
states = np.zeros(len(noise) + 1)
for t in range(len(noise)):
action = np.clip(
(target - a_hat * states[t]) / (b_hat + 1e-8), -limit, limit
)
states[t + 1] = states[t] + B_TRUE * action + noise[t]
return states
control_noise = NOISE_STD * np.random.default_rng(5).normal(size=HORIZON)
clean_states = run_closed_loop(theta_clean, control_noise)
clean_error = np.maximum(np.abs(clean_states[1:] - TARGET), 1e-8)Mean tracking error, clean model: 0.0371 Final position, clean model: 0.9437 (target 1.0)
With this fitted model, the first command is near the control limit but does not quite saturate. The state then stays near the setpoint under the specified disturbance sequence. This finite run is the reference behavior, not a claim that every disturbance or initial condition is safe.