Skip to content
24 changes: 24 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,29 @@ detect configuration drift; they are not cryptographic proof against arbitrary
in-process code execution. The evidence artifact does not authorize a request
or replace application path, credential, tenant, or destination authorization.

### Release-evidence preparation layer

Release-evidence preparation is repository tooling, not part of the runtime
network path. It runs without repository-write, signing, OIDC-attestation,
publication, tag, or release credentials and accepts only already-built wheel
and source-distribution archives plus two reviewed dependency inputs.

The reviewed dependency manifest and hash-locked runtime requirements are each
bounded to 1 MiB and bound to an accepted `(device, inode, size)` identity. The
preparer opens each path with no-follow semantics and copies the descriptor bytes
into separate owner-only private snapshots with fixed internal names before the
SBOM generator is loaded. Both wheel and source-distribution SBOM passes receive
the same detached snapshots; the generator never reopens caller-controlled
reviewed-input paths. Distribution archives are independently copied through the
same identity-bound pattern into private parser snapshots. All private snapshots
are removed before generated evidence is published.

This layer establishes a deterministic, internally consistent handoff for one
exact repository/source identity. It does not prove that the distributions were
honestly built from that source and does not itself create provenance; those
claims remain the responsibility of independently reviewed, credential-separated
hosted build and attestation controls.

## Trust boundaries

| Boundary | Trusted input | Untrusted input | Required behavior |
Expand All @@ -180,6 +203,7 @@ or replace application path, credential, tenant, or destination authorization.
| TLS | fresh context and validated hostname | peer certificate and caller SNI override | bind identity; deny mismatch |
| Response delivery | finite response policy | peer fields, framing, coding, and body | bound and validate before exposure |
| Audit export | revalidated decision | paths, credentials, IPs, response data | omit sensitive request and peer data |
| Release evidence | exact accepted file identities | mutable reviewed-input and archive paths | consume bounded no-follow private snapshots; fail closed on drift |

Arbitrary code execution inside the embedding Python process is outside the
security model. Network firewalls, service-mesh policies, sandboxing, tenant
Expand Down
26 changes: 20 additions & 6 deletions docs/release-evidence-preparation.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ duplicate distribution kind, or wheel/source version mismatch fails before an
evidence output is created. The reviewed dependency manifest and hash-locked
runtime requirements must also be existing canonical regular files.

Each reviewed dependency input is independently limited to 1 MiB and bound to
its accepted device, inode, and size identity before the deterministic generator
is loaded. The preparer opens each pathname with no-follow semantics, copies the
bounded descriptor bytes into a fresh owner-only private snapshot under a
distinct fixed internal name, and rechecks descriptor and pathname identity
before and after the copy. Both wheel and source-distribution SBOM builds receive
the same detached dependency-manifest and runtime-lock snapshots; the generator
never reopens the caller-controlled reviewed-input paths. A pathname replacement
after acceptance therefore cannot change the dependency bytes delegated to the
generator or make the two SBOM passes observe different reviewed inputs. Every
reviewed-input rejection remains generic, and the private snapshots are removed
before evidence publication.

Each selected wheel and source distribution is preflighted as a current regular
file with a finite compressed-byte bound before the deterministic generator is
loaded or any ZIP or tar archive parser runs. The preparer records that exact
Expand Down Expand Up @@ -80,12 +93,13 @@ attestation credentials.
## Generated contract

The preparer computes both deterministic CycloneDX 1.7 JSON documents from the
private identity-bound parser snapshots, constructs canonical strict-JSON source
identity, computes sorted lowercase SHA-256 entries over the original accepted
distributions and generated payloads, and then exclusively creates owner-only
generated files. The private parser snapshots are deleted with their temporary
directory before any generated evidence is published. After successful
preparation, the evidence directory contains exactly:
private identity-bound distribution snapshots and the detached reviewed-input
snapshots, constructs canonical strict-JSON source identity, computes sorted
lowercase SHA-256 entries over the original accepted distributions and generated
payloads, and then exclusively creates owner-only generated files. All private
snapshots are deleted with their temporary directory before any generated
evidence is published. After successful preparation, the evidence directory
contains exactly:

```text
egressweave-X.Y.Z-py3-none-any.whl
Expand Down
124 changes: 101 additions & 23 deletions scripts/ci/prepare_release_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
"generate_attestable_release_sbom.py"
)
MAX_DISTRIBUTION_BYTES = release_evidence.MAX_ARTIFACT_BYTES
MAX_REVIEWED_INPUT_BYTES = 1_048_576
COPY_BLOCK_BYTES = 1_048_576
REVIEWED_INPUT_REJECTION = "reviewed input is unreadable or unsafe"
DistributionIdentity = tuple[int, int, int]

__all__ = ["main", "prepare_release_evidence"]
Expand Down Expand Up @@ -135,11 +137,12 @@ def _require_distribution_metadata(
metadata: os.stat_result,
*,
label: str,
max_bytes: int = MAX_DISTRIBUTION_BYTES,
) -> DistributionIdentity:
"""Return one regular finite distribution identity or fail through stable errors."""
"""Return one regular finite input identity or fail through stable errors."""
if not stat.S_ISREG(metadata.st_mode):
raise SystemExit(f"{label} is unreadable or unsafe")
if metadata.st_size > MAX_DISTRIBUTION_BYTES:
if metadata.st_size > max_bytes:
raise SystemExit(f"{label} exceeds the safety bound")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return _distribution_identity(metadata)

Expand All @@ -157,17 +160,37 @@ def _require_distribution_preflight(
return _require_distribution_metadata(path_state, label=label)


def _require_reviewed_input_preflight(
path: Path,
*,
label: str,
) -> DistributionIdentity:
"""Bind one reviewed input while hiding which safety rule rejected it."""
del label
try:
path_state = path.lstat()
return _require_distribution_metadata(
path_state,
label="reviewed input",
max_bytes=MAX_REVIEWED_INPUT_BYTES,
)
except (OSError, SystemExit):
raise SystemExit(REVIEWED_INPUT_REJECTION) from None


def _snapshot_distribution(
path: Path,
snapshot_root: Path,
accepted_identity: DistributionIdentity,
*,
label: str,
max_bytes: int = MAX_DISTRIBUTION_BYTES,
snapshot_name: str | None = None,
) -> Path:
"""Copy one accepted descriptor into a private parser-only immutable snapshot.

The accepted path identity is checked against both the no-follow descriptor
and the current pathname before and after the bounded copy. Archive parsers
and the current pathname before and after the bounded copy. Downstream parsers
receive only the private snapshot, never the mutable caller-controlled path.
"""
read_flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_NOFOLLOW", 0)
Expand All @@ -180,14 +203,19 @@ def _snapshot_distribution(
)
source_descriptor: int | None = None
snapshot_descriptor: int | None = None
snapshot_path = snapshot_root / path.name
snapshot_path = snapshot_root / (snapshot_name or path.name)
try:
source_descriptor = os.open(path, read_flags)
opened_identity = _require_distribution_metadata(
os.fstat(source_descriptor),
label=label,
max_bytes=max_bytes,
)
current_identity = _require_distribution_metadata(
path.lstat(),
label=label,
max_bytes=max_bytes,
)
current_identity = _require_distribution_metadata(path.lstat(), label=label)
if opened_identity != accepted_identity or current_identity != accepted_identity:
raise SystemExit(f"{label} is unreadable or unsafe")

Expand All @@ -199,7 +227,7 @@ def _snapshot_distribution(
if not block:
break
copied_bytes += len(block)
if copied_bytes > MAX_DISTRIBUTION_BYTES:
if copied_bytes > max_bytes:
raise SystemExit(f"{label} exceeds the safety bound")
remaining = memoryview(block)
while remaining:
Expand All @@ -212,8 +240,13 @@ def _snapshot_distribution(
final_opened_identity = _require_distribution_metadata(
os.fstat(source_descriptor),
label=label,
max_bytes=max_bytes,
)
final_path_identity = _require_distribution_metadata(
path.lstat(),
label=label,
max_bytes=max_bytes,
)
final_path_identity = _require_distribution_metadata(path.lstat(), label=label)
snapshot_state = os.fstat(snapshot_descriptor)
if (
final_opened_identity != accepted_identity
Expand All @@ -235,6 +268,27 @@ def _snapshot_distribution(
os.close(source_descriptor)


def _snapshot_reviewed_input(
path: Path,
snapshot_root: Path,
accepted_identity: DistributionIdentity,
*,
snapshot_name: str,
) -> Path:
"""Copy one reviewed input while normalizing every rejection to one message."""
try:
return _snapshot_distribution(
path,
snapshot_root,
accepted_identity,
label="reviewed input",
max_bytes=MAX_REVIEWED_INPUT_BYTES,
snapshot_name=snapshot_name,
)
except SystemExit:
raise SystemExit(REVIEWED_INPUT_REJECTION) from None


def _load_attestable_generator() -> ModuleType:
"""Load the repository-only deterministic generator without importing archives."""
specification = importlib.util.spec_from_file_location(
Expand Down Expand Up @@ -369,25 +423,37 @@ def prepare_release_evidence(
"""Create and independently verify one credential-free release handoff.

The input directory must initially contain only one canonical wheel and one
matching source distribution. Each accepted archive is copied from its
no-follow identity-bound descriptor into a private parser-only snapshot
before the generator loads. Every generated file is new, owner-only, and
deterministic. The returned mapping is the exact manifest already rebuilt
and verified after the separately stored handoff has been durably published.
matching source distribution. Each accepted archive and reviewed dependency
input is copied from its no-follow identity-bound descriptor into one private
parser-only snapshot before the generator loads. Every generated file is new,
owner-only, and deterministic. The returned mapping is the exact manifest
already rebuilt and verified after the separately stored handoff is published.
"""
_require_source_identity(repository, source_sha)
evidence_root = _require_canonical_directory(
evidence_dir,
label="release evidence input directory",
)
resolved_handoff = _require_handoff_outside_evidence(handoff_path, evidence_root)
dependency_manifest = _require_canonical_file(
dependency_manifest_path,
label="reviewed runtime dependency manifest",
reviewed_input_label = "reviewed input"
try:
dependency_manifest = _require_canonical_file(
dependency_manifest_path,
label=reviewed_input_label,
)
runtime_lock = _require_canonical_file(
runtime_lock_path,
label=reviewed_input_label,
)
except SystemExit:
raise SystemExit(REVIEWED_INPUT_REJECTION) from None
dependency_manifest_identity = _require_reviewed_input_preflight(
dependency_manifest,
label=reviewed_input_label,
)
runtime_lock = _require_canonical_file(
runtime_lock_path,
label="hash-locked runtime requirements",
runtime_lock_identity = _require_reviewed_input_preflight(
runtime_lock,
label=reviewed_input_label,
)
wheel_path, sdist_path = _select_distributions(evidence_root)
wheel_label = f"release distribution {wheel_path.name}"
Expand All @@ -409,19 +475,31 @@ def prepare_release_evidence(
sdist_identity,
label=sdist_label,
)
dependency_manifest_snapshot = _snapshot_reviewed_input(
dependency_manifest,
snapshot_root,
dependency_manifest_identity,
snapshot_name="reviewed-dependency-manifest",
)
runtime_lock_snapshot = _snapshot_reviewed_input(
runtime_lock,
snapshot_root,
runtime_lock_identity,
snapshot_name="reviewed-runtime-lock",
)
generator = _load_attestable_generator()
wheel_sbom = _strict_pretty_json_bytes(
generator.build_attestable_sbom(
wheel_snapshot,
dependency_manifest,
runtime_lock,
dependency_manifest_snapshot,
runtime_lock_snapshot,
)
)
sdist_sbom = _strict_pretty_json_bytes(
generator.build_attestable_sbom(
sdist_snapshot,
dependency_manifest,
runtime_lock,
dependency_manifest_snapshot,
runtime_lock_snapshot,
)
)

Expand Down Expand Up @@ -486,4 +564,4 @@ def main() -> int:


if __name__ == "__main__":
raise SystemExit(main())
raise SystemExit(main())
Loading
Loading