AID-M-002.001: Data & Artifact Versioning

Version 1.20261015 · Actionable sub-technique

Implements systems and processes to version control datasets and model artifacts, treating them with the same rigor as source code. By tracking every version of a data file and linking it to specific code commits, this technique ensures perfect reproducibility, provides an auditable history of changes, and enables rapid rollbacks to a known-good state, which is critical for recovering from data corruption or poisoning incidents.

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.
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 push
Verify 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

Define the data processing pipeline in a version-controlled manifest to map data flow.
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.pkl
Step 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 dag

Action: 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

Link specific data versions to training runs in an MLOps platform.
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

Commercial / hosted tools

Threat mappings

MITRE ATLAS

MAESTRO

OWASP LLM Top 10 2026

OWASP ML Top 10 2023

OWASP Top 10 for Agentic Applications 2026

NIST Adversarial Machine Learning 2025

Cisco Integrated AI Security and Safety Framework

Google Secure AI Framework 2.0 - Risks

Databricks AI Security Framework 3.0

AID-M-002.001: Data & Artifact Versioning | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version