AID-M-002.001: Data & Artifact Versioning
Pillars: data, model
Lifecycle: building
Scope boundary
Owns immutable version identity, reproducible lineage, and change history for datasets, model artifacts, code, and pipeline inputs so an exact prior state can be reconstructed or selected for rollback.
AID-M-002.002: AID-M-002.001 establishes which version exists and how it was derived; AID-M-002.002 verifies that bytes match an expected digest and trusted signature. Version history provides lineage but not cryptographic authenticity, while a valid signature does not by itself provide reproducible version lineage.
Implementation guidance
- AID-M-002.001-G001: Use a dedicated data version control system to track large files alongside Git.
- AID-M-002.001-G002: Define the data processing pipeline in a version-controlled manifest to map data flow.
- AID-M-002.001-G003: Link specific data versions to training runs in an MLOps platform.
AID-M-002.001-G001
Concept:
Tools like Data Version Control (DVC) handle large files that are unsuitable for Git. DVC stores small pointer metadata files in Git while the content lives in remote object storage. Treat this as reproducible version selection, not cryptographic trust: security-sensitive admission still requires the signed integrity control in AID-M-002.002.
Add a dataset to DVC tracking
# In your Git repository
dvc add data/training_data.csv
git add data/training_data.csv.dvc .gitignore
git commit -m "feat: track v1 of training data"
dvc pushVerify the exact commit from a clean checkout
A separate read-only job checks out the reviewed Git commit, pulls the object named by its DVC pointer, recomputes DVC's object-addressing hash, and records a SHA-256 for the artifact that downstream signing can bind.
set -euo pipefail
EXPECTED_COMMIT=$(git rev-parse HEAD)
export EXPECTED_COMMIT
VERIFY_DIR=$(mktemp -d)
trap 'rm -rf -- "$VERIFY_DIR"' EXIT
git clone --no-local . "$VERIFY_DIR/replay"
git -C "$VERIFY_DIR/replay" checkout --detach "$EXPECTED_COMMIT"
(
cd "$VERIFY_DIR/replay"
test "$(git rev-parse HEAD)" = "$EXPECTED_COMMIT"
dvc pull data/training_data.csv.dvc
python - <<'PY'
import hashlib
import json
import os
import re
from pathlib import Path
import yaml
pointer_path = Path("data/training_data.csv.dvc")
artifact_path = Path("data/training_data.csv")
pointer = yaml.safe_load(pointer_path.read_text(encoding="utf-8"))
outputs = pointer.get("outs") if isinstance(pointer, dict) else None
if not isinstance(outputs, list) or len(outputs) != 1:
raise SystemExit("DVC pointer must contain exactly one output")
expected_md5 = outputs[0].get("md5")
if not isinstance(expected_md5, str) or not re.fullmatch(r"[0-9a-f]{32}", expected_md5):
raise SystemExit("single-file DVC pointer lacks a valid MD5 object address")
if not artifact_path.is_file():
raise SystemExit("DVC did not materialize the expected artifact")
def file_digest(name: str) -> str:
digest = hashlib.new(name, usedforsecurity=(name != "md5"))
with artifact_path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
actual_md5 = file_digest("md5")
if actual_md5 != expected_md5:
raise SystemExit("materialized artifact differs from the committed DVC pointer")
print(json.dumps({
"git_commit": os.environ["EXPECTED_COMMIT"],
"dvc_object_md5": actual_md5,
"artifact_sha256": file_digest("sha256"),
}, sort_keys=True))
PY
)
Action: Require the clean-checkout replay for tracked datasets and model artifacts. A missing object, pointer mismatch, checkout mismatch, or unreadable artifact blocks use; sign the resulting SHA-256 through AID-M-002.002 before promotion.
AID-M-002.001-G002
Concept:
DVC can capture your data pipeline as code. Each stage (preprocess, train, etc.) declares its inputs and outputs. This becomes auditable lineage: you know exactly which raw data and which script produced which model file.
Step 1: Define Stages in dvc.yaml
# File: dvc.yaml
stages:
preprocess:
cmd: python scripts/preprocess.py data/raw.csv data/processed.csv
deps:
- data/raw.csv
- scripts/preprocess.py
outs:
- data/processed.csv
train:
cmd: python scripts/train_model.py data/processed.csv models/model.pkl
deps:
- data/processed.csv
- scripts/train_model.py
outs:
- models/model.pklStep 2: Visualize the Lineage
Generate a DAG (Directed Acyclic Graph) to show how data flows from raw input to final model.
# View lineage graph based on dvc.yaml
dvc dagAction: Treat dvc.yaml as mandatory documentation. It is machine-readable lineage (for audit) and also human-reviewable (for threat modeling and compliance).
AID-M-002.001-G003
Concept:
When you train a model, you should permanently record exactly which dataset version you trained on. We do this by extracting the dataset checksum from DVC and attaching it as metadata to the MLflow run. Later, if we suspect poisoning, we can trace every affected model back to the exact dataset hash.
Step 1: Get the Dataset Hash from DVC
Each tracked file has a corresponding .dvc entry (or appears in dvc.lock) that includes the content hash. You can parse that value in Python at train time.
# utils/get_dvc_hash.py
import yaml
def get_dvc_hash(dvc_pointer_file):
# Example: data/processed.csv.dvc stores the checksum for that file
with open(dvc_pointer_file, "r") as f:
meta = yaml.safe_load(f)
# DVC stores checksum under 'outs' -> 'md5' (or 'etag'/'checksum' depending on backend)
return meta["outs"][0].get("md5") or meta["outs"][0].get("checksum")
Step 2: Log the Hash in MLflow
# train_and_log.py
import mlflow
from utils.get_dvc_hash import get_dvc_hash
DATA_HASH = get_dvc_hash("data/processed.csv.dvc")
mlflow.set_tracking_uri("http://127.0.0.1:5000")
mlflow.set_experiment("fraud-detection-training")
with mlflow.start_run() as run:
mlflow.set_tag("dataset_hash", DATA_HASH)
mlflow.set_tag("dataset_pointer", "data/processed.csv.dvc")
mlflow.log_artifact("data/processed.csv.dvc", artifact_path="lineage")
mlflow.log_param("dataset_hash", DATA_HASH)
print(f"Logged dataset hash {DATA_HASH} for run {run.info.run_id}")Action: Make dataset hash logging part of the CI/CD training job. Every production-candidate model must have a recorded dataset hash. This gives you provable linkage between model version and data lineage for audit, rollback, and poisoning investigation.
Read back and reproduce the lineage binding
After logging the run, use a read-only MLflow identity to fetch the exact run ID and compare its dataset_hash and archived .dvc pointer with the signed release manifest. In a clean workspace, run dvc pull and dvc checkout, hash the restored bytes, and block promotion if the object is missing or any digest differs.
Open-source tools
- DVC
- Git LFS
- lakeFS
- Pachyderm
- MLflow
Commercial / hosted tools
- Databricks Data Intelligence Platform (Delta Lake time travel)
- Amazon S3 Versioning
- Azure Blob Storage versioning
Threat mappings
MITRE ATLAS
- N/A
MAESTRO
- N/A
OWASP LLM Top 10 2026
- LLM05:2026 Data and Model Poisoning (versioned histories and known-good rollback support recovery and forensics after poisoning rather than blocking malicious data at admission)
OWASP ML Top 10 2023
- N/A
OWASP Top 10 for Agentic Applications 2026
- N/A
NIST Adversarial Machine Learning 2025
- N/A
Cisco Integrated AI Security and Safety Framework
- AITech-6.1 Training Data Poisoning (Rejects dataset bytes that differ from a reviewed reproducible version)
Google Secure AI Framework 2.0 - Risks
- N/A
Databricks AI Security Framework 3.0
- Raw Data 1.5: Lack of data versioning
- Raw Data 1.6: Insufficient data lineage
- Governance 4.1: Lack of traceability and transparency of model assets
- Algorithms 5.1: Lack of tracking and reproducibility of experiments