Claude 5e2094f2ad
feat(media): local inference sidecar for Media Studio
FastAPI service in infra/media-worker implementing every
MediaInferenceProvider operation behind HTTP API v1, plus capabilities.
Bearer auth, MEDIA_ROOT realpath enforcement, strict request schemas,
cancellation on disconnect, GPU/CPU gates with 503 overflow, input
size/duration limits, lazy SHA256-pinned model downloads, fail-closed
redaction with atomic output, offline pytest suite with fake backends,
CPU/CUDA/ROCm Dockerfile and a license review of every model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138YqDj3ttfayRwuMKKrqVj
2026-10-03 18:19:47 +00:00

127 lines
4.1 KiB
Python

from __future__ import annotations
import os
import shutil
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from app.errors import InvalidPath
from app.paths import input_file, output_file, resolve_inside
from .conftest import AUTH
@pytest.fixture
def root(tmp_path: Path) -> Path:
path = tmp_path / "root"
path.mkdir()
(path / "a.txt").write_text("a")
return path
@pytest.mark.parametrize(
"requested",
[
"relative.txt",
"",
"/etc/passwd",
"{root}/../outside.txt",
"{root}/sub/../../outside.txt",
"{root}/a\0.txt",
],
)
def test_rejects_paths_outside_root(root: Path, requested: str) -> None:
with pytest.raises(InvalidPath):
resolve_inside(str(root), requested.format(root=root))
def test_accepts_paths_inside_root(root: Path) -> None:
assert resolve_inside(str(root), f"{root}/a.txt") == os.path.realpath(root / "a.txt")
assert resolve_inside(str(root), f"{root}/new/dir/file.mkv").endswith("/new/dir/file.mkv")
def test_rejects_symlink_escape(root: Path, tmp_path: Path) -> None:
outside = tmp_path / "outside"
outside.mkdir()
(outside / "secret.mp4").write_text("x")
os.symlink(outside, root / "link")
os.symlink(outside / "secret.mp4", root / "file-link.mp4")
with pytest.raises(InvalidPath):
input_file(str(root), f"{root}/link/secret.mp4")
with pytest.raises(InvalidPath):
input_file(str(root), f"{root}/file-link.mp4")
with pytest.raises(InvalidPath):
output_file(str(root), f"{root}/link/new/out.mkv")
assert not (outside / "new").exists()
def test_symlink_inside_root_is_allowed(root: Path) -> None:
os.symlink(root / "a.txt", root / "alias.txt")
assert input_file(str(root), f"{root}/alias.txt") == os.path.realpath(root / "a.txt")
def test_missing_input_and_directory_input(root: Path) -> None:
with pytest.raises(InvalidPath):
input_file(str(root), f"{root}/missing.mp4")
(root / "dir").mkdir()
with pytest.raises(InvalidPath):
input_file(str(root), f"{root}/dir")
def test_output_parent_is_created(root: Path) -> None:
real = output_file(str(root), f"{root}/renders/r1/out.mkv")
assert os.path.isdir(os.path.dirname(real))
def test_http_rejects_traversal_and_symlink_escape(
client: TestClient, media_root: Path, tmp_path: Path
) -> None:
outside = tmp_path / "elsewhere"
outside.mkdir()
shutil.copy(media_root / "testsrc.mp4", outside / "clip.mp4")
link = media_root / "escape-link"
if not link.exists():
os.symlink(outside, link)
for path in ["/etc/hostname", f"{media_root}/../x.mp4", f"{link}/clip.mp4", "testsrc.mp4"]:
response = client.post("/v1/detectScenes", headers=AUTH, json={"path": path})
assert response.status_code == 400, path
assert response.json()["error"] == {
"code": "invalid_path",
"message": response.json()["error"]["message"],
"operation": "detectScenes",
}
response = client.post(
"/v1/redact",
headers=AUTH,
json={
"path": f"{media_root}/testsrc.mp4",
"outPath": f"{link}/out.mkv",
"redactions": [
{
"keyframes": [{"tMs": 0, "box": {"x": 0, "y": 0, "w": 0.1, "h": 0.1}}],
"mode": "blur",
"paddingPct": 0,
"featherPct": 0,
}
],
},
)
assert response.status_code == 400
assert response.json()["error"]["code"] == "invalid_path"
assert not (outside / "out.mkv").exists()
def test_input_size_limit(make_client, media_root: Path) -> None:
client = make_client(max_input_bytes=10)
response = client.post("/v1/audioFeatures", headers=AUTH, json={"path": f"{media_root}/testsrc.mp4"})
assert response.status_code == 413
assert response.json()["error"]["code"] == "too_large"
def test_duration_limit(make_client, media_root: Path) -> None:
client = make_client(max_seconds=1)
response = client.post("/v1/audioFeatures", headers=AUTH, json={"path": f"{media_root}/testsrc.mp4"})
assert response.status_code == 413