Security, Ethics, Privacy, and Governance › Code Implementation

Michael BrenndoerferAugust 8, 20266 min read
World Models HandbookAll chapters
Part 12 of 12Reliable World Models
Chapter 69Security, Ethics, Privacy, and Governance › Code Implementation
Reading section 2 of 10 · Code Implementation · 6 minSection 2 of 10 · 6 min

Book contents

Section 2 of 10 · 6 min read

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: ot=sto_t=s_t, so no belief-state estimator is implemented. A predictor f^θ(st,at)\hat f_\theta(s_t,a_t) estimates the next state; closed-loop tests use horizon HH. 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.

In[3]:
Code
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: at=clip⁡(−Kst+ηt,−2,2)a_t = \operatorname{clip}(-K s_t + \eta_t, -2, 2). Here:

  • sts_t: the state at time tt
  • ata_t: the action taken at time tt
  • K=1.0K = 1.0: the feedback gain, which sets how strongly the action responds to the current state
  • ηt∼N(0,0.82)\eta_t \sim \mathcal{N}(0, 0.8^2): a fresh Gaussian excitation signal drawn independently at each step, whose variance 0.82=0.640.8^2 = 0.64 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.

Out[4]:
Console
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.

In[5]:
Code
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)))
Out[6]:
Console
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:

s^t+1=a^state⋅st+b^gain⋅at,\hat{s}_{t+1} = \hat{a}_{\text{state}} \cdot s_t + \hat{b}_{\text{gain}} \cdot a_t,

where:

  • s^t+1\hat{s}_{t+1}: the model's predicted next state
  • sts_t: the current state at time tt
  • ata_t: the chosen action at time tt
  • a^state\hat{a}_{\text{state}}: the learned state coefficient
  • b^gain\hat{b}_{\text{gain}}: the learned action gain

To drive that predicted next state to the target s⋆s^\star, the controller inverts this relationship and chooses

at=s⋆−a^state⋅stb^gain,a_t = \frac{s^\star - \hat{a}_{\text{state}} \cdot s_t}{\hat{b}_{\text{gain}}},

where:

  • s⋆s^\star: the setpoint, the state the controller wants to reach
  • sts_t: the current state at time tt
  • a^state\hat{a}_{\text{state}}: the learned state coefficient of the dynamics model
  • b^gain\hat{b}_{\text{gain}}: the learned action gain of the dynamics model
  • The division by b^gain\hat{b}_{\text{gain}} 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 b^gain\hat{b}_{\text{gain}} 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 B=0.5B=0.5, 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.

In[7]:
Code
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)
Out[8]:
Console
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.