File size: 4,137 Bytes
551b309 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | """Fail-closed integrity guard.
A signed file manifest ships inside the built wheel. On import the package
recomputes the hash of every shipped file and compares it to the manifest. Any
edit, overwrite, added file, or removed file trips the guard: the package marks
itself bricked and refuses to run.
Honest limit: this is tamper-evident, not tamper-proof. A user who edits the
guard itself, or deletes the manifest, disables the check, because the code runs
on their machine. No client-side mechanism can prevent that. What this does
guarantee is that any accidental or casual modification of a shipped file stops
the product working rather than running altered code silently.
The manifest is generated at build time (see hatch_build.py) and is absent from
the source tree, so an editable development checkout is not enforced.
"""
# Credit: @paparichens
from __future__ import annotations
import hashlib
import json
from collections.abc import Iterator
from pathlib import Path
MANIFEST_NAME = "_integrity_manifest.json"
MANIFEST_ALGORITHM = "sha256"
_SKIP_SUFFIXES = (".pyc", ".pyo")
_SKIP_DIRS = frozenset({"__pycache__"})
_bricked_reason: str | None = None
class IntegrityError(RuntimeError):
"""Raised when a shipped file has been altered, added, or removed."""
def _iter_files(root: Path) -> Iterator[Path]:
for path in sorted(root.rglob("*")):
if not path.is_file():
continue
if any(part in _SKIP_DIRS for part in path.relative_to(root).parts):
continue
if path.suffix in _SKIP_SUFFIXES:
continue
if path.name == MANIFEST_NAME:
continue
yield path
def _hash_file(path: Path) -> str:
digest = hashlib.new(MANIFEST_ALGORITHM)
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(65536), b""):
digest.update(chunk)
return digest.hexdigest()
def build_manifest(root: Path) -> dict[str, object]:
"""Compute the manifest for a package directory."""
files = {
str(path.relative_to(root).as_posix()): _hash_file(path)
for path in _iter_files(root)
}
return {"version": 1, "algorithm": MANIFEST_ALGORITHM, "files": files}
def verify_tree(root: Path) -> None:
"""
Verify a package directory against its manifest.
No manifest present means an unbuilt or editable tree, which is not
enforced. A present manifest is enforced strictly.
"""
manifest_path = root / MANIFEST_NAME
if not manifest_path.exists():
return
try:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
expected = dict(manifest["files"])
except (OSError, ValueError, KeyError, TypeError) as exc:
raise IntegrityError("Integrity manifest is unreadable or malformed") from exc
actual = {
str(path.relative_to(root).as_posix()): _hash_file(path)
for path in _iter_files(root)
}
added = sorted(set(actual) - set(expected))
removed = sorted(set(expected) - set(actual))
changed = sorted(
name for name in expected.keys() & actual.keys() if expected[name] != actual[name]
)
if added or removed or changed:
parts = []
if changed:
parts.append(f"modified: {', '.join(changed)}")
if removed:
parts.append(f"missing: {', '.join(removed)}")
if added:
parts.append(f"unexpected: {', '.join(added)}")
raise IntegrityError(
"Package integrity check failed (" + "; ".join(parts) + "). "
"A shipped file was altered. Reinstall a clean copy: "
"pip install --force-reinstall affix-huggingface"
)
def enforce() -> None:
"""Run the guard for this installed package and remember the outcome."""
global _bricked_reason
try:
verify_tree(Path(__file__).resolve().parent)
except IntegrityError as exc:
_bricked_reason = str(exc)
raise
def ensure_intact() -> None:
"""Refuse to proceed once the guard has tripped."""
if _bricked_reason is not None:
raise IntegrityError(_bricked_reason)
|