This commit is contained in:
@@ -0,0 +1,410 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Twenty-four host-only guardrails for Phase-0.9E-R2."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from io import BytesIO
|
||||
import hashlib
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
from types import ModuleType
|
||||
from typing import Callable
|
||||
import zipfile
|
||||
|
||||
|
||||
def load_module(name: str, path: Path) -> ModuleType:
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"could not load {path}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
validator = load_module(
|
||||
"phase09er2_validator",
|
||||
root / "tools/validate_phase09er2_correlation.py",
|
||||
)
|
||||
inspector = load_module(
|
||||
"phase09er2_inspector", root / "tools/inspect_siecaf_header.py"
|
||||
)
|
||||
manifest = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9e-r2-inner-correlation.json"
|
||||
)
|
||||
fingerprints = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9e-r2-siecaf-fingerprints.json"
|
||||
)
|
||||
cases: list[tuple[str, Callable[[], None]]] = []
|
||||
|
||||
def case(name: str) -> Callable[[Callable[[], None]], Callable[[], None]]:
|
||||
def register(function: Callable[[], None]) -> Callable[[], None]:
|
||||
cases.append((name, function))
|
||||
return function
|
||||
|
||||
return register
|
||||
|
||||
def build_archive(
|
||||
*,
|
||||
count: int = 1,
|
||||
metadata_ids: list[int] | None = None,
|
||||
offsets: list[int] | None = None,
|
||||
payload_byte: int = 0xA5,
|
||||
) -> bytes:
|
||||
alignment = inspector.ALIGNMENT
|
||||
file_offset = alignment
|
||||
file_size = count * alignment
|
||||
metadata_ids = metadata_ids or list(range(10000, 10000 + count))
|
||||
offsets = offsets or [file_offset + index * alignment for index in range(count)]
|
||||
header = inspector.HEADER.pack(
|
||||
inspector.MAGIC,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
bytes(range(16)),
|
||||
bytes(range(12)),
|
||||
0,
|
||||
count,
|
||||
file_offset,
|
||||
file_size,
|
||||
)
|
||||
metadata = bytearray()
|
||||
hashes = bytearray()
|
||||
for index in range(count):
|
||||
metadata.extend(
|
||||
inspector.SEGMENT_META.pack(
|
||||
metadata_ids[index],
|
||||
0,
|
||||
0,
|
||||
offsets[index],
|
||||
alignment,
|
||||
3,
|
||||
1,
|
||||
b"\x00" * 12,
|
||||
0,
|
||||
123,
|
||||
)
|
||||
)
|
||||
hashes.extend(
|
||||
inspector.SECTION_HASH.pack(
|
||||
index,
|
||||
0,
|
||||
bytes([index + 1]) * 16,
|
||||
b"\x00" * 24,
|
||||
)
|
||||
)
|
||||
tables = header + metadata + hashes
|
||||
return (
|
||||
tables
|
||||
+ b"\x00" * (file_offset - len(tables))
|
||||
+ bytes([payload_byte]) * file_size
|
||||
)
|
||||
|
||||
@case("01 outer mismatch does not exclude an inner match")
|
||||
def _() -> None:
|
||||
require(
|
||||
"a" * 64 != "b" * 64
|
||||
and validator.inner_byte_match(
|
||||
left_size=10,
|
||||
right_size=10,
|
||||
left_sha256="c" * 64,
|
||||
right_sha256="c" * 64,
|
||||
full_byte_equal=True,
|
||||
),
|
||||
"outer mismatch incorrectly excluded an independent inner match",
|
||||
)
|
||||
|
||||
@case("02 outer filename is not inner provenance")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.inner_byte_match(
|
||||
left_size=10,
|
||||
right_size=10,
|
||||
left_sha256="a" * 64,
|
||||
right_sha256="b" * 64,
|
||||
full_byte_equal=False,
|
||||
),
|
||||
"filename-equivalent containers established inner provenance",
|
||||
)
|
||||
|
||||
@case("03 recompression changes outer bytes without changing inner")
|
||||
def _() -> None:
|
||||
payload = b"same-inner-content" * 128
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
first = Path(directory) / "first.zip"
|
||||
second = Path(directory) / "second.zip"
|
||||
with zipfile.ZipFile(first, "w", compression=zipfile.ZIP_STORED) as out:
|
||||
out.writestr("archive.dat", payload)
|
||||
with zipfile.ZipFile(second, "w", compression=zipfile.ZIP_DEFLATED) as out:
|
||||
out.writestr("archive.dat", payload)
|
||||
require(
|
||||
hashlib.sha256(first.read_bytes()).digest()
|
||||
!= hashlib.sha256(second.read_bytes()).digest(),
|
||||
"different ZIP encodings unexpectedly matched",
|
||||
)
|
||||
with zipfile.ZipFile(first) as left, zipfile.ZipFile(second) as right:
|
||||
require(
|
||||
left.read("archive.dat") == right.read("archive.dat"),
|
||||
"recompression changed the inner bytes",
|
||||
)
|
||||
|
||||
@case("04 inner byte match requires size hash and complete bytes")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.inner_byte_match(
|
||||
left_size=1,
|
||||
right_size=1,
|
||||
left_sha256="a" * 64,
|
||||
right_sha256="a" * 64,
|
||||
full_byte_equal=True,
|
||||
)
|
||||
and not validator.inner_byte_match(
|
||||
left_size=1,
|
||||
right_size=1,
|
||||
left_sha256="a" * 64,
|
||||
right_sha256="a" * 64,
|
||||
full_byte_equal=False,
|
||||
),
|
||||
"complete-byte requirement failed",
|
||||
)
|
||||
|
||||
@case("05 size-only match is not a byte match")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.classify_inner_comparison(
|
||||
download_valid=True,
|
||||
inner_present=True,
|
||||
left_size=10,
|
||||
right_size=10,
|
||||
left_sha256="a" * 64,
|
||||
right_sha256="b" * 64,
|
||||
full_byte_equal=False,
|
||||
)
|
||||
== "INNER_ARCHIVE_SIZE_ONLY_MATCH",
|
||||
"size-only result was promoted",
|
||||
)
|
||||
|
||||
@case("06 structural equality is not content equality")
|
||||
def _() -> None:
|
||||
left = build_archive(payload_byte=0x11)
|
||||
right = build_archive(payload_byte=0x22)
|
||||
left_result = inspector.inspect_siecaf(BytesIO(left), len(left))
|
||||
right_result = inspector.inspect_siecaf(BytesIO(right), len(right))
|
||||
require(
|
||||
inspector.compare_structures(left_result, right_result)["classification"]
|
||||
== "SIECAF_STRUCTURAL_EXACT"
|
||||
and hashlib.sha256(left).digest() != hashlib.sha256(right).digest(),
|
||||
"structure was treated as decrypted/content identity",
|
||||
)
|
||||
|
||||
@case("07 malformed segment table is rejected")
|
||||
def _() -> None:
|
||||
malformed = build_archive()[: inspector.HEADER.size + 5]
|
||||
result = inspector.inspect_siecaf(BytesIO(malformed), len(malformed))
|
||||
require(result["classification"] == "SIECAF_MALFORMED", str(result))
|
||||
|
||||
@case("08 integer overflow is rejected")
|
||||
def _() -> None:
|
||||
header = inspector.HEADER.pack(
|
||||
inspector.MAGIC,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
b"\x00" * 16,
|
||||
b"\x00" * 12,
|
||||
0,
|
||||
inspector.UINT64_MAX,
|
||||
inspector.ALIGNMENT,
|
||||
inspector.ALIGNMENT,
|
||||
)
|
||||
result = inspector.inspect_siecaf(BytesIO(header), len(header))
|
||||
require(result["classification"] == "SIECAF_MALFORMED", str(result))
|
||||
|
||||
@case("09 out-of-range offset is rejected")
|
||||
def _() -> None:
|
||||
value = build_archive(offsets=[inspector.ALIGNMENT * 2])
|
||||
result = inspector.inspect_siecaf(BytesIO(value), len(value))
|
||||
require(result["classification"] == "SIECAF_MALFORMED", str(result))
|
||||
|
||||
@case("10 overlapping ranges are reported")
|
||||
def _() -> None:
|
||||
value = build_archive(
|
||||
count=2, offsets=[inspector.ALIGNMENT, inspector.ALIGNMENT]
|
||||
)
|
||||
result = inspector.inspect_siecaf(BytesIO(value), len(value))
|
||||
require(
|
||||
result["classification"] == "SIECAF_MALFORMED" and result["overlaps"],
|
||||
str(result),
|
||||
)
|
||||
|
||||
@case("11 duplicate section identity is reported")
|
||||
def _() -> None:
|
||||
value = build_archive(count=2, metadata_ids=[10000, 10000])
|
||||
result = inspector.inspect_siecaf(BytesIO(value), len(value))
|
||||
require(
|
||||
result["classification"] == "SIECAF_MALFORMED"
|
||||
and result["duplicate_metadata_section_keys"],
|
||||
str(result),
|
||||
)
|
||||
|
||||
@case("12 official inner match would not make MediaFire outer official")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["outer_zip_official"] is False,
|
||||
"outer MediaFire ZIP was promoted to official",
|
||||
)
|
||||
|
||||
@case("13 community source family is not an official community asset")
|
||||
def _() -> None:
|
||||
itsplk = manifest["community_families"][0]
|
||||
require(
|
||||
itsplk["classification"] == "THIRD_PARTY_BUILD_FROM_PUBLIC_SOURCE_POSSIBLE"
|
||||
and itsplk["system_backup_distributed_by_project"] is False,
|
||||
"third-party build possibility became an official release",
|
||||
)
|
||||
|
||||
@case("14 browser history is not fully exported")
|
||||
def _() -> None:
|
||||
browser = manifest["local_download_provenance"]["browser_history"]
|
||||
require(
|
||||
browser["full_history_exported"] is False
|
||||
and browser["matching_records"] == 0,
|
||||
"browser-history boundary changed",
|
||||
)
|
||||
|
||||
@case("15 signed URL material is redacted")
|
||||
def _() -> None:
|
||||
host = manifest["local_download_provenance"]["zone_identifier"]["host_url"]
|
||||
require(
|
||||
"<redacted-signed-segment>" in host["path_redacted"]
|
||||
and len(host["full_value_sha256"]) == 64,
|
||||
"signed path was not safely represented",
|
||||
)
|
||||
|
||||
@case("16 no large backup is tracked")
|
||||
def _() -> None:
|
||||
for relative in validator.git(root, "ls-files").splitlines():
|
||||
path = root / relative
|
||||
require(
|
||||
not path.is_file()
|
||||
or path.stat().st_size <= validator.MAX_TRACKED_FILE_SIZE,
|
||||
f"large tracked file: {relative}",
|
||||
)
|
||||
|
||||
@case("17 no downloaded file was executed")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["actions"]["downloaded_file_executed"] is False,
|
||||
"download execution was recorded",
|
||||
)
|
||||
|
||||
@case("18 ps5-bar-tool was not executed")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["actions"]["ps5_bar_tool_executed"] is False
|
||||
and fingerprints["parser_evidence"]["ps5_bar_tool_executed"] is False,
|
||||
"ps5-bar-tool execution was recorded",
|
||||
)
|
||||
|
||||
@case("19 no PS5 hostname or IP was used")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["actions"]["ps5_connected"] is False
|
||||
and manifest["actions"]["ps5_ip_used"] is False,
|
||||
"PS5 network use was recorded",
|
||||
)
|
||||
|
||||
@case("20 no target source appears")
|
||||
def _() -> None:
|
||||
try:
|
||||
validator.git(root, "cat-file", "-e", f"{validator.BASELINE}^{{commit}}")
|
||||
except RuntimeError:
|
||||
# A parentless public release intentionally has no private history.
|
||||
# The historical delta remains enforced in the canonical repository.
|
||||
return
|
||||
changed = set(
|
||||
filter(
|
||||
None,
|
||||
validator.git(
|
||||
root,
|
||||
"diff",
|
||||
"--name-only",
|
||||
"--diff-filter=ACMR",
|
||||
validator.BASELINE,
|
||||
).splitlines(),
|
||||
)
|
||||
)
|
||||
for relative in changed:
|
||||
normalized = relative.replace("\\", "/")
|
||||
require(
|
||||
not normalized.startswith(validator.FORBIDDEN_PREFIXES)
|
||||
and Path(normalized).suffix.lower() not in validator.FORBIDDEN_SUFFIXES,
|
||||
f"target/binary material appeared: {normalized}",
|
||||
)
|
||||
|
||||
@case("21 every authorization remains false")
|
||||
def _() -> None:
|
||||
for value in (manifest, fingerprints):
|
||||
for field in validator.AUTHORIZATION_FIELDS:
|
||||
require(
|
||||
value["authorization"][field] is False,
|
||||
f"authorization changed: {field}",
|
||||
)
|
||||
|
||||
@case("22 automatic retry remains false")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["authorization"]["automatic_retry"] is False
|
||||
and manifest["actions"]["automatic_retry_used"] is False,
|
||||
"automatic retry was enabled or used",
|
||||
)
|
||||
|
||||
@case("23 outer metadata alone cannot open Phase 0.9F")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.phase09f_reconsideration_allowed(
|
||||
inner_source_bound=False,
|
||||
mediafire_maker_source_bound=False,
|
||||
auditable_bootstrap_closure=True,
|
||||
),
|
||||
"outer metadata opened Phase 0.9F",
|
||||
)
|
||||
|
||||
@case("24 runtime deployment remains unproven")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["runtime_deployment_verified"] is False
|
||||
and manifest["current_device_contents_verified"] is False
|
||||
and manifest["runtime_firmware_9_60"] == "UNPROVEN",
|
||||
"host-only evidence became runtime proof",
|
||||
)
|
||||
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
except Exception as error:
|
||||
raise RuntimeError(f"{name}: {error}") from error
|
||||
print(f"Phase-0.9E-R2 guardrails: {len(cases)}/{len(cases)} PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user