fix(assessment): keep release origin unproven
Some checks failed
Dependency Audit / dependency-audit (push) Failing after 15s
Security Audit / security-audit (push) Failing after 14s

This commit is contained in:
2026-07-22 19:55:35 +02:00
parent 4c16069f88
commit ddee5c00dc
6 changed files with 689 additions and 121 deletions

View File

@@ -389,7 +389,7 @@ that observation to the assessment and signed catalog:
--output /var/tmp/govoplan-fit-review.json --output /var/tmp/govoplan-fit-review.json
``` ```
The collector follows the strict version `0.2.0` The collector follows the strict version `0.3.0`
[`installed-composition-evidence.schema.json`](installed-composition-evidence.schema.json) [`installed-composition-evidence.schema.json`](installed-composition-evidence.schema.json)
contract. It enumerates all installed distributions whose normalized name starts contract. It enumerates all installed distributions whose normalized name starts
with `govoplan-`, compares the enabled assessed package and module-manifest with `govoplan-`, compares the enabled assessed package and module-manifest
@@ -399,37 +399,47 @@ entries and evidence-backed conclusions.
For each distribution, the collector also: For each distribution, the collector also:
- verifies every supported SHA-256 immutable wheel-payload entry in installed - reads the installed wheel `RECORD` file directly, with a 4 MiB metadata bound,
`RECORD` metadata, rather than relying on an interpreted distribution file list; missing files,
within fixed limits of 256 GovOPlaN distributions, 10,000 files and 512 MiB duplicate or malformed declarations, mismatched declared sizes and hashes all
hashed per distribution, 50,000 files and 2 GiB hashed for one collection, prevent a `verified` result;
and 64 MiB for any single file; - verifies every supported SHA-256 wheel-payload declaration in that local
metadata, within fixed limits of 256 GovOPlaN distributions, 10,000 files and
512 MiB hashed per distribution, 50,000 files and 2 GiB hashed for one
collection, and 64 MiB for any single file;
- accepts package-owned data-file targets below the installation prefix and - accepts package-owned data-file targets below the installation prefix and
declared `console_scripts`, while rejecting other traversal, undeclared declared `console_scripts`, while rejecting other traversal, undeclared
scripts, paths outside the prefix and leaf symlinks before opening a file; scripts, paths outside the prefix and leaf symlinks before opening a file;
- reports pip-generated, unhashed `__pycache__/*.pyc` entries separately only - reports pip-generated, unhashed `__pycache__/*.pyc` entries separately only
when their derived source is a hashed payload entry, including generated when their derived source is a hashed payload entry, including generated
files below a confined package-owned runtime-data prefix; files below a confined package-owned runtime-data prefix;
- distinguishes coherent PEP 610 Git commits and archive hashes from editable installs, - labels every PEP 610 observation with the explicit
local directories, package-index/unknown origins and malformed metadata; `local-pep610-metadata` basis and distinguishes coherent declared Git commits
and archive hashes from editable installs, local directories,
package-index/unknown origins and malformed metadata;
- compares a non-editable VCS commit with the assessment commit and, when one is - compares a non-editable VCS commit with the assessment commit and, when one is
present, the signed catalog `selected_units` commit; present, the signed catalog `selected_units` commit;
- records only package/module identifiers, versions, bounded counters, hashes - records only package/module identifiers, versions, bounded counters, hashes
and stable error codes. It never writes direct URLs, paths, hostnames, and stable error codes. It never writes direct URLs, paths, hostnames,
usernames, exception text or file contents. usernames, exception text or file contents.
`RECORD` `verified` means complete matching coverage of the immutable wheel `RECORD` `verified` means complete matching coverage of the wheel payload
payload, not every runtime byte. The proof scope exposes the aggregate count of declared by the local metadata, not every runtime byte and not a release-origin
generated unhashed bytecode; runtime activation remains unchecked. `RECORD` binding. The proof scope exposes the aggregate count of generated unhashed
agreement proves that payload files agree with installed metadata, but does not bytecode; runtime activation remains unchecked. `RECORD` agreement proves that
make self-consistent metadata a trusted release origin. Similarly, a version payload files agree with installed metadata, but does not make self-consistent
string is not artifact integrity. PEP 610 VCS evidence is accepted only for metadata a trusted release origin. Similarly, a version string is not artifact
integrity. PEP 610 VCS metadata is accepted only for
`vcs: git`, a full 4064 hexadecimal commit, one mutually exclusive provenance `vcs: git`, a full 4064 hexadecimal commit, one mutually exclusive provenance
form and a coherent bounded URL shape. Editable and directory-backed installs form and a coherent bounded URL shape. Editable and directory-backed installs
remain mutable even when their small editable-install `RECORD` is valid. Archive remain mutable even when their small editable-install `RECORD` is valid. Archive
or index artifacts without a hash anchored by the assessed release remain or index artifacts without a hash anchored by the assessed release remain
unanchored. These conditions cause review rather than being promoted to unanchored. A matching declaration is reported under
immutable installed-release proof. `installed_source_provenance` as local consistency only, with
`release_origin_bound: false`. The separate `installed_release_origin` scope
remains unchecked until an independently anchored artifact digest or signed
installer receipt binds each installed payload to the signed catalog. The tool
does not accept a digest merely asserted inside the installed evidence document.
Only evidence produced in-process by `--collect-installed-evidence` is a local Only evidence produced in-process by `--collect-installed-evidence` is a local
observation, and it must be no more than five minutes old (with at most 30 observation, and it must be no more than five minutes old (with at most 30
@@ -466,7 +476,10 @@ claims use `passed` or `failed`; production claims use `approved` or `rejected`.
One claim per scope, unique control/artifact IDs, `issued_at < expires_at`, current One claim per scope, unique control/artifact IDs, `issued_at < expires_at`, current
validity and exact digest binding are mandatory. Any schema, binding, time, validity and exact digest binding are mandatory. Any schema, binding, time,
signature or authority blocker leaves every supplied boundary claim unchecked; signature or authority blocker leaves every supplied boundary claim unchecked;
an authorized negative result is checked but requires review. one malformed authority member or invalid validity interval invalidates the
supplied authority set as a whole. An authorized negative result is checked but
requires review only after every prerequisite, including installed
release-origin binding, is established.
Signatures cover UTF-8 JSON with the `signatures` member omitted, object keys Signatures cover UTF-8 JSON with the `signatures` member omitted, object keys
sorted, compact `,`/`:` separators and non-ASCII characters escaped, matching sorted, compact `,`/`:` separators and non-ASCII characters escaped, matching
the tool's deterministic canonicalization. the tool's deterministic canonicalization.
@@ -483,11 +496,13 @@ the tool's deterministic canonicalization.
With `--installed-evidence`, this command performs comparison and proof-binding With `--installed-evidence`, this command performs comparison and proof-binding
diagnostics only: the imported installed document is unsigned, so neither it nor diagnostics only: the imported installed document is unsigned, so neither it nor
the boundary claim becomes accepted proof. An accepted boundary review requires the boundary claim becomes accepted proof. Direct in-process collection removes
a trusted orchestrator to retain the direct in-process observation, obtain the that unsigned-import gap, but still establishes only local metadata consistency.
external signature over its digest, and call the review library within the No boundary claim is admitted by the current format because it has no separately
five-minute observation window. A future signed collector-attestation format anchored artifact digest or signed installer receipt. A future evidence contract
should make that exchange durable across processes. must bind such an independently trusted release-artifact receipt to the installed
evidence digest; a signed collector-attestation format should also make that
exchange durable across processes.
Target-environment and production-approval claims must use the assessment's Target-environment and production-approval claims must use the assessment's
`deployment_profile.id` as `subject_id`. External-provider claims require the `deployment_profile.id` as `subject_id`. External-provider claims require the
@@ -499,8 +514,10 @@ The authority keyring is a governance trust root. Do not download or generate it
from the proof bundle being checked. The checker rejects proof-authority public from the proof bundle being checked. The checker rejects proof-authority public
keys reused by either the published or independently trusted catalog keyring, keys reused by either the published or independently trusted catalog keyring,
and rejects the same proof public-key material assigned to multiple authority and rejects the same proof public-key material assigned to multiple authority
IDs. Authority `not_after` is exclusive. A target operator or approver must validate IDs. A malformed key, an invalid or empty validity interval, or ambiguous key
the referenced drill/result artifacts before signing. The rerun verifies the identity invalidates the supplied authority set. Authority `not_after` is
exclusive. A target operator or approver must validate the referenced
drill/result artifacts before signing. The rerun verifies the
attestation and its bindings; it does not fetch or reinterpret those artifacts. attestation and its bindings; it does not fetch or reinterpret those artifacts.
`--verification-time` exists only for reproducible historical review. Supplying `--verification-time` exists only for reproducible historical review. Supplying
it always marks the machine and human report as a **HISTORICAL override**; callers it always marks the machine and human report as a **HISTORICAL override**; callers

View File

@@ -20,7 +20,7 @@
"format": "uri-reference" "format": "uri-reference"
}, },
"schema_version": { "schema_version": {
"const": "0.2.0" "const": "0.3.0"
}, },
"evidence_kind": { "evidence_kind": {
"const": "govoplan.installed-composition" "const": "govoplan.installed-composition"
@@ -122,8 +122,11 @@
{ {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["kind", "commit"], "required": ["basis", "kind", "commit"],
"properties": { "properties": {
"basis": {
"const": "local-pep610-metadata"
},
"kind": { "kind": {
"const": "vcs-commit" "const": "vcs-commit"
}, },
@@ -136,8 +139,11 @@
{ {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["kind", "sha256"], "required": ["basis", "kind", "sha256"],
"properties": { "properties": {
"basis": {
"const": "local-pep610-metadata"
},
"kind": { "kind": {
"const": "archive" "const": "archive"
}, },
@@ -149,8 +155,11 @@
{ {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["kind"], "required": ["basis", "kind"],
"properties": { "properties": {
"basis": {
"const": "local-pep610-metadata"
},
"kind": { "kind": {
"enum": [ "enum": [
"editable-local", "editable-local",

View File

@@ -55,7 +55,7 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
"docs/capability-fit-proof-authority-keyring.schema.json" "docs/capability-fit-proof-authority-keyring.schema.json"
) )
def test_matching_installed_composition_proves_versions_records_and_commits( def test_matching_installed_composition_proves_local_consistency_not_origin(
self, self,
) -> None: ) -> None:
catalog, keyring = signed_catalog(self.assessment) catalog, keyring = signed_catalog(self.assessment)
@@ -71,6 +71,17 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
self.assertTrue(report["proof_scope"]["installed_artifacts"]["valid"]) self.assertTrue(report["proof_scope"]["installed_artifacts"]["valid"])
self.assertTrue(report["proof_scope"]["installed_record_integrity"]["valid"]) self.assertTrue(report["proof_scope"]["installed_record_integrity"]["valid"])
self.assertTrue(report["proof_scope"]["installed_source_provenance"]["valid"]) self.assertTrue(report["proof_scope"]["installed_source_provenance"]["valid"])
self.assertEqual(
"local-pep610-metadata",
report["proof_scope"]["installed_source_provenance"]["basis"],
)
self.assertFalse(
report["proof_scope"]["installed_source_provenance"][
"release_origin_bound"
]
)
self.assertFalse(report["proof_scope"]["installed_release_origin"]["checked"])
self.assertIsNone(report["proof_scope"]["installed_release_origin"]["valid"])
self.assertFalse(report["proof_scope"]["runtime_activation"]["checked"]) self.assertFalse(report["proof_scope"]["runtime_activation"]["checked"])
self.assertFalse(report["proof_scope"]["target_environment"]["checked"]) self.assertFalse(report["proof_scope"]["target_environment"]["checked"])
self.assertEqual( self.assertEqual(
@@ -78,6 +89,40 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
report["proof_scope"]["installed_artifacts"]["evidence_sha256"], report["proof_scope"]["installed_artifacts"]["evidence_sha256"],
) )
def test_catalog_trust_blocker_invalidates_dependent_evidence_scopes(
self,
) -> None:
catalog, keyring = signed_catalog(self.assessment)
catalog["signatures"][0]["value"] = "AAAA"
installed = matching_installed_evidence(self.assessment)
proof, authority = signed_boundary_evidence(
assessment=self.assessment,
installed=installed,
claims=[boundary_claim("target_environment", "passed")],
allowed_scopes=["target_environment"],
)
report = self.review(
catalog=catalog,
keyring=keyring,
installed_evidence=installed,
boundary_evidence=proof,
authority_keyring=authority,
)
self.assertEqual("blocked", report["status"])
self.assertFalse(
report["proof_scope"]["catalog_signature_and_keyring"]["valid"]
)
self.assertFalse(report["proof_scope"]["installed_artifacts"]["checked"])
self.assertIsNone(report["proof_scope"]["installed_artifacts"]["valid"])
self.assertFalse(report["proof_scope"]["target_environment"]["checked"])
self.assertIsNone(report["proof_scope"]["target_environment"]["valid"])
self.assertIn(
"installed_catalog_trust_unavailable",
{item["code"] for item in report["findings"]},
)
def test_missing_extra_and_version_drift_have_deterministic_review_targets( def test_missing_extra_and_version_drift_have_deterministic_review_targets(
self, self,
) -> None: ) -> None:
@@ -137,7 +182,10 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
for item in evidence["artifacts"] for item in evidence["artifacts"]
if item["package_name"] == "govoplan-campaign" if item["package_name"] == "govoplan-campaign"
) )
campaign["source_provenance"] = {"kind": "editable-local"} campaign["source_provenance"] = {
"basis": "local-pep610-metadata",
"kind": "editable-local",
}
campaign["record_integrity"] = { campaign["record_integrity"] = {
**campaign["record_integrity"], **campaign["record_integrity"],
"status": "partial", "status": "partial",
@@ -212,6 +260,7 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
schema=self.installed_schema, schema=self.installed_schema,
observation_mode="direct_local", observation_mode="direct_local",
verification_time=datetime(2026, 7, 23, 12, tzinfo=UTC), verification_time=datetime(2026, 7, 23, 12, tzinfo=UTC),
catalog_trusted=True,
) )
codes = {item.code for item in result.findings} codes = {item.code for item in result.findings}
@@ -317,6 +366,10 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
) )
observed = evidence["artifacts"][0] observed = evidence["artifacts"][0]
self.assertEqual("editable-local", observed["source_provenance"]["kind"]) self.assertEqual("editable-local", observed["source_provenance"]["kind"])
self.assertEqual(
"local-pep610-metadata",
observed["source_provenance"]["basis"],
)
self.assertEqual("verified", observed["record_integrity"]["status"]) self.assertEqual("verified", observed["record_integrity"]["status"])
encoded = json.dumps(evidence, sort_keys=True) encoded = json.dumps(evidence, sort_keys=True)
self.assertNotIn(str(root), encoded) self.assertNotIn(str(root), encoded)
@@ -338,6 +391,108 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
tampered["artifacts"][0]["record_integrity"]["mismatched_file_count"], tampered["artifacts"][0]["record_integrity"]["mismatched_file_count"],
) )
def test_collector_detects_payload_declared_in_record_but_missing(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
distribution, payload_file = create_distribution(root)
payload_file.unlink()
with mock.patch.object(sys, "path", [str(root), *sys.path]):
evidence = collect_installed_composition(
assessment=self.assessment,
collected_at=datetime(2026, 7, 22, 12, tzinfo=UTC),
distributions=[distribution],
)
integrity = evidence["artifacts"][0]["record_integrity"]
self.assertEqual("mismatch", integrity["status"])
self.assertEqual(1, integrity["missing_file_count"])
def test_collector_rejects_duplicate_record_declarations(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
distribution, _ = create_distribution(root)
record_path = root / "govoplan_demo-1.0.0.dist-info" / "RECORD"
rows = record_path.read_text(encoding="utf-8").splitlines()
record_path.write_text(
"\n".join((*rows, rows[0])) + "\n",
encoding="utf-8",
)
with mock.patch.object(sys, "path", [str(root), *sys.path]):
evidence = collect_installed_composition(
assessment=self.assessment,
collected_at=datetime(2026, 7, 22, 12, tzinfo=UTC),
distributions=[distribution],
)
integrity = evidence["artifacts"][0]["record_integrity"]
self.assertEqual("partial", integrity["status"])
self.assertGreater(integrity["unverifiable_file_count"], 0)
def test_collector_rejects_malformed_record_declarations(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
distribution, _ = create_distribution(root)
record_path = root / "govoplan_demo-1.0.0.dist-info" / "RECORD"
record_path.write_text(
record_path.read_text(encoding="utf-8") + "malformed,row\n",
encoding="utf-8",
)
with mock.patch.object(sys, "path", [str(root), *sys.path]):
evidence = collect_installed_composition(
assessment=self.assessment,
collected_at=datetime(2026, 7, 22, 12, tzinfo=UTC),
distributions=[distribution],
)
integrity = evidence["artifacts"][0]["record_integrity"]
self.assertEqual("partial", integrity["status"])
self.assertGreater(integrity["unverifiable_file_count"], 0)
def test_collector_verifies_declared_record_sizes(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
distribution, _ = create_distribution(root)
record_path = root / "govoplan_demo-1.0.0.dist-info" / "RECORD"
rows = record_path.read_text(encoding="utf-8").splitlines()
path, digest, size = rows[0].split(",")
rows[0] = f"{path},{digest},{int(size) + 1}"
record_path.write_text("\n".join(rows) + "\n", encoding="utf-8")
with mock.patch.object(sys, "path", [str(root), *sys.path]):
evidence = collect_installed_composition(
assessment=self.assessment,
collected_at=datetime(2026, 7, 22, 12, tzinfo=UTC),
distributions=[distribution],
)
integrity = evidence["artifacts"][0]["record_integrity"]
self.assertEqual("mismatch", integrity["status"])
self.assertEqual(1, integrity["mismatched_file_count"])
def test_collector_rejects_symlinked_distribution_metadata_root(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
create_distribution(root)
metadata_root = root / "govoplan_demo-1.0.0.dist-info"
real_metadata_root = root / "real-govoplan-demo.dist-info"
metadata_root.rename(real_metadata_root)
metadata_root.symlink_to(real_metadata_root, target_is_directory=True)
distribution = metadata.PathDistribution(metadata_root)
with mock.patch.object(sys, "path", [str(root), *sys.path]):
evidence = collect_installed_composition(
assessment=self.assessment,
collected_at=datetime(2026, 7, 22, 12, tzinfo=UTC),
distributions=[distribution],
)
integrity = evidence["artifacts"][0]["record_integrity"]
self.assertEqual("unavailable", integrity["status"])
self.assertEqual(0, integrity["hashed_file_count"])
def test_collector_accepts_real_core_wheel_payload_scripts_data_and_pyc( def test_collector_accepts_real_core_wheel_payload_scripts_data_and_pyc(
self, self,
) -> None: ) -> None:
@@ -604,7 +759,9 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
json.dumps(reversed_order, sort_keys=True), json.dumps(reversed_order, sort_keys=True),
) )
def test_signed_scope_authority_can_validate_only_its_external_claim(self) -> None: def test_signed_boundary_claim_waits_for_anchored_installed_release_origin(
self,
) -> None:
catalog, keyring = signed_catalog(self.assessment) catalog, keyring = signed_catalog(self.assessment)
installed = matching_installed_evidence(self.assessment) installed = matching_installed_evidence(self.assessment)
proof, authority = signed_boundary_evidence( proof, authority = signed_boundary_evidence(
@@ -622,10 +779,15 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
authority_keyring=authority, authority_keyring=authority,
) )
self.assertEqual("current", report["status"]) self.assertEqual("blocked", report["status"])
self.assertTrue(report["proof_scope"]["target_environment"]["valid"]) self.assertFalse(report["proof_scope"]["target_environment"]["checked"])
self.assertIsNone(report["proof_scope"]["target_environment"]["valid"])
self.assertFalse(report["proof_scope"]["external_providers"]["checked"]) self.assertFalse(report["proof_scope"]["external_providers"]["checked"])
self.assertFalse(report["proof_scope"]["production_approval"]["checked"]) self.assertFalse(report["proof_scope"]["production_approval"]["checked"])
self.assertIn(
"boundary_installed_release_origin_untrusted",
{item["code"] for item in report["findings"]},
)
self.assertEqual( self.assertEqual(
self.assessment["deployment_profile"]["id"], self.assessment["deployment_profile"]["id"],
report["proof_scope"]["target_environment"]["expected_subject_id"], report["proof_scope"]["target_environment"]["expected_subject_id"],
@@ -666,10 +828,15 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
"boundary_provider_subject_unconfigured", "boundary_provider_subject_unconfigured",
{item["code"] for item in unconfigured["findings"]}, {item["code"] for item in unconfigured["findings"]},
) )
self.assertEqual("current", accepted["status"]) self.assertEqual("blocked", accepted["status"])
provider_scope = accepted["proof_scope"]["external_providers"] provider_scope = accepted["proof_scope"]["external_providers"]
self.assertFalse(provider_scope["checked"])
self.assertEqual("provider:test", provider_scope["expected_subject_id"]) self.assertEqual("provider:test", provider_scope["expected_subject_id"])
self.assertEqual("provider:test", provider_scope["observed_subject_id"]) self.assertEqual("provider:test", provider_scope["observed_subject_id"])
self.assertIn(
"boundary_installed_release_origin_untrusted",
{item["code"] for item in accepted["findings"]},
)
def test_boundary_subject_mismatch_blocks_without_echoing_invalid_config( def test_boundary_subject_mismatch_blocks_without_echoing_invalid_config(
self, self,
@@ -711,7 +878,7 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
self.assertNotIn("private.invalid", encoded) self.assertNotIn("private.invalid", encoded)
self.assertNotIn("secret", encoded) self.assertNotIn("secret", encoded)
def test_authorized_negative_claim_creates_scope_specific_review_target( def test_authorized_negative_claim_waits_for_installed_origin_binding(
self, self,
) -> None: ) -> None:
catalog, keyring = signed_catalog(self.assessment) catalog, keyring = signed_catalog(self.assessment)
@@ -734,14 +901,12 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
authority_keyring=authority, authority_keyring=authority,
) )
targets = {item["id"] for item in report["review_targets"]} self.assertEqual("blocked", report["status"])
self.assertIn( self.assertFalse(report["proof_scope"]["target_environment"]["checked"])
self.assertFalse(report["proof_scope"]["production_approval"]["checked"])
self.assertNotIn(
"boundary.target_environment.production-like-dev", "boundary.target_environment.production-like-dev",
targets, {item["id"] for item in report["review_targets"]},
)
self.assertIn(
"boundary.production_approval.production-like-dev",
targets,
) )
def test_proof_authorities_must_be_independent_and_key_ids_unambiguous( def test_proof_authorities_must_be_independent_and_key_ids_unambiguous(
@@ -818,6 +983,55 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
{item["code"] for item in report["findings"]}, {item["code"] for item in report["findings"]},
) )
def test_malformed_authority_members_invalidate_the_whole_keyring(self) -> None:
catalog, keyring = signed_catalog(self.assessment)
installed = matching_installed_evidence(self.assessment)
proof, authority = signed_boundary_evidence(
assessment=self.assessment,
installed=installed,
claims=[boundary_claim("target_environment", "passed")],
allowed_scopes=["target_environment"],
)
malformed = deepcopy(authority)
malformed["keys"].append(
{
"key_id": "authority:malformed",
"status": "active",
"public_key": "not-base64",
"allowed_scopes": ["target_environment"],
}
)
inverted = deepcopy(authority)
inverted["keys"][0]["not_before"] = "2026-08-01T00:00:00Z"
inverted["keys"][0]["not_after"] = "2026-07-01T00:00:00Z"
cases = (
(malformed, "boundary_authority_key_invalid"),
(inverted, "boundary_authority_key_interval_invalid"),
)
for supplied_keyring, expected_code in cases:
with self.subTest(expected_code=expected_code):
report = self.review(
catalog=catalog,
keyring=keyring,
installed_evidence=installed,
boundary_evidence=proof,
authority_keyring=supplied_keyring,
)
self.assertEqual("blocked", report["status"])
self.assertIn(
expected_code,
{item["code"] for item in report["findings"]},
)
self.assertIn(
"boundary_authority_untrusted",
{item["code"] for item in report["findings"]},
)
self.assertFalse(
report["proof_scope"]["target_environment"]["checked"]
)
def test_historical_verification_and_generated_runtime_gap_are_visible( def test_historical_verification_and_generated_runtime_gap_are_visible(
self, self,
) -> None: ) -> None:
@@ -855,6 +1069,7 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
observation_mode="direct_local", observation_mode="direct_local",
verification_time=datetime(2026, 7, 23, 12, tzinfo=UTC), verification_time=datetime(2026, 7, 23, 12, tzinfo=UTC),
verification_mode="https://private.invalid/secret", verification_mode="https://private.invalid/secret",
catalog_trusted=True,
) )
encoded = json.dumps(result.proof_scope, sort_keys=True) encoded = json.dumps(result.proof_scope, sort_keys=True)
@@ -981,7 +1196,7 @@ class CapabilityFitEvidenceTests(unittest.TestCase):
def matching_installed_evidence(assessment: dict[str, object]) -> dict[str, object]: def matching_installed_evidence(assessment: dict[str, object]) -> dict[str, object]:
return { return {
"$schema": "./installed-composition-evidence.schema.json", "$schema": "./installed-composition-evidence.schema.json",
"schema_version": "0.2.0", "schema_version": "0.3.0",
"evidence_kind": "govoplan.installed-composition", "evidence_kind": "govoplan.installed-composition",
"assessment_id": assessment["assessment_id"], "assessment_id": assessment["assessment_id"],
"assessment_release": assessment["release"]["ref"], "assessment_release": assessment["release"]["ref"],
@@ -1010,7 +1225,11 @@ def artifact(
"modules": [] "modules": []
if module_id == "core" if module_id == "core"
else [{"module_id": module_id, "manifest_version": version}], else [{"module_id": module_id, "manifest_version": version}],
"source_provenance": {"kind": "vcs-commit", "commit": commit}, "source_provenance": {
"basis": "local-pep610-metadata",
"kind": "vcs-commit",
"commit": commit,
},
"record_integrity": { "record_integrity": {
"status": "verified", "status": "verified",
"hashed_file_count": 1, "hashed_file_count": 1,

View File

@@ -360,7 +360,8 @@ class CapabilityFitReviewTests(unittest.TestCase):
self.assertIn("Capability fit rerun: current", rendered) self.assertIn("Capability fit rerun: current", rendered)
self.assertIn( self.assertIn(
"No installed-artifact, target-provider, or production proof", rendered "No installed-composition, installed-release-origin, target-environment, external-provider, production-approval proof",
rendered,
) )
def test_public_fetch_failure_is_generic_and_blocking(self) -> None: def test_public_fetch_failure_is_generic_and_blocking(self) -> None:

View File

@@ -363,6 +363,9 @@ def review_capability_fit(
evidence_verification_mode = ( evidence_verification_mode = (
"historical_override" if verification_time is not None else "current" "historical_override" if verification_time is not None else "current"
) )
catalog_dependency_trusted = not any(
item.severity == "blocker" for item in findings
)
installed_review = review_installed_composition( installed_review = review_installed_composition(
assessment=assessment, assessment=assessment,
catalog_entries=catalog_entries, catalog_entries=catalog_entries,
@@ -373,6 +376,7 @@ def review_capability_fit(
observation_mode=installed_evidence_mode, observation_mode=installed_evidence_mode,
verification_time=evidence_verification_time, verification_time=evidence_verification_time,
verification_mode=evidence_verification_mode, verification_mode=evidence_verification_mode,
catalog_trusted=catalog_dependency_trusted,
) )
findings.extend( findings.extend(
Finding(item.severity, item.code, item.message, item.assessment_ids) Finding(item.severity, item.code, item.message, item.assessment_ids)
@@ -884,7 +888,17 @@ def build_report(
else { else {
"installed_artifacts": {"checked": False, "valid": None}, "installed_artifacts": {"checked": False, "valid": None},
"installed_record_integrity": {"checked": False, "valid": None}, "installed_record_integrity": {"checked": False, "valid": None},
"installed_source_provenance": {"checked": False, "valid": None}, "installed_source_provenance": {
"checked": False,
"valid": None,
"basis": "local-pep610-metadata",
"release_origin_bound": False,
},
"installed_release_origin": {
"checked": False,
"valid": None,
"release_origin_bound": False,
},
"runtime_activation": {"checked": False, "valid": None}, "runtime_activation": {"checked": False, "valid": None},
} }
) )
@@ -925,7 +939,7 @@ def build_report(
**boundary_scope, **boundary_scope,
} }
return { return {
"report_version": "0.3.0", "report_version": "0.4.0",
"status": status, "status": status,
"assessment_id": assessment.get("assessment_id"), "assessment_id": assessment.get("assessment_id"),
"assessment_release": assessment.get("release", {}).get("ref") "assessment_release": assessment.get("release", {}).get("ref")
@@ -950,6 +964,9 @@ def render_review(report: dict[str, Any]) -> str:
proof_scope = report.get("proof_scope", {}) proof_scope = report.get("proof_scope", {})
installed_scope = proof_scope.get("installed_artifacts", {}) installed_scope = proof_scope.get("installed_artifacts", {})
installed_checked = installed_scope.get("checked") is True installed_checked = installed_scope.get("checked") is True
release_origin_checked = (
proof_scope.get("installed_release_origin", {}).get("checked") is True
)
installed_supplied = bool(installed_scope.get("evidence_sha256")) or ( installed_supplied = bool(installed_scope.get("evidence_sha256")) or (
"installed_evidence_observation" in proof_scope "installed_evidence_observation" in proof_scope
) )
@@ -960,7 +977,7 @@ def render_review(report: dict[str, Any]) -> str:
( (
"Scope: schema, signed release metadata, optional local tag provenance, " "Scope: schema, signed release metadata, optional local tag provenance, "
+ ( + (
"and locally observed installed-composition evidence." "and locally observed installed-composition consistency evidence; release origin remains a separate proof boundary."
if installed_checked if installed_checked
else ( else (
"and supplied installed-composition evidence that did not establish local proof." "and supplied installed-composition evidence that did not establish local proof."
@@ -1006,12 +1023,12 @@ def render_review(report: dict[str, Any]) -> str:
if report.get("proof_scope", {}).get(key, {}).get("checked") is not True if report.get("proof_scope", {}).get(key, {}).get("checked") is not True
] ]
if not installed_checked: if not installed_checked:
unchecked_boundaries.insert(0, "installed-artifact") unchecked_boundaries.insert(0, "installed-composition")
if not installed_checked and len(unchecked_boundaries) == 4: if not release_origin_checked:
lines.append( unchecked_boundaries.insert(
"No installed-artifact, target-provider, or production proof was performed." 1 if not installed_checked else 0, "installed-release-origin"
) )
elif unchecked_boundaries: if unchecked_boundaries:
lines.append("No " + ", ".join(unchecked_boundaries) + " proof was performed.") lines.append("No " + ", ".join(unchecked_boundaries) + " proof was performed.")
return "\n".join(lines) + "\n" return "\n".join(lines) + "\n"

View File

@@ -3,12 +3,14 @@
from __future__ import annotations from __future__ import annotations
import base64 import base64
import csv
from dataclasses import dataclass from dataclasses import dataclass
from datetime import UTC, datetime, timedelta from datetime import UTC, datetime, timedelta
import hashlib import hashlib
import hmac import hmac
from importlib import metadata from importlib import metadata
from importlib.util import source_from_cache from importlib.util import source_from_cache
import io
import json import json
import os import os
from pathlib import Path, PurePosixPath from pathlib import Path, PurePosixPath
@@ -27,6 +29,8 @@ from jsonschema.exceptions import SchemaError
MAX_GOVOPLAN_DISTRIBUTIONS = 256 MAX_GOVOPLAN_DISTRIBUTIONS = 256
MAX_DISTRIBUTION_FILES = 10_000 MAX_DISTRIBUTION_FILES = 10_000
MAX_COLLECTION_RECORD_FILES = 50_000 MAX_COLLECTION_RECORD_FILES = 50_000
MAX_RECORD_BYTES = 4 * 1024 * 1024
MAX_RECORD_PATH_CHARACTERS = 4096
MAX_HASHED_FILE_BYTES = 64 * 1024 * 1024 MAX_HASHED_FILE_BYTES = 64 * 1024 * 1024
MAX_HASHED_DISTRIBUTION_BYTES = 512 * 1024 * 1024 MAX_HASHED_DISTRIBUTION_BYTES = 512 * 1024 * 1024
MAX_HASHED_COLLECTION_BYTES = 2 * 1024 * 1024 * 1024 MAX_HASHED_COLLECTION_BYTES = 2 * 1024 * 1024 * 1024
@@ -56,17 +60,17 @@ NEGATIVE_RESULTS = {
} }
PROOF_REQUIREMENTS = { PROOF_REQUIREMENTS = {
"target_environment": ( "target_environment": (
"A target-run result bound to this assessment release and installed-evidence digest.", "A target-run result bound to this assessment release and installed evidence whose release origin is independently anchored.",
"An independently trusted authority key permitted for target_environment proof.", "An independently trusted authority key permitted for target_environment proof.",
"Opaque control IDs and content hashes for the target acceptance artifacts.", "Opaque control IDs and content hashes for the target acceptance artifacts.",
), ),
"external_providers": ( "external_providers": (
"Provider acceptance results bound to this assessment release and installed-evidence digest.", "Provider acceptance results bound to this assessment release and installed evidence whose release origin is independently anchored.",
"An independently trusted authority key permitted for external_providers proof.", "An independently trusted authority key permitted for external_providers proof.",
"Opaque control IDs and content hashes; credentials and endpoint identifiers stay outside the report.", "Opaque control IDs and content hashes; credentials and endpoint identifiers stay outside the report.",
), ),
"production_approval": ( "production_approval": (
"An explicit approval bound to this assessment release and installed-evidence digest.", "An explicit approval bound to this assessment release and installed evidence whose release origin is independently anchored.",
"An independently provisioned authority key permitted for production_approval.", "An independently provisioned authority key permitted for production_approval.",
"An unexpired signed proof bundle; an assessment or operator cannot approve itself.", "An unexpired signed proof bundle; an assessment or operator cannot approve itself.",
), ),
@@ -104,6 +108,14 @@ class CollectionHashBudget:
remaining_bytes: int = MAX_HASHED_COLLECTION_BYTES remaining_bytes: int = MAX_HASHED_COLLECTION_BYTES
@dataclass(frozen=True, slots=True)
class RecordEntry:
path: str
hash_mode: str | None
hash_value: str | None
size: int | None
def validate_payload( def validate_payload(
*, payload: dict[str, Any], schema: dict[str, Any] *, payload: dict[str, Any], schema: dict[str, Any]
) -> tuple[str, ...]: ) -> tuple[str, ...]:
@@ -231,7 +243,7 @@ def collect_installed_composition(
sorted_issues.sort(key=lambda item: (item["package_name"], item["code"])) sorted_issues.sort(key=lambda item: (item["package_name"], item["code"]))
return { return {
"$schema": "./installed-composition-evidence.schema.json", "$schema": "./installed-composition-evidence.schema.json",
"schema_version": "0.2.0", "schema_version": "0.3.0",
"evidence_kind": "govoplan.installed-composition", "evidence_kind": "govoplan.installed-composition",
"assessment_id": _safe_opaque_id( "assessment_id": _safe_opaque_id(
assessment.get("assessment_id"), fallback="invalid-assessment" assessment.get("assessment_id"), fallback="invalid-assessment"
@@ -264,6 +276,7 @@ def review_installed_composition(
observation_mode: str = "imported_unsigned", observation_mode: str = "imported_unsigned",
verification_time: datetime | None = None, verification_time: datetime | None = None,
verification_mode: str = "current", verification_mode: str = "current",
catalog_trusted: bool = False,
) -> InstalledEvidenceReview: ) -> InstalledEvidenceReview:
if evidence is None: if evidence is None:
return InstalledEvidenceReview( return InstalledEvidenceReview(
@@ -337,6 +350,15 @@ def review_installed_composition(
("assessment.installed_composition",), ("assessment.installed_composition",),
) )
) )
if catalog_trusted is not True:
findings.append(
EvidenceFinding(
"blocker",
"installed_catalog_trust_unavailable",
"Installed-composition proof depends on a valid signed catalog.",
("assessment.release", "assessment.installed_composition"),
)
)
changes: list[dict[str, Any]] = [] changes: list[dict[str, Any]] = []
changed_repositories: set[str] = set() changed_repositories: set[str] = set()
affected_module_ids: set[str] = set() affected_module_ids: set[str] = set()
@@ -391,6 +413,8 @@ def review_installed_composition(
) )
if effective_verification_mode == "invalid": if effective_verification_mode == "invalid":
observation_trusted = False observation_trusted = False
if catalog_trusted is not True:
observation_trusted = False
expected_release = ( expected_release = (
assessment.get("release", {}).get("ref") assessment.get("release", {}).get("ref")
if isinstance(assessment.get("release"), dict) if isinstance(assessment.get("release"), dict)
@@ -501,7 +525,7 @@ def review_installed_composition(
record_verified = 0 record_verified = 0
generated_unhashed_file_count = 0 generated_unhashed_file_count = 0
provenance_expected = 0 provenance_expected = 0
provenance_anchored = 0 provenance_declared_match = 0
provenance_mutable = 0 provenance_mutable = 0
provenance_unanchored = 0 provenance_unanchored = 0
artifact_valid = binding_valid and not duplicate_packages artifact_valid = binding_valid and not duplicate_packages
@@ -648,6 +672,23 @@ def review_installed_composition(
provenance_kind = ( provenance_kind = (
str(provenance.get("kind") or "") if isinstance(provenance, dict) else "" str(provenance.get("kind") or "") if isinstance(provenance, dict) else ""
) )
provenance_basis = (
str(provenance.get("basis") or "") if isinstance(provenance, dict) else ""
)
if provenance_basis != "local-pep610-metadata":
artifact_valid = False
provenance_unanchored += 1
_record_change(
findings=findings,
changes=changes,
changed_repositories=changed_repositories,
affected_module_ids=affected_module_ids,
component=component,
kind="installed_source_metadata_invalid",
code="installed_source_metadata_basis_invalid",
message=f"Installed distribution {package_name!r} does not identify its source statement as local PEP 610 metadata.",
)
continue
if provenance_kind == "vcs-commit": if provenance_kind == "vcs-commit":
commit = str(provenance.get("commit") or "").lower() commit = str(provenance.get("commit") or "").lower()
selected_version = selected_versions.get(repository) selected_version = selected_versions.get(repository)
@@ -660,7 +701,7 @@ def review_installed_composition(
or commit == selected_commit.lower() or commit == selected_commit.lower()
) )
if commit_matches and selected_matches: if commit_matches and selected_matches:
provenance_anchored += 1 provenance_declared_match += 1
else: else:
artifact_valid = False artifact_valid = False
_record_change( _record_change(
@@ -671,7 +712,7 @@ def review_installed_composition(
component=component, component=component,
kind="installed_commit_mismatch", kind="installed_commit_mismatch",
code="installed_source_commit_mismatch", code="installed_source_commit_mismatch",
message=f"Installed immutable VCS provenance for {package_name!r} does not match the assessed or signed selected-unit commit.", message=f"Installed distribution {package_name!r} declares a local PEP 610 VCS commit that does not match the assessed or signed selected-unit commit.",
) )
elif provenance_kind in {"editable-vcs", "editable-local", "local-directory"}: elif provenance_kind in {"editable-vcs", "editable-local", "local-directory"}:
provenance_mutable += 1 provenance_mutable += 1
@@ -684,7 +725,7 @@ def review_installed_composition(
component=component, component=component,
kind="installed_source_mutable", kind="installed_source_mutable",
code="installed_source_mutable", code="installed_source_mutable",
message=f"Installed distribution {package_name!r} is editable or directory-backed and is not immutable release evidence.", message=f"Installed distribution {package_name!r} declares an editable or directory-backed local source.",
) )
else: else:
provenance_unanchored += 1 provenance_unanchored += 1
@@ -697,7 +738,7 @@ def review_installed_composition(
component=component, component=component,
kind="installed_source_unanchored", kind="installed_source_unanchored",
code="installed_source_unanchored", code="installed_source_unanchored",
message=f"Installed distribution {package_name!r} has no source provenance anchored to the assessed release.", message=f"Installed distribution {package_name!r} has no local PEP 610 source statement matching the assessed release.",
) )
expected_packages = set(expected_by_package) expected_packages = set(expected_by_package)
@@ -785,13 +826,25 @@ def review_installed_composition(
}, },
"installed_source_provenance": { "installed_source_provenance": {
"checked": provenance_checked, "checked": provenance_checked,
"valid": provenance_anchored == provenance_expected "valid": provenance_declared_match == provenance_expected
if provenance_checked if provenance_checked
else None, else None,
"anchored_distribution_count": provenance_anchored, "basis": "local-pep610-metadata",
"declared_match_distribution_count": provenance_declared_match,
"mutable_distribution_count": provenance_mutable, "mutable_distribution_count": provenance_mutable,
"unanchored_distribution_count": provenance_unanchored, "unanchored_distribution_count": provenance_unanchored,
"expected_distribution_count": provenance_expected, "expected_distribution_count": provenance_expected,
"release_origin_bound": False,
},
"installed_release_origin": {
"checked": False,
"valid": None,
"release_origin_bound": False,
"anchored_artifact_digest_count": 0,
"expected_distribution_count": provenance_expected,
"required_evidence": (
"An independently anchored artifact digest or signed installer receipt that binds each installed payload to the signed release catalog.",
),
}, },
"runtime_activation": { "runtime_activation": {
"checked": False, "checked": False,
@@ -807,6 +860,7 @@ def review_installed_composition(
"collected_at": _iso_datetime(collected_at), "collected_at": _iso_datetime(collected_at),
"evaluated_at": _iso_datetime(now), "evaluated_at": _iso_datetime(now),
"verification_mode": effective_verification_mode, "verification_mode": effective_verification_mode,
"catalog_trusted": catalog_trusted is True,
"freshness_window_seconds": int(MAX_LOCAL_OBSERVATION_AGE.total_seconds()), "freshness_window_seconds": int(MAX_LOCAL_OBSERVATION_AGE.total_seconds()),
}, },
} }
@@ -949,6 +1003,9 @@ def review_boundary_evidence(
) )
installed_digest = installed_review.evidence_sha256 installed_digest = installed_review.evidence_sha256
installed_scope = installed_review.proof_scope.get("installed_artifacts", {}) installed_scope = installed_review.proof_scope.get("installed_artifacts", {})
installed_origin_scope = installed_review.proof_scope.get(
"installed_release_origin", {}
)
if ( if (
installed_digest is None installed_digest is None
or evidence.get("installed_evidence_sha256") != installed_digest or evidence.get("installed_evidence_sha256") != installed_digest
@@ -962,6 +1019,15 @@ def review_boundary_evidence(
("assessment.installed_composition", "assessment.external_proof"), ("assessment.installed_composition", "assessment.external_proof"),
) )
) )
if installed_origin_scope.get("valid") is not True:
findings.append(
EvidenceFinding(
"blocker",
"boundary_installed_release_origin_untrusted",
"Boundary evidence requires an independently anchored digest or signed installer receipt for every installed release artifact; local PEP 610 and RECORD metadata are insufficient.",
("assessment.installed_composition", "assessment.external_proof"),
)
)
issued_at = _datetime(evidence.get("issued_at")) issued_at = _datetime(evidence.get("issued_at"))
expires_at = _datetime(evidence.get("expires_at")) expires_at = _datetime(evidence.get("expires_at"))
@@ -1295,19 +1361,19 @@ def _source_provenance(distribution: metadata.Distribution) -> dict[str, str]:
try: try:
raw = distribution.read_text("direct_url.json") raw = distribution.read_text("direct_url.json")
except Exception: except Exception:
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
if raw is None: if raw is None:
return {"kind": "index-or-unknown"} return _local_source_metadata("index-or-unknown")
if not isinstance(raw, str) or len(raw) > MAX_DIRECT_URL_BYTES: if not isinstance(raw, str) or len(raw) > MAX_DIRECT_URL_BYTES:
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
if len(raw.encode("utf-8")) > MAX_DIRECT_URL_BYTES: if len(raw.encode("utf-8")) > MAX_DIRECT_URL_BYTES:
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
try: try:
direct_url = json.loads(raw) direct_url = json.loads(raw)
except (TypeError, json.JSONDecodeError): except (TypeError, json.JSONDecodeError):
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
if not isinstance(direct_url, dict): if not isinstance(direct_url, dict):
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
raw_url = direct_url.get("url") raw_url = direct_url.get("url")
if ( if (
not isinstance(raw_url, str) not isinstance(raw_url, str)
@@ -1315,13 +1381,13 @@ def _source_provenance(distribution: metadata.Distribution) -> dict[str, str]:
or len(raw_url) > 2048 or len(raw_url) > 2048
or any(character.isspace() or ord(character) < 32 for character in raw_url) or any(character.isspace() or ord(character) < 32 for character in raw_url)
): ):
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
try: try:
parsed_url = urlsplit(raw_url) parsed_url = urlsplit(raw_url)
except ValueError: except ValueError:
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
if parsed_url.password is not None or parsed_url.query or parsed_url.fragment: if parsed_url.password is not None or parsed_url.query or parsed_url.fragment:
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
vcs_info = direct_url.get("vcs_info") vcs_info = direct_url.get("vcs_info")
directory_info = direct_url.get("dir_info") directory_info = direct_url.get("dir_info")
archive_info = direct_url.get("archive_info") archive_info = direct_url.get("archive_info")
@@ -1335,10 +1401,10 @@ def _source_provenance(distribution: metadata.Distribution) -> dict[str, str]:
if value is not None if value is not None
) )
if len(provenance_forms) != 1: if len(provenance_forms) != 1:
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
if isinstance(vcs_info, dict): if isinstance(vcs_info, dict):
if vcs_info.get("vcs") != "git": if vcs_info.get("vcs") != "git":
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
commit = str(vcs_info.get("commit_id") or "").lower() commit = str(vcs_info.get("commit_id") or "").lower()
requested_revision = vcs_info.get("requested_revision") requested_revision = vcs_info.get("requested_revision")
if ( if (
@@ -1359,8 +1425,8 @@ def _source_provenance(distribution: metadata.Distribution) -> dict[str, str]:
) )
) )
): ):
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
return {"kind": "vcs-commit", "commit": commit} return _local_source_metadata("vcs-commit", commit=commit)
if isinstance(archive_info, dict): if isinstance(archive_info, dict):
if ( if (
parsed_url.scheme not in {"file", "https"} parsed_url.scheme not in {"file", "https"}
@@ -1370,7 +1436,7 @@ def _source_provenance(distribution: metadata.Distribution) -> dict[str, str]:
and (not parsed_url.hostname or parsed_url.username) and (not parsed_url.hostname or parsed_url.username)
) )
): ):
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
hashes = archive_info.get("hashes") hashes = archive_info.get("hashes")
digest = hashes.get("sha256") if isinstance(hashes, dict) else None digest = hashes.get("sha256") if isinstance(hashes, dict) else None
if digest is None: if digest is None:
@@ -1379,8 +1445,8 @@ def _source_provenance(distribution: metadata.Distribution) -> dict[str, str]:
digest = legacy.removeprefix("sha256=") digest = legacy.removeprefix("sha256=")
digest_text = str(digest or "").lower() digest_text = str(digest or "").lower()
if SHA256_PATTERN.fullmatch(digest_text): if SHA256_PATTERN.fullmatch(digest_text):
return {"kind": "archive", "sha256": digest_text} return _local_source_metadata("archive", sha256=digest_text)
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
if isinstance(directory_info, dict): if isinstance(directory_info, dict):
editable = directory_info.get("editable", False) editable = directory_info.get("editable", False)
if ( if (
@@ -1388,9 +1454,142 @@ def _source_provenance(distribution: metadata.Distribution) -> dict[str, str]:
or not parsed_url.path or not parsed_url.path
or not isinstance(editable, bool) or not isinstance(editable, bool)
): ):
return {"kind": "malformed-direct-url"} return _local_source_metadata("malformed-direct-url")
return {"kind": "editable-local" if editable else "local-directory"} return _local_source_metadata(
return {"kind": "malformed-direct-url"} "editable-local" if editable else "local-directory"
)
return _local_source_metadata("malformed-direct-url")
def _local_source_metadata(kind: str, **values: str) -> dict[str, str]:
return {"basis": "local-pep610-metadata", "kind": kind, **values}
def _read_record_entries(
*,
distribution: metadata.Distribution,
distribution_root: Path,
max_entries: int,
) -> tuple[tuple[RecordEntry, ...], int, str]:
"""Read RECORD itself without importlib's missing-path filtering.
``Distribution.files`` is an interpreted convenience API rather than the
raw declaration being attested. Reading the metadata file directly lets
this collector retain missing and malformed declarations and enforce byte
and row limits before resolving any untrusted payload path.
"""
metadata_root_value = getattr(distribution, "_path", None)
if metadata_root_value is None:
return (), 0, "unavailable"
try:
metadata_root = Path(os.fspath(metadata_root_value))
if metadata_root.is_symlink():
return (), 0, "unavailable"
resolved_metadata_root = metadata_root.resolve()
except (OSError, TypeError, ValueError):
return (), 0, "unavailable"
if not _is_relative_to(resolved_metadata_root, distribution_root):
return (), 0, "unavailable"
record_path = resolved_metadata_root / "RECORD"
try:
if record_path.is_symlink():
return (), 0, "unavailable"
encoded, exceeded, stable = _bounded_regular_file_bytes(
record_path,
max_bytes=MAX_RECORD_BYTES,
)
except OSError:
return (), 0, "unavailable"
if exceeded:
return (), 0, "limit-exceeded"
if not stable:
return (), 1, "unavailable"
try:
text = encoded.decode("utf-8")
except UnicodeDecodeError:
return (), 1, "unavailable"
entries: list[RecordEntry] = []
malformed_rows = 0
declared_paths: set[str] = set()
try:
rows = csv.reader(io.StringIO(text, newline=""))
for row in rows:
if len(entries) + malformed_rows >= max_entries:
return (), 0, "limit-exceeded"
if len(row) != 3:
malformed_rows += 1
continue
raw_path, raw_hash, raw_size = row
if (
not raw_path
or len(raw_path) > MAX_RECORD_PATH_CHARACTERS
or any(ord(character) < 32 for character in raw_path)
or (raw_size and (len(raw_size) > 20 or not raw_size.isdecimal()))
):
malformed_rows += 1
continue
declared_path = str(PurePosixPath(raw_path.replace("\\", "/")))
if declared_path in declared_paths:
malformed_rows += 1
continue
declared_paths.add(declared_path)
if raw_hash:
hash_mode, separator, hash_value = raw_hash.partition("=")
if not separator or not hash_mode or not hash_value:
hash_mode = raw_hash
hash_value = ""
else:
hash_mode = None
hash_value = None
entries.append(
RecordEntry(
path=raw_path,
hash_mode=hash_mode,
hash_value=hash_value,
size=int(raw_size) if raw_size else None,
)
)
except (csv.Error, ValueError):
return (), 1, "unavailable"
return tuple(entries), malformed_rows, "ok"
def _bounded_regular_file_bytes(
path: Path,
*,
max_bytes: int,
) -> tuple[bytes, bool, bool]:
flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0)
descriptor = os.open(path, flags)
chunks: list[bytes] = []
total = 0
try:
initial = os.fstat(descriptor)
if not stat.S_ISREG(initial.st_mode):
raise OSError("RECORD metadata is not a regular file")
if initial.st_size > max_bytes:
return b"", True, True
while total <= max_bytes:
chunk = os.read(descriptor, min(1024 * 1024, max_bytes + 1 - total))
if not chunk:
break
chunks.append(chunk)
total += len(chunk)
final = os.fstat(descriptor)
finally:
os.close(descriptor)
exceeded = total > max_bytes or final.st_size > max_bytes
stable = (
not exceeded
and total == initial.st_size
and final.st_size == initial.st_size
and (final.st_dev, final.st_ino) == (initial.st_dev, initial.st_ino)
and final.st_mtime_ns == initial.st_mtime_ns
and final.st_ctime_ns == initial.st_ctime_ns
)
return b"".join(chunks), exceeded, stable
def _record_integrity( def _record_integrity(
@@ -1407,53 +1606,84 @@ def _record_integrity(
"missing_file_count": 0, "missing_file_count": 0,
"mismatched_file_count": 0, "mismatched_file_count": 0,
} }
try:
files = tuple(distribution.files or ())
except Exception:
files = ()
if not files:
return {"status": "unavailable", **counts}
if len(files) > MAX_DISTRIBUTION_FILES or len(files) > budget.remaining_files:
budget.remaining_files = 0
return {"status": "limit-exceeded", **counts}
budget.remaining_files -= len(files)
try: try:
distribution_root = Path(distribution.locate_file("")).resolve() distribution_root = Path(distribution.locate_file("")).resolve()
installation_root = Path(sys.prefix).resolve() installation_root = Path(sys.prefix).resolve()
except OSError: except Exception:
return {"status": "unavailable", **counts} return {"status": "unavailable", **counts}
if not _trusted_distribution_root(distribution_root): if not _trusted_distribution_root(distribution_root):
return {"status": "unavailable", **counts} return {"status": "unavailable", **counts}
entries, malformed_rows, record_status = _read_record_entries(
distribution=distribution,
distribution_root=distribution_root,
max_entries=min(MAX_DISTRIBUTION_FILES, budget.remaining_files),
)
if record_status == "limit-exceeded":
budget.remaining_files = 0
return {"status": "limit-exceeded", **counts}
if record_status != "ok":
counts["unverifiable_file_count"] = malformed_rows
return {"status": "unavailable", **counts}
record_file_count = len(entries) + malformed_rows
if record_file_count == 0:
return {"status": "unavailable", **counts}
budget.remaining_files -= record_file_count
counts["unverifiable_file_count"] = malformed_rows
declared_scripts = _declared_distribution_scripts(distribution) declared_scripts = _declared_distribution_scripts(distribution)
owner_prefix = package_name.replace("-", "_") owner_prefix = package_name.replace("-", "_")
resolved_entries: list[tuple[object, Path | None]] = [ resolved_entries: list[tuple[RecordEntry, Path | None]] = [
( (
package_path, entry,
_resolve_record_path( _resolve_record_path(
distribution=distribution, distribution=distribution,
package_path=package_path, package_path=entry.path,
distribution_root=distribution_root, distribution_root=distribution_root,
installation_root=installation_root, installation_root=installation_root,
owner_prefix=owner_prefix, owner_prefix=owner_prefix,
declared_scripts=declared_scripts, declared_scripts=declared_scripts,
), ),
) )
for package_path in files for entry in entries
] ]
resolved_path_counts: dict[Path, int] = {}
for _, resolved_path in resolved_entries:
if resolved_path is not None:
resolved_path_counts[resolved_path] = (
resolved_path_counts.get(resolved_path, 0) + 1
)
duplicate_resolved_paths = {
path for path, count in resolved_path_counts.items() if count > 1
}
counts["unverifiable_file_count"] += sum(
resolved_path_counts[path] for path in duplicate_resolved_paths
)
hashed_payload_paths = { hashed_payload_paths = {
resolved_path resolved_path
for package_path, resolved_path in resolved_entries for entry, resolved_path in resolved_entries
if resolved_path is not None if resolved_path is not None
and getattr(getattr(package_path, "hash", None), "mode", None) == "sha256" and resolved_path not in duplicate_resolved_paths
and entry.hash_mode == "sha256"
} }
total_hashed_bytes = 0 total_hashed_bytes = 0
limit_exceeded = False limit_exceeded = False
for package_path, resolved_path in resolved_entries: for entry, resolved_path in resolved_entries:
hash_info = getattr(package_path, "hash", None)
if resolved_path is None: if resolved_path is None:
counts["unverifiable_file_count"] += 1 counts["unverifiable_file_count"] += 1
continue continue
if hash_info is None: if resolved_path in duplicate_resolved_paths:
continue
try:
file_stat = resolved_path.lstat()
except OSError:
counts["missing_file_count"] += 1
continue
if not stat.S_ISREG(file_stat.st_mode):
counts["missing_file_count"] += 1
continue
if entry.size is not None and file_stat.st_size != entry.size:
counts["mismatched_file_count"] += 1
continue
if entry.hash_mode is None:
if _permitted_unhashed_record_path( if _permitted_unhashed_record_path(
resolved_path, resolved_path,
distribution_root=distribution_root, distribution_root=distribution_root,
@@ -1468,17 +1698,9 @@ def _record_integrity(
else: else:
counts["unverifiable_file_count"] += 1 counts["unverifiable_file_count"] += 1
continue continue
if getattr(hash_info, "mode", None) != "sha256": if entry.hash_mode != "sha256":
counts["unverifiable_file_count"] += 1 counts["unverifiable_file_count"] += 1
continue continue
try:
file_stat = resolved_path.stat()
except OSError:
counts["missing_file_count"] += 1
continue
if not stat.S_ISREG(file_stat.st_mode):
counts["missing_file_count"] += 1
continue
remaining_distribution_bytes = ( remaining_distribution_bytes = (
MAX_HASHED_DISTRIBUTION_BYTES - total_hashed_bytes MAX_HASHED_DISTRIBUTION_BYTES - total_hashed_bytes
) )
@@ -1490,7 +1712,7 @@ def _record_integrity(
if allowed_bytes <= 0 or file_stat.st_size > allowed_bytes: if allowed_bytes <= 0 or file_stat.st_size > allowed_bytes:
limit_exceeded = True limit_exceeded = True
continue continue
expected = _record_hash_bytes(str(getattr(hash_info, "value", ""))) expected = _record_hash_bytes(entry.hash_value or "")
if expected is None: if expected is None:
counts["unverifiable_file_count"] += 1 counts["unverifiable_file_count"] += 1
continue continue
@@ -1541,7 +1763,7 @@ def _resolve_record_path(
if unresolved_path.is_symlink(): if unresolved_path.is_symlink():
return None return None
resolved_path = unresolved_path.resolve() resolved_path = unresolved_path.resolve()
except OSError: except (OSError, TypeError, ValueError):
return None return None
has_parent_traversal = ".." in record_path.parts has_parent_traversal = ".." in record_path.parts
if not has_parent_traversal: if not has_parent_traversal:
@@ -1704,10 +1926,49 @@ def _active_authority_keys(
seen: set[str] = set() seen: set[str] = set()
public_key_owners: dict[bytes, str] = {} public_key_owners: dict[bytes, str] = {}
duplicate_public_keys: set[bytes] = set() duplicate_public_keys: set[bytes] = set()
for item in authority_keyring.get("keys", []): raw_keys = authority_keyring.get("keys")
if not isinstance(raw_keys, list):
return {}, (
EvidenceFinding(
"blocker",
"boundary_authority_key_invalid",
"Proof-authority keyring does not contain a valid key list.",
("assessment.external_proof",),
),
)
for item in raw_keys:
if not isinstance(item, dict): if not isinstance(item, dict):
findings.append(
EvidenceFinding(
"blocker",
"boundary_authority_key_invalid",
"Proof-authority keyring contains a malformed key entry.",
("assessment.external_proof",),
)
)
continue continue
key_id = str(item.get("key_id") or "") key_id = str(item.get("key_id") or "")
status = item.get("status")
raw_allowed_scopes = item.get("allowed_scopes")
if (
len(key_id) > 160
or OPAQUE_ID_PATTERN.fullmatch(key_id) is None
or status not in {"active", "next", "revoked", "disabled", "retired"}
or not isinstance(raw_allowed_scopes, list)
or not raw_allowed_scopes
or any(not isinstance(scope, str) for scope in raw_allowed_scopes)
or len(raw_allowed_scopes) != len(set(raw_allowed_scopes))
or any(scope not in PROOF_REQUIREMENTS for scope in raw_allowed_scopes)
):
findings.append(
EvidenceFinding(
"blocker",
"boundary_authority_key_invalid",
"Proof-authority keyring contains a malformed key entry.",
("assessment.external_proof",),
)
)
continue
if key_id in seen: if key_id in seen:
findings.append( findings.append(
EvidenceFinding( EvidenceFinding(
@@ -1725,6 +1986,36 @@ def _active_authority_keys(
) )
Ed25519PublicKey.from_public_bytes(public_key) Ed25519PublicKey.from_public_bytes(public_key)
except ValueError: except ValueError:
findings.append(
EvidenceFinding(
"blocker",
"boundary_authority_key_invalid",
"Proof-authority keyring contains invalid Ed25519 public-key material.",
("assessment.external_proof",),
)
)
continue
raw_not_before = item.get("not_before")
raw_not_after = item.get("not_after")
not_before = _datetime(raw_not_before)
not_after = _datetime(raw_not_after)
if (
(raw_not_before is not None and not_before is None)
or (raw_not_after is not None and not_after is None)
or (
not_before is not None
and not_after is not None
and not_before >= not_after
)
):
findings.append(
EvidenceFinding(
"blocker",
"boundary_authority_key_interval_invalid",
"Proof-authority keyring contains an invalid or empty key-validity interval.",
("assessment.external_proof",),
)
)
continue continue
previous_owner = public_key_owners.get(public_key) previous_owner = public_key_owners.get(public_key)
if previous_owner is not None and previous_owner != key_id: if previous_owner is not None and previous_owner != key_id:
@@ -1741,10 +2032,8 @@ def _active_authority_keys(
result.pop(previous_owner, None) result.pop(previous_owner, None)
continue continue
public_key_owners[public_key] = key_id public_key_owners[public_key] = key_id
if item.get("status") not in {"active", "next"}: if status not in {"active", "next"}:
continue continue
not_before = _datetime(item.get("not_before"))
not_after = _datetime(item.get("not_after"))
if (not_before is not None and verification_time < not_before) or ( if (not_before is not None and verification_time < not_before) or (
not_after is not None and verification_time >= not_after not_after is not None and verification_time >= not_after
): ):
@@ -1761,9 +2050,12 @@ def _active_authority_keys(
continue continue
result[key_id] = ( result[key_id] = (
public_key, public_key,
frozenset(str(value) for value in item.get("allowed_scopes", [])), frozenset(raw_allowed_scopes),
) )
return result, tuple(findings) # A supplied authority keyring is one trust root. Selecting usable members
# out of a malformed set would let a valid-looking sibling conceal an
# invalid interval, duplicate identity, or ambiguous key assignment.
return ({} if findings else result), tuple(findings)
def _record_change( def _record_change(
@@ -1810,7 +2102,20 @@ def _unchecked_installed_scope() -> dict[str, Any]:
), ),
}, },
"installed_record_integrity": {"checked": False, "valid": None}, "installed_record_integrity": {"checked": False, "valid": None},
"installed_source_provenance": {"checked": False, "valid": None}, "installed_source_provenance": {
"checked": False,
"valid": None,
"basis": "local-pep610-metadata",
"release_origin_bound": False,
},
"installed_release_origin": {
"checked": False,
"valid": None,
"release_origin_bound": False,
"required_evidence": (
"An independently anchored artifact digest or signed installer receipt bound to the signed release catalog.",
),
},
"runtime_activation": { "runtime_activation": {
"checked": False, "checked": False,
"valid": None, "valid": None,