Add workload SDK foundation
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
"""Independent installed-distribution content measurement for SDK allowlists."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import importlib.util
|
||||
from importlib import metadata
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def installed_distribution_digest(
|
||||
distribution: metadata.Distribution | str,
|
||||
*,
|
||||
allow_editable: bool = False,
|
||||
) -> str:
|
||||
"""Hash installed package payload files using a stable path/length framing.
|
||||
|
||||
Distribution metadata is deliberately excluded: editable/non-editable
|
||||
installers generate different RECORD and entry-point files for identical
|
||||
package code. All source/native modules and package data below declared
|
||||
top-level packages are included. Interpreter-generated ``__pycache__``
|
||||
files are excluded because they are neither stable wheel payloads nor used
|
||||
by the registry's cache-isolated discovery import.
|
||||
"""
|
||||
installed = metadata.distribution(distribution) if isinstance(distribution, str) else distribution
|
||||
raw_top_level = installed.read_text("top_level.txt")
|
||||
if raw_top_level is None:
|
||||
raise ValueError("installed distribution does not declare top-level packages")
|
||||
declared_top_levels = [line.strip() for line in raw_top_level.splitlines() if line.strip()]
|
||||
if any(not value.isidentifier() for value in declared_top_levels):
|
||||
raise ValueError("installed distribution declares an invalid top-level package")
|
||||
top_levels = set(declared_top_levels)
|
||||
if not top_levels:
|
||||
raise ValueError("installed distribution has no measurable top-level package")
|
||||
declared_files = tuple(installed.files or ())
|
||||
editable_bootstrap = any(
|
||||
Path(str(item)).name.startswith("__editable__") and Path(str(item)).suffix == ".pth"
|
||||
for item in declared_files
|
||||
)
|
||||
if editable_bootstrap and not allow_editable:
|
||||
raise ValueError("editable workload installations are not accepted for secure discovery")
|
||||
for item in declared_files:
|
||||
relative = Path(str(item))
|
||||
suffix = relative.suffix.lower()
|
||||
if suffix == ".pth" and not allow_editable:
|
||||
raise ValueError("installed workload distribution declares a .pth bootstrap")
|
||||
if suffix in {".pyc", ".pyo"} and "__pycache__" not in relative.parts:
|
||||
raise ValueError("installed workload distribution declares sourceless bytecode")
|
||||
|
||||
selected: list[tuple[str, Path]] = []
|
||||
for top_level in sorted(top_levels):
|
||||
root = Path(installed.locate_file(top_level))
|
||||
if not root.exists():
|
||||
# PEP 660 editable distributions may expose source packages through
|
||||
# a meta-path finder rather than a physical site-packages path.
|
||||
spec = importlib.util.find_spec(top_level)
|
||||
locations = tuple(spec.submodule_search_locations or ()) if spec is not None else ()
|
||||
if len(locations) > 1:
|
||||
raise ValueError("shared namespace packages are not supported for workload integrity")
|
||||
if locations:
|
||||
root = Path(locations[0])
|
||||
if root.is_symlink():
|
||||
raise ValueError("installed workload package root must not be a symbolic link")
|
||||
if root.is_dir():
|
||||
candidates = root.rglob("*")
|
||||
for path in candidates:
|
||||
if path.is_symlink():
|
||||
raise ValueError("installed workload package contains a symbolic-link payload")
|
||||
if not path.is_file():
|
||||
continue
|
||||
relative_parts = path.relative_to(root).parts
|
||||
if "__pycache__" in relative_parts:
|
||||
continue
|
||||
if path.suffix.lower() in {".pyc", ".pyo"}:
|
||||
raise ValueError("installed workload package contains sourceless bytecode")
|
||||
relative = f"{top_level}/{path.relative_to(root).as_posix()}"
|
||||
selected.append((relative, path))
|
||||
continue
|
||||
module = Path(installed.locate_file(top_level + ".py"))
|
||||
if not module.exists():
|
||||
spec = importlib.util.find_spec(top_level)
|
||||
if spec is not None and spec.origin is not None:
|
||||
module = Path(spec.origin)
|
||||
if module.is_symlink() or not module.is_file():
|
||||
raise ValueError("installed workload package contains a missing top-level payload")
|
||||
selected.append((top_level + ".py", module))
|
||||
# Include declared package data outside top-level import trees. Generated
|
||||
# console wrappers and installer metadata are excluded; executable .pth and
|
||||
# sourceless bytecode payloads were rejected above. Generated pycache
|
||||
# entries are deliberately ignored and discovery imports from an empty
|
||||
# cache prefix.
|
||||
selected_names = {relative for relative, _ in selected}
|
||||
metadata_root_names = {
|
||||
Path(str(item)).parts[0]
|
||||
for item in declared_files
|
||||
if Path(str(item)).parts
|
||||
and Path(str(item)).parts[0].endswith((".dist-info", ".egg-info"))
|
||||
}
|
||||
for item in declared_files:
|
||||
relative = Path(str(item))
|
||||
text = relative.as_posix()
|
||||
if (
|
||||
not relative.parts
|
||||
or relative.parts[0] in metadata_root_names
|
||||
or text.startswith("../../../bin/")
|
||||
or "__pycache__" in relative.parts
|
||||
):
|
||||
continue
|
||||
path = Path(installed.locate_file(item))
|
||||
if path.is_symlink():
|
||||
raise ValueError("installed workload distribution contains a symbolic-link payload")
|
||||
if not path.is_file() or text in selected_names:
|
||||
continue
|
||||
selected.append((text, path))
|
||||
selected_names.add(text)
|
||||
|
||||
entry_point_payloads = [
|
||||
(
|
||||
f".entry-points/{entry_point.group}/{entry_point.name}",
|
||||
entry_point.value.encode("utf-8"),
|
||||
)
|
||||
for entry_point in installed.entry_points
|
||||
]
|
||||
if not selected:
|
||||
raise ValueError("installed distribution has no measurable package payload")
|
||||
digest = hashlib.sha256()
|
||||
for relative, path in sorted(selected):
|
||||
name = relative.encode("utf-8")
|
||||
payload = path.read_bytes()
|
||||
digest.update(len(name).to_bytes(4, "big"))
|
||||
digest.update(name)
|
||||
digest.update(len(payload).to_bytes(8, "big"))
|
||||
digest.update(payload)
|
||||
for relative, payload in sorted(entry_point_payloads):
|
||||
name = relative.encode("utf-8")
|
||||
digest.update(len(name).to_bytes(4, "big"))
|
||||
digest.update(name)
|
||||
digest.update(len(payload).to_bytes(8, "big"))
|
||||
digest.update(payload)
|
||||
return "sha256:" + digest.hexdigest()
|
||||
Reference in New Issue
Block a user