Protect manifest artifacts in RC10 audit
This commit is contained in:
@@ -165,6 +165,33 @@ def normalize_storage_reference(value: str | None, storage_root: Path) -> Path |
|
||||
return resolved
|
||||
|
||||
|
||||
def expand_manifest_references(references: set[Path], storage_root: Path) -> set[Path]:
|
||||
"""Protect files named by referenced, bounded JSON manifests."""
|
||||
root = storage_root.resolve()
|
||||
expanded = set(references)
|
||||
for manifest in list(references):
|
||||
if manifest.suffix.lower() != ".json" or not manifest.is_file():
|
||||
continue
|
||||
try:
|
||||
if manifest.stat().st_size > 16 * 1024 * 1024:
|
||||
continue
|
||||
payload = json.loads(manifest.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeDecodeError, json.JSONDecodeError):
|
||||
continue
|
||||
for value in _iter_strings(payload):
|
||||
normalized = normalize_storage_reference(value, root)
|
||||
if normalized is None and "://" not in value:
|
||||
try:
|
||||
relative = (manifest.parent / value).resolve()
|
||||
relative.relative_to(root)
|
||||
except (OSError, ValueError):
|
||||
continue
|
||||
normalized = relative
|
||||
if normalized is not None:
|
||||
expanded.add(normalized)
|
||||
return expanded
|
||||
|
||||
|
||||
def _add_row_references(target: set[Path], row: Any, storage_root: Path, direct_fields: Iterable[str]) -> None:
|
||||
for field in direct_fields:
|
||||
normalized = normalize_storage_reference(getattr(row, field, None), storage_root)
|
||||
@@ -327,9 +354,11 @@ def collect_database_state(db: Any, storage_root: Path) -> dict[str, Any]:
|
||||
def disk_pressure(storage_root: Path) -> dict[str, Any]:
|
||||
usage = shutil.disk_usage(storage_root)
|
||||
free_percent = (usage.free / usage.total * 100) if usage.total else 0.0
|
||||
if usage.free < 10 * 1024**3 or free_percent < 5:
|
||||
# Absolute headroom governs very large Unraid arrays: a low percentage of
|
||||
# tens of terabytes can still leave hundreds of GiB safely available.
|
||||
if usage.free < 10 * 1024**3:
|
||||
status = "critical"
|
||||
elif usage.free < 25 * 1024**3 or free_percent < 10:
|
||||
elif usage.free < 50 * 1024**3 or (free_percent < 2 and usage.free < 250 * 1024**3):
|
||||
status = "warning"
|
||||
else:
|
||||
status = "ok"
|
||||
@@ -357,7 +386,7 @@ def build_report(
|
||||
raise RuntimeError(f"Storage root is not a directory: {root}")
|
||||
records, skipped_symlinks = inventory_storage(root)
|
||||
database = collect_database_state(db, root)
|
||||
references: set[Path] = database.pop("references")
|
||||
references: set[Path] = expand_manifest_references(database.pop("references"), root)
|
||||
cutoff = utc_now() - timedelta(days=minimum_age_days)
|
||||
|
||||
categories: dict[str, dict[str, Any]] = defaultdict(
|
||||
@@ -376,12 +405,14 @@ def build_report(
|
||||
for path in references
|
||||
if path not in existing_paths and not path.is_dir()
|
||||
)
|
||||
referenced_directories = tuple(path for path in references if path.is_dir())
|
||||
candidates = sorted(
|
||||
(
|
||||
record
|
||||
for record in records
|
||||
if record.cleanup_eligible
|
||||
and record.path not in references
|
||||
and not any(record.path.is_relative_to(reference) for reference in referenced_directories)
|
||||
and record.modified_at <= cutoff
|
||||
and record.path.name not in IGNORED_FILENAMES
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user