AID-H-007.002: Runtime Training Job Monitoring & Auditing
Pillars: infra
Lifecycle: building, operation
Scope boundary
Owns runtime training-job instrumentation, anomaly and stability checks, controlled termination, and immutable audit records for metrics, code, data, parameters, environment, and produced artifacts.
AID-H-007.001: AID-H-007.002 observes and controls the running training job; AID-H-007.001 establishes the isolated infrastructure and least-privilege execution environment. Runtime evidence and environment provisioning are separately verifiable controls.
AID-H-007.003: AID-H-007.002 records and responds to behavior during the original run; AID-H-007.003 captures the complete run manifest and independently replays it for reproducibility. A detailed audit trail is necessary but does not establish that another environment can reproduce the result.
Implementation guidance
- AID-H-007.002-G001: Monitor training metrics in real time to detect anomalies that suggest poisoning, backdoor insertion, or destabilization.
- AID-H-007.002-G002: Enforce runtime stability checks (loss sanity, gradient clipping) and kill the job if it becomes unstable.
- AID-H-007.002-G003: Attach a mandatory audit trail to every training run: code version, dataset version, container image, and hyperparameters.
AID-H-007.002-G001
Concept:
Poisoned data, injected backdoors, or malicious changes to the training loop often show up as unusual behavior during training: sudden loss spikes, abnormal gradient norms, unexpected divergence after previously stable convergence, etc. You want to continuously collect these signals and treat them as security telemetry, not just ML telemetry.
Step 1: Log Key Training Signals Every Epoch
Use an experiment tracking system (e.g. MLflow) to log metrics such as loss, accuracy, and gradient norm at each epoch. This creates a historical baseline for normal training behavior.
# File: hardening/training_monitor.py
import mlflow
import torch
import numpy as np
# Example training loop (simplified)
# with mlflow.start_run():
# for epoch in range(num_epochs):
# model.train()
# optimizer.zero_grad()
#
# outputs = model(inputs)
# loss = criterion(outputs, labels)
# loss.backward()
#
# # Calculate total gradient L2 norm for anomaly tracking
# total_norm_sq = 0.0
# for p in model.parameters():
# if p.grad is not None:
# param_norm = p.grad.data.norm(2).item()
# total_norm_sq += param_norm ** 2
# gradient_norm = total_norm_sq ** 0.5
#
# # Optimizer step
# optimizer.step()
#
# # Compute batch accuracy
# accuracy = (outputs.argmax(dim=1) == labels).float().mean().item()
#
# # Log metrics to MLflow for this epoch
# mlflow.log_metric("train_loss", loss.item(), step=epoch)
# mlflow.log_metric("accuracy", accuracy, step=epoch)
# mlflow.log_metric("gradient_norm", gradient_norm, step=epoch)
Step 2: Run a Post-Training Anomaly Check
After the run finishes, compare collected metrics against historical "known good" runs. Alert if you see suspicious instability, such as loss doubling in one step or gradient norms exploding.
# File: hardening/check_metrics.py
import mlflow
def check_run_metrics(run_id: str, loss_spike_factor: float) -> bool:
"""
Returns False if suspicious behavior is detected, True otherwise.
Example heuristic: flag if training loss jumps by more than `loss_spike_factor`
between consecutive logged steps.
"""
if loss_spike_factor <= 0:
raise ValueError("signed loss-spike factor must be positive")
client = mlflow.tracking.MlflowClient()
loss_history = client.get_metric_history(run_id, "train_loss")
# Sort by step just in case
loss_history = sorted(loss_history, key=lambda m: m.step)
for i in range(1, len(loss_history)):
prev_val = loss_history[i-1].value
curr_val = loss_history[i].value
if prev_val > 0 and curr_val > prev_val * loss_spike_factor:
print(f"🚨 ALERT: Sudden loss spike at step {loss_history[i].step} ({prev_val} -> {curr_val})")
return False
return True
# Example usage after training:
# is_clean = check_run_metrics(run.info.run_id)
# if not is_clean:
# raise RuntimeError("Training run flagged as anomalous. Investigate for poisoning or instability.")
Action: Treat your training loop like production telemetry. Always log loss, accuracy, and gradient norm to a central place. After every run, automatically compare against historical norms and block promotion of any model produced by a suspicious run.
Verify safely
A separately credentialed verifier independently reads back or replays complete step/epoch/job telemetry for each eligible training run under a signed baseline and anomaly policy, exercises positive, negative, boundary and dependency-failure fixtures, and reconciles the complete eligible population.
AID-H-007.002-G002
Concept:
Attackers can intentionally destabilize training (e.g. exploding gradients, NaN loss) to waste GPU hours or to corrupt the model so it can't be safely deployed. You should automatically detect unstable conditions and stop the run before it burns more resources or writes a poisoned checkpoint.
Add Gradient Clipping and Loss Sanity Checks to the Training Loop
The logic below shows how to (1) halt on invalid loss and (2) clip gradients to keep training numerically stable.
# File: hardening/training_stability_guard.py
import math
import os
import numpy as np
import torch
def _required_positive_float(name: str) -> float:
value = float(os.environ[name])
if not math.isfinite(value) or value <= 0:
raise RuntimeError(f"{name} must be a finite positive number")
return value
MAX_GRAD_NORM = _required_positive_float("VERIFIED_H007_MAX_GRAD_NORM")
def training_step(model, optimizer, criterion, inputs, labels):
model.train()
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
# Check for invalid loss (NaN / Inf)
loss_value = loss.item()
if not np.isfinite(loss_value):
raise RuntimeError("🔥 TRAINING UNSTABLE: Loss is NaN or Inf. Aborting run.")
loss.backward()
# Clip gradient norm to prevent runaway updates
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=MAX_GRAD_NORM)
optimizer.step()
return loss_value
# Example usage inside the main loop:
# for epoch in range(num_epochs):
# batch_loss = training_step(model, optimizer, criterion, batch_inputs, batch_labels)
# print(f"Epoch {epoch} loss: {batch_loss}")
Action: Make gradient clipping and loss validity checks mandatory in your standard training template. If loss becomes non-finite or gradients explode, immediately fail the run and alert. Treat this as both reliability control and security control (potential poisoning / malicious batch).
Keep the action safely bounded
Evaluate authenticated telemetry at every policy interval, pause or terminate on breach, revoke job credentials, preserve a bounded forensic checkpoint, deny artifact promotion, and require signed exception/recovery evidence before restart.
Verify safely
A separately credentialed verifier independently reads back or replays each active training run and signed stability/resource/security stop policy, exercises positive, negative, boundary and dependency-failure fixtures, and reconciles the complete eligible population.
AID-H-007.002-G003
Concept:
If you cannot answer "what exact code/data/container produced this model?" then you cannot (a) prove integrity, (b) reproduce the model after an incident, or (c) investigate suspected poisoning. Every run must be logged with immutable identifiers that tie it back to source control, data lineage, and the runtime environment.
Collect Immutable Metadata at Job Start
The CI/CD or orchestrator that launches training should inject metadata (Git commit SHA, dataset hash from DVC or internal catalog, container image URI) into environment variables. The training script then records them in an experiment tracker.
# File: hardening/auditable_training.py
import json
import mlflow
import os
# These environment variables should be populated by your pipeline
# before the training container starts.
GIT_COMMIT_SHA = os.environ["GIT_COMMIT_SHA"]
DVC_DATA_HASH = os.environ["DVC_DATA_HASH"]
TRAINING_IMAGE_URI = os.environ["TRAINING_IMAGE_URI"]
HYPERPARAMS = json.loads(os.environ["VERIFIED_H007_TRAINING_HYPERPARAMS_JSON"])
if not isinstance(HYPERPARAMS, dict) or not HYPERPARAMS:
raise RuntimeError("verified training hyperparameter profile is empty or invalid")
with mlflow.start_run() as run:
# Record provenance as tags (immutable context)
if GIT_COMMIT_SHA:
mlflow.set_tag("git_commit", GIT_COMMIT_SHA)
if DVC_DATA_HASH:
mlflow.set_tag("data_hash", DVC_DATA_HASH)
if TRAINING_IMAGE_URI:
mlflow.set_tag("docker_image", TRAINING_IMAGE_URI)
# Record hyperparameters
mlflow.log_params(HYPERPARAMS)
# The signed production training entry point executes between these audit events.
# During training, also log metrics like loss/accuracy/gradient_norm
print(f"Completed auditable run. Run ID: {run.info.run_id}")
Action: Make this metadata logging non-optional. Your promotion pipeline should refuse to register or deploy any model artifact that does not have: commit hash, dataset hash, container image reference, and hyperparameters logged. This turns every training run into a forensically usable record.
Verify safely
A separately credentialed verifier independently reads back or replays the complete event population for each training run from admission through artifact publication or termination, exercises positive, negative, boundary and dependency-failure fixtures, and reconciles the complete eligible population.
Open-source tools
- MLflow
- Kubeflow Pipelines
- ClearML Python SDK (Apache-2.0; ClearML Server backend required)
- clearml-agent (Apache-2.0; ClearML Server backend required)
- Prometheus
- Grafana
- PyTorch
- TensorFlow
- TensorBoard
Source-available / open-weight tools
- ClearML Server (SSPL-1.0; source-available)
- Arize Phoenix (Elastic License 2.0; source-available)
- Sentry self-hosted (FSL-1.1-Apache-2.0; source-available)
Commercial / hosted tools
- Amazon SageMaker Experiments
- Vertex AI Experiments
- Databricks Managed MLflow
- Azure Machine Learning
- Weights & Biases
Threat mappings
MITRE ATLAS
- AML.T0020 Training Data Poisoning
- AML.T0018 Manipulate AI Model
- AML.T0018.000 Manipulate AI Model: Poison AI Model
- AML.T0031 Erode AI Model Integrity (monitoring detects integrity erosion during training)
MAESTRO
- Data Poisoning (L2)
- Data Poisoning (Training Phase) (L1)
OWASP LLM Top 10 2026
- LLM05:2026 Data and Model Poisoning
OWASP ML Top 10 2023
- ML02:2023 Data Poisoning Attack
- ML10:2023 Model Poisoning
OWASP Top 10 for Agentic Applications 2026
- N/A
NIST Adversarial Machine Learning 2025
- NISTAML.026 Model Poisoning (Integrity)
- NISTAML.013 Data Poisoning
Cisco Integrated AI Security and Safety Framework
- AISubtech-13.2.1 Service Misuse for Cost Inflation (Halts poisoned training runs that waste GPU compute)
- AITech-13.2 Cost Harvesting / Repurposing (Halts poisoned training runs that waste GPU compute)
- AITech-6.1 Training Data Poisoning
Google Secure AI Framework 2.0 - Risks
- DP: Data Poisoning (runtime monitoring detects poisoning effects during training)
- MST: Model Source Tampering (audit trail detects unauthorized modifications to training process)
Databricks AI Security Framework 3.0
- Algorithms 5.1: Lack of tracking and reproducibility of experiments
- Governance 4.1: Lack of traceability and transparency of model assets
- Governance 4.2: Lack of end-to-end ML lifecycle