Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ The following sequence is mandatory and ordered. Candidate bytes are inert: neve

1. Obtain explicit live-upload authority naming the repo/release/candidate. Record a non-secret authority reference. Fetch the reviewed warehouse PR, confirm the exact full commit SHA, clean worktree, and exact `origin`.
2. Confirm GitHub host/account/effective write permission and exact repo/release numeric+node identities. Authentication reports are suppressed to avoid credential metadata.
3. Independently verify the allowlisted immutable forge repository/workflow/ref/full SHA, candidate release/tag/ID/node ID/immutable state, canonical envelope, source manifest, checksums, staging asset-list digest, raw attestation bundle, and every inert asset size/digest. The preflight command itself freshly queries `github.com`, downloads the staging artifact and every candidate-release asset, compares those bytes with the local candidate, and reruns the raw protocol/attestation verifier; it never trusts an operator-supplied verification record. The warehouse consumes the exact audited promotion implementation pinned by [`protocol/forge-promotion-envelope-v1.json`](protocol/forge-promotion-envelope-v1.json) and separately requires the candidate issuer to contain or descend from the independently audited component-policy remediation pinned by [`protocol/forge-component-policy-v1.json`](protocol/forge-component-policy-v1.json). The exact remediated component/build schemas and validator blobs must also match that second pin. The warehouse does not redefine forge canonicalization, and a pre-remediation, unrelated, dirty, or regressed issuer is rejected.
3. Independently verify the allowlisted immutable forge repository/workflow/ref/full SHA, candidate release/tag/ID/node ID/immutable state, canonical envelope, source manifest, checksums, staging asset-list digest, raw attestation bundle, and every inert asset size/digest. The preflight command itself freshly queries `github.com`, downloads the staging artifact and every candidate-release asset, compares those bytes with the local candidate, and reruns the raw protocol/attestation verifier; it never trusts an operator-supplied verification record. The warehouse consumes the exact audited promotion implementation pinned by [`protocol/forge-promotion-envelope-v1.json`](protocol/forge-promotion-envelope-v1.json) and separately requires the live candidate issuer to descend from both the independently audited component-policy remediation and the Phase-6 policy revision pinned by [`protocol/forge-component-policy-v1.json`](protocol/forge-component-policy-v1.json). The exact current component/build schemas and validator blobs must match that second pin. Its older audited fixture blobs remain in an explicit `testFixture` lane used only with the noncryptographic test marker; they cannot authorize a live candidate. The warehouse does not redefine forge canonicalization, and a pre-revision, unrelated, dirty, or regressed live issuer is rejected.
4. Capture the complete FR-039 snapshot through all pages. It binds repository/release/body identities plus every legacy asset ID/node ID/name/state/size/API and independent download digest/API URL/download URL/content type/timestamps. A partial name-only inventory is invalid.
5. Run complete-set zero-write preflight. It reports every absent, identical no-op, and conflicting candidate name. Any conflict creates no release write, journal, catalog state, or stable index change.
6. Bind explicit authority, exact proposal, canonical candidate/envelope/list, complete snapshot hash, and intended warning-body digest into a mode-`0600` receipt. Type the exact receipt-hash confirmation.
Expand Down
24 changes: 21 additions & 3 deletions protocol/forge-component-policy-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"schemaVersion": "forge-component-policy-pin-v1",
"repository": "acedward/midnight-binary-forge",
"minimumCommitSha": "ddd2838d0226eeaaca8f7a42ad82cba1a132bbfe",
"policyRevisionCommitSha": "0170c0c4dbad8707e3b34e4daf029d3377d47077",
"audit": {
"path": "audits/00002-binary-publication-pipeline-phase1-remediation.md",
"sha256": "8277a72c892d9d8ea87e3276cd6a75f1470ec1b282ed8495a7d9add3b4a8db72",
Expand All @@ -15,11 +16,28 @@
},
{
"path": "schema/build-set-v1.schema.json",
"sha256": "0800a100413b6866b12530af74421d78ff800514f79ad124ef2ffedcbad31d87"
"sha256": "77a08d5397d27edb45b08e5f6a6229bb6f195ec8117c5c8cae06a8901f690c78"
},
{
"path": "scripts/validate_catalog.py",
"sha256": "a0e5b6d1787db3faf1d9e40280682f7acca882b9706477126e51bffc1b1832d3"
"sha256": "4b4c6215fb852dcbdfb12b899a724642747e143cb6a2527605d50c261ed49a06"
}
]
],
"testFixture": {
"commitSha": "ddd2838d0226eeaaca8f7a42ad82cba1a132bbfe",
"files": [
{
"path": "schema/component-v1.schema.json",
"sha256": "e5073ce9367af76e43ee1eb675a73a819d4940dcd88619c5b5fc4e92bd28b982"
},
{
"path": "schema/build-set-v1.schema.json",
"sha256": "0800a100413b6866b12530af74421d78ff800514f79ad124ef2ffedcbad31d87"
},
{
"path": "scripts/validate_catalog.py",
"sha256": "a0e5b6d1787db3faf1d9e40280682f7acca882b9706477126e51bffc1b1832d3"
}
]
}
}
5 changes: 5 additions & 0 deletions scripts/promotion_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,11 @@ def validate_component_policy_checkout(record: dict[str, Any], checkout: Path) -
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
expect(ancestry.returncode == 0, "candidate issuer does not descend from audited component-policy remediation")
revision_ancestry = subprocess.run(
["git", "-C", os.fspath(checkout), "merge-base", "--is-ancestor", pin["policyRevisionCommitSha"], head],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
expect(revision_ancestry.returncode == 0, "candidate issuer does not descend from Phase-6 component-policy revision")
for item in pin["files"]:
path = checkout / item["path"]
expect(path.is_file() and not path.is_symlink() and sha256_file(path) == item["sha256"], f"component-policy blob mismatch: {item['path']}")
Expand Down
27 changes: 17 additions & 10 deletions scripts/verify-candidate
Original file line number Diff line number Diff line change
Expand Up @@ -177,19 +177,26 @@ def main() -> int:
fail("forge component-policy checkout origin is not allowlisted")
minimum_component_commit = component_pin["minimumCommitSha"]
if args.test_noncryptographic_fixture:
if component_head != minimum_component_commit:
fail("test component-policy checkout must be the exact independently audited remediation pin")
fixture = component_pin["testFixture"]
if component_head != fixture["commitSha"]:
fail("test component-policy checkout must be the exact nonauthorizing fixture pin")
pinned_component_files = fixture["files"]
else:
if component_head != claims["issuer"]["commitSha"]:
fail("candidate issuer commit is not the exact component-policy checkout HEAD")
ancestry = subprocess.run(
["git", "-C", os.fspath(component_checkout), "merge-base", "--is-ancestor", minimum_component_commit, component_head],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
if ancestry.returncode != 0:
fail("candidate issuer does not descend from the independently audited component-policy remediation")
for item in component_pin["files"]:
for required_ancestor, label in (
(minimum_component_commit, "independently audited component-policy remediation"),
(component_pin["policyRevisionCommitSha"], "Phase-6 component-policy revision"),
):
ancestry = subprocess.run(
["git", "-C", os.fspath(component_checkout), "merge-base", "--is-ancestor", required_ancestor, component_head],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
if ancestry.returncode != 0:
fail(f"candidate issuer does not descend from the {label}")
pinned_component_files = component_pin["files"]
for item in pinned_component_files:
path = component_checkout / item["path"]
if not path.is_file() or path.is_symlink() or sha256(path) != item["sha256"]:
fail(f"component-policy pinned blob mismatch: {item['path']}")
Expand Down
3 changes: 3 additions & 0 deletions tests/test_promotion.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,9 @@ def test_prerequisite_live_recheck_rejects_cross_state_checkout(self) -> None:

def test_component_policy_pin_rejects_pre_pin_unrelated_and_regressed_checkouts(self) -> None:
pin = load_json(ROOT / "protocol/forge-component-policy-v1.json")
self.assertEqual(pin["testFixture"]["commitSha"], pin["minimumCommitSha"])
self.assertEqual({row["path"] for row in pin["testFixture"]["files"]}, {row["path"] for row in pin["files"]})
self.assertNotEqual(pin["policyRevisionCommitSha"], pin["minimumCommitSha"])
_, verification = self.evidence(canonical_bytes(self.envelope([], self.fixture_proposal([]))))
with tempfile.TemporaryDirectory() as temporary:
checkout = Path(temporary) / "component"
Expand Down