consume only artifacts the runtime produced
tile_manifest_path arrives in the detection and segmentation request and was read straight off disk, and a manifest entry may name an absolute tile path. That makes an API field an unbounded reference to the host filesystem, and it contradicts the rule the persistence model rests on: only a governed, runtime-produced artifact may be consumed, and a file outside the storage root is not one. Both the manifest and every tile it names now resolve under STORAGE_ROOT. Resolution happens before the comparison, so ".." cannot climb out and a sibling that merely shares a name prefix does not pass. GEOINTEL_ALLOW_EXTERNAL_ARTIFACT_PATHS opts out for provisioning workflows that stage tiles before ingest. The check honours the Settings the caller is operating under rather than the process-wide ones, because every analysis path already threads its own. The affected tests write manifests into tmp_path, so they now declare tmp_path as the storage root — which is what a deployment does, and makes the fixtures more honest than they were. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from hashlib import sha256
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -138,7 +139,12 @@ def test_yolo_preflight_validates_model_and_manifest_without_importing_yolo(tmp_
|
||||
model_path = tmp_path / "model.pt"
|
||||
model_path.write_bytes(b"weights")
|
||||
manifest_path = _manifest(tmp_path, tile_count=2)
|
||||
settings = Settings(yolo_enabled=True, yolo_model_path=str(model_path), yolo_max_tiles=4)
|
||||
settings = Settings(
|
||||
storage_root=str(tmp_path),
|
||||
yolo_enabled=True,
|
||||
yolo_model_path=str(model_path),
|
||||
yolo_max_tiles=4,
|
||||
)
|
||||
_write_model_sidecar(model_path, settings)
|
||||
|
||||
result = YoloPreflightService.run(
|
||||
@@ -162,7 +168,12 @@ def test_yolo_preflight_marks_assumed_dependencies_in_runtime_details(tmp_path:
|
||||
model_path = tmp_path / "model.pt"
|
||||
model_path.write_bytes(b"weights")
|
||||
manifest_path = _manifest(tmp_path, tile_count=1)
|
||||
settings = Settings(yolo_enabled=True, yolo_model_path=str(model_path), yolo_max_tiles=4)
|
||||
settings = Settings(
|
||||
storage_root=str(tmp_path),
|
||||
yolo_enabled=True,
|
||||
yolo_model_path=str(model_path),
|
||||
yolo_max_tiles=4,
|
||||
)
|
||||
_write_model_sidecar(model_path, settings)
|
||||
|
||||
result = YoloPreflightService.run(
|
||||
@@ -182,7 +193,12 @@ def test_yolo_preflight_can_explicitly_smoke_load_local_model(tmp_path: Path) ->
|
||||
model_path = tmp_path / "model.pt"
|
||||
model_path.write_bytes(b"weights")
|
||||
manifest_path = _manifest(tmp_path)
|
||||
settings = Settings(yolo_enabled=True, yolo_model_path=str(model_path), yolo_max_tiles=4)
|
||||
settings = Settings(
|
||||
storage_root=str(tmp_path),
|
||||
yolo_enabled=True,
|
||||
yolo_model_path=str(model_path),
|
||||
yolo_max_tiles=4,
|
||||
)
|
||||
_write_model_sidecar(model_path, settings)
|
||||
|
||||
result = YoloPreflightService.run(
|
||||
@@ -202,7 +218,12 @@ def test_yolo_preflight_can_explicitly_smoke_load_local_model(tmp_path: Path) ->
|
||||
def test_yolo_preflight_reports_explicit_model_load_failure(tmp_path: Path) -> None:
|
||||
model_path = tmp_path / "model.pt"
|
||||
model_path.write_bytes(b"weights")
|
||||
settings = Settings(yolo_enabled=True, yolo_model_path=str(model_path), yolo_max_tiles=4)
|
||||
settings = Settings(
|
||||
storage_root=str(tmp_path),
|
||||
yolo_enabled=True,
|
||||
yolo_model_path=str(model_path),
|
||||
yolo_max_tiles=4,
|
||||
)
|
||||
_write_model_sidecar(model_path, settings)
|
||||
|
||||
result = YoloPreflightService.run(
|
||||
@@ -238,6 +259,9 @@ def test_yolo_preflight_script_outputs_json(tmp_path: Path) -> None:
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
# The script reads process settings; the manifest it is asked to
|
||||
# validate lives here, so this is the storage root for that run.
|
||||
env={**os.environ, "STORAGE_ROOT": str(tmp_path)},
|
||||
)
|
||||
payload = json.loads(result.stdout)
|
||||
|
||||
@@ -250,7 +274,13 @@ def test_yolo_preflight_script_uses_environment_configuration(tmp_path: Path, mo
|
||||
model_path = tmp_path / "model.pt"
|
||||
model_path.write_bytes(b"weights")
|
||||
manifest_path = _manifest(tmp_path)
|
||||
_write_model_sidecar(model_path, Settings(yolo_enabled=True, yolo_model_path=str(model_path), yolo_max_tiles=4))
|
||||
_write_model_sidecar(model_path, Settings(
|
||||
storage_root=str(tmp_path),
|
||||
yolo_enabled=True,
|
||||
yolo_model_path=str(model_path),
|
||||
yolo_max_tiles=4,
|
||||
))
|
||||
monkeypatch.setenv("STORAGE_ROOT", str(tmp_path))
|
||||
monkeypatch.setenv("YOLO_ENABLED", "true")
|
||||
monkeypatch.setenv("YOLO_MODEL_PATH", str(model_path))
|
||||
monkeypatch.setenv("YOLO_MAX_TILES", "4")
|
||||
@@ -314,6 +344,7 @@ def test_yolo_preflight_script_rejects_assumed_dependencies_for_model_load(tmp_p
|
||||
|
||||
|
||||
def test_yolo_preflight_api_returns_canonical_envelope(monkeypatch, tmp_path: Path) -> None:
|
||||
monkeypatch.setenv("STORAGE_ROOT", str(tmp_path))
|
||||
monkeypatch.setenv("YOLO_ENABLED", "false")
|
||||
monkeypatch.setenv("YOLO_MODEL_PATH", str(tmp_path / "missing.pt"))
|
||||
monkeypatch.setenv("YOLO_CONFIG_DIR", str(tmp_path / "ultralytics"))
|
||||
|
||||
Reference in New Issue
Block a user