Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docker-compose.mlflow-security.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
services:
mlflow:
image: ${MLFLOW_IMAGE:-ghcr.io/mlflow/mlflow:v3.14.0}
image: ${MLFLOW_IMAGE:-ghcr.io/mlflow/mlflow:v3.15.1}
environment:
MLFLOW_SERVER_ENABLE_JOB_EXECUTION: "false"
MLFLOW_SERVER_ALLOWED_HOSTS: "mlflow:5000,hurc_mlflow:5000,localhost:*,127.0.0.1:*"
PYTHONDONTWRITEBYTECODE: "1"
HOME: /tmp/mlflow-home
XDG_CACHE_HOME: /tmp/.cache
Expand Down
12 changes: 9 additions & 3 deletions docs/technical/MLFLOW_SECURITY_BASELINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ The Python client and server image must use the same MLflow version and must not

The reviewed baseline in this repository is:

- Python client: `mlflow==3.14.0`;
- Server image: `ghcr.io/mlflow/mlflow:v3.14.0`.
- Python client: `mlflow==3.15.1`;
- Server image: `ghcr.io/mlflow/mlflow:v3.15.1`.

This version is newer than the fixes referenced for archive path traversal, model-serving command injection and FastAPI authentication bypass issues.

Expand All @@ -25,7 +25,13 @@ The MLflow host port must remain bound to loopback:
127.0.0.1:${MLFLOW_HOST_PORT:-5000}:5000
```

MLflow must not be published on `0.0.0.0` at the host boundary. Containers that require tracking access communicate through the private `backend-net` network.
The MLflow Host allowlist must remain restricted to the Compose service name, the fixed container name and loopback health-check hosts:

```yaml
MLFLOW_SERVER_ALLOWED_HOSTS: "mlflow:5000,hurc_mlflow:5000,localhost:*,127.0.0.1:*"
```

Do not replace this allowlist with `*`. MLflow must not be published on `0.0.0.0` at the host boundary. Containers that require tracking access communicate through the private `backend-net` network.

For remote access, place MLflow behind an approved reverse proxy or identity-aware gateway. Do not expose the container port directly to an external network.

Expand Down
8 changes: 7 additions & 1 deletion infra/vision-trainer/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import os
import re
import threading
import uuid
from datetime import datetime, timezone
Expand Down Expand Up @@ -49,6 +50,11 @@ def now_iso():
return datetime.now(timezone.utc).isoformat()


def normalize_mlflow_metric_name(value: str):
normalized = re.sub(r"[^A-Za-z0-9_. /:-]", "_", value)
return normalized or "metric"


def default_state():
return {"datasets": {}, "samples": {}, "jobs": {}}

Expand Down Expand Up @@ -177,7 +183,7 @@ def run_training(job_id: str):
)
best_path = Path(result.save_dir) / "weights" / "best.pt"
mlflow.log_artifact(str(best_path), artifact_path="model")
metrics = {key: float(value) for key, value in getattr(result, "results_dict", {}).items() if isinstance(value, (int, float))}
metrics = {normalize_mlflow_metric_name(key): float(value) for key, value in getattr(result, "results_dict", {}).items() if isinstance(value, (int, float))}
if metrics:
mlflow.log_metrics(metrics)
with LOCK:
Expand Down
12 changes: 6 additions & 6 deletions infra/vision-trainer/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
fastapi==0.116.1
uvicorn[standard]==0.35.0
python-multipart==0.0.31
fastapi==0.141.1
uvicorn[standard]==0.52.3
python-multipart==0.0.32
Pillow==12.3.0
PyYAML==6.0.2
ultralytics==8.3.203
mlflow==3.14.0
PyYAML==6.0.3
ultralytics==8.4.121
mlflow==3.15.1
1 change: 1 addition & 0 deletions scripts/check-mlflow-security.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ if (packageVersion && imageVersion && compareVersion(packageVersion, imageVersio

requireText(platformRunner, "'docker-compose.mlflow-security.yml'", 'Platform compose must load the MLflow security override.');
requireText(securityOverride, 'MLFLOW_SERVER_ENABLE_JOB_EXECUTION: "false"', 'MLflow job execution must be explicitly disabled.');
requireText(securityOverride, 'MLFLOW_SERVER_ALLOWED_HOSTS: "mlflow:5000,hurc_mlflow:5000,localhost:*,127.0.0.1:*"', 'MLflow must allow only the expected internal and loopback Host headers.');
requireText(securityOverride, 'read_only: true', 'MLflow container root filesystem must be read-only.');
requireText(securityOverride, 'no-new-privileges:true', 'MLflow container must enable no-new-privileges.');
requireText(securityOverride, 'cap_drop:', 'MLflow container must drop Linux capabilities.');
Expand Down
Loading