From 03d208f5e5df7e07514f71226af01730884d8e50 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Fri, 13 Nov 2026 10:52:11 +0000 Subject: [PATCH] Bind the Server 3.8 cooperative Composer build recipe --- qualification/README.md | 8 ++++++ qualification/composer-container-trust.json | 16 +++++++++++ tests/test_composer_container_trust.py | 31 +++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/qualification/README.md b/qualification/README.md index a18ba21..9b192ec 100644 --- a/qualification/README.md +++ b/qualification/README.md @@ -191,6 +191,14 @@ The `sdk-php-cooperative-server` entry in `composer-container-trust.json` applies only to its registered Compose request and reviewed Server recipes. It supplies no trust for other build or runtime paths. +Its additional Server 3.8.0 recipe comes from published source +`28f084513235f6647e740c4b3d9e3aace7a323b7`. It binds the fixed APT parent +Dockerfile, lockfile, copied/executed `docker/apt/require-fixed-apt.sh` and +`docker/apt/parent.v1.json` proof alongside the existing preparation inputs. +The older recipe remains available. Source matching alone does not qualify +the caller: its clean predecessor, live in-container verifier and actual +registered Compose request remain required. + The container control runs the actual digest-pinned Composer image against a local synthetic TLS registry. The ambient store accepts its synthetic blocked root. The verified clean store refuses before an HTTP request. Wrong bundle diff --git a/qualification/composer-container-trust.json b/qualification/composer-container-trust.json index 90a9c3e..71faf1a 100644 --- a/qualification/composer-container-trust.json +++ b/qualification/composer-container-trust.json @@ -28,6 +28,22 @@ }, "composer_image": "composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee", "composer_stage": "composer" + }, + { + "files": { + "Dockerfile": "d70f15b575b98f0cfd6c5b6abc95a9d53b9560b03aeed9c1996fec46d4d9d41a", + ".dockerignore": "9ae64622b015a660f64c594f6948540e224255ffe64ab1a766ee0633f1d5f5c4", + "Dockerfile.dockerignore": "e7bf60d34720f0639193f44ce715d74165bba3355c0dacb5445fe5e82c64ba33", + "composer.json": "e22b3c192ac024781142c44a6cc2c81deb6abdd364cf84cba5f15381be98c4f5", + "composer.lock": "c622d912e8367c8a1bc5a532adf302c1c28d4b2479bbdac043eb2b2ffbf78bfc", + "scripts/ci/WorkflowPackageAuthority.php": "24960bb0afe9078ee0522c81f6be45aa85122e76655fe0a1dfabd5f4ee4de9a3", + "scripts/ci/resolve-workflow-package-authority.php": "53ae520195192c20efe6531c3c2a50aef599ff0dcf99ebaf0933cbb6b5a04f86", + "scripts/ci/prepare-release-workflow-composer-metadata.php": "a96f9a3d0472646de3476fc1a07ba4754b5832f8cf8739223b16ca8d57a122fd", + "docker/apt/require-fixed-apt.sh": "082e4bcf3aebaf8da74658c2f15c4b6df259e0cb8f2b09e4e16a737e223a94a3", + "docker/apt/parent.v1.json": "09078d7166cd57d8781ef344f5f7b2f69712ace54aa867d320d99954f039cd60" + }, + "composer_image": "composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee", + "composer_stage": "composer" } ] } diff --git a/tests/test_composer_container_trust.py b/tests/test_composer_container_trust.py index 1865f9e..45348dc 100644 --- a/tests/test_composer_container_trust.py +++ b/tests/test_composer_container_trust.py @@ -120,6 +120,37 @@ def test_unrelated_product_source_can_advance_without_changing_the_recipe(self): self.commit = self.git("rev-parse", "HEAD") self.assertEqual(self.commit, self.check()[1]["source_commit"]) + def test_a_successor_recipe_binds_its_added_executed_helper(self): + helper = self.source / "docker/apt/require-fixed-apt.sh" + helper.parent.mkdir(parents=True) + helper.write_text("#!/bin/sh\nexit 0\n") + self.dockerfile += "COPY docker/apt/require-fixed-apt.sh /fixed-apt.sh\nRUN sh /fixed-apt.sh\n" + (self.source / "Dockerfile").write_text(self.dockerfile) + self.git("add", ".") + self.git("commit", "--quiet", "-m", "successor with fixed APT prerequisite") + self.commit = self.git("rev-parse", "HEAD") + with self.assertRaisesRegex(ContainerTrustRefused, "no uniquely approved"): + self.check() + + successor = deepcopy(self.catalog["requests"]["fixture"]["approved_recipes"][0]) + successor["files"]["Dockerfile"] = sha256(self.dockerfile.encode()) + successor["files"]["docker/apt/require-fixed-apt.sh"] = sha256(helper.read_bytes()) + self.catalog["requests"]["fixture"]["approved_recipes"].append(successor) + self.assertEqual(self.commit, self.check()[1]["source_commit"]) + + helper.write_text("#!/bin/sh\nexit 1\n") + self.git("add", ".") + self.git("commit", "--quiet", "-m", "changed helper without recipe review") + self.commit = self.git("rev-parse", "HEAD") + with self.assertRaisesRegex(ContainerTrustRefused, "reviewed recipe files"): + self.check() + + self.git("rm", "docker/apt/require-fixed-apt.sh") + self.git("commit", "--quiet", "-m", "missing helper") + self.commit = self.git("rev-parse", "HEAD") + with self.assertRaisesRegex(ContainerTrustRefused, "required recipe file is missing"): + self.check() + def test_a_new_dockerfile_specific_context_policy_requires_recipe_review(self): (self.source / "Dockerfile.dockerignore").write_text("# Changes the actual Docker context\n") self.git("add", "Dockerfile.dockerignore")