From 3ca536dea490a2884e64753d511dc18950b04c73 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Fri, 13 Nov 2026 12:50:20 +0000 Subject: [PATCH 1/3] Bind the SDK unit and Carbon PHP runtime sources --- qualification/composer-container-trust.json | 83 +++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/qualification/composer-container-trust.json b/qualification/composer-container-trust.json index 366c7a5..30feb6d 100644 --- a/qualification/composer-container-trust.json +++ b/qualification/composer-container-trust.json @@ -209,6 +209,89 @@ "mode": 420 } } + }, + "sdk-php-unit-ci": { + "target": "sdk-php", + "matrix": { + "php": [ + "8.1", + "8.2", + "8.4" + ] + }, + "workflows": { + "ci.yml": [ + "test", + "carbon-2" + ] + }, + "commands": [ + "bash \"$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh\" \"${{ matrix.php }}\"", + "bash \"$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh\" \"8.1\"" + ], + "environment": [ + "SDK_PHP_CI_IMAGE", + "SDK_PHP_CI_ROOT", + "SDK_PHP_CI_STATE" + ], + "absent_files": [ + "docker/php-ci/Dockerfile.dockerignore", + "docker/php-ci/Dockerfile.81.dockerignore" + ], + "files": { + ".dockerignore": { + "sha256": "77afbcb7937677f268f626472f8cf3fef5c8a7384e743df09e081d38279383e1", + "mode": 420 + }, + "docker/php-ci/Dockerfile": { + "sha256": "81d23fbbee7e4ee97ca14df9d17982aa664335b94499c5019662259fe0387eaa", + "mode": 420 + }, + "docker/php-ci/Dockerfile.81": { + "sha256": "c16c2c06252b1b179ccab41876ce76a30febf8a6fd5d92234a2bbed54d7be222", + "mode": 420 + }, + "docker/php-ci/ci-defaults.ini": { + "sha256": "656a644d0a5392488dd843614c40c763a950294e4af6eee124c919930a21a72f", + "mode": 420 + }, + "docker/php-ci/verify-php-trust.php": { + "sha256": "529c282e7422202bf25565431a6391de388f86dfe5bfd6c75cf21daa56ffa75e", + "mode": 420 + }, + "scripts/ci/php-container-images.json": { + "sha256": "389247efc6e551b553314bfe64f66aaef3a96c0bf659e6f78322b84b0dc9860b", + "mode": 420 + }, + "scripts/ci/setup-php-container.sh": { + "sha256": "37dd931f07749ebd29ec6700816e4c8b42ee16d52ae05f426647def250867553", + "mode": 493 + }, + "scripts/ci/run-php-container.sh": { + "sha256": "96727d850846f1bd0b96468e9de8d34c6f6131896b5a80157dc1b71ec39b9392", + "mode": 493 + }, + "tests/ci/test_php_container_tls.py": { + "sha256": "94decbb86d8b6112ba864f927463a41462fb6455707c19b6127f217007d82423", + "mode": 420 + }, + "docker/brotli/install-vendor.sh": { + "sha256": "d7eb8b2a3e0af1c3072a56bcb81632b22f061f211d8fe5e57ee901c8bf94c92f", + "mode": 420 + }, + "docker/brotli/verify-runtime.php": { + "sha256": "580cc465e349f3872eca592355294e7320fe7f1a4560ff5d4c83d5900921c00e", + "mode": 420 + }, + "docker/brotli/verify-system-brotli.c": { + "sha256": "3353927d206aaf5e63157e46be715c6cbee46bac777e29f8f9e25c4c1f656667", + "mode": 420 + }, + "docker/brotli/version": { + "sha256": "e064953e075ddb99fb7b48ad29e215f2dfa075812c127eb1d6ab680e9920fba2", + "mode": 420 + } + } } } } From 8e7e24cc4217942f35259fa6b288eb4278781fe0 Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Fri, 13 Nov 2026 13:09:41 +0000 Subject: [PATCH 2/3] Confine SDK runtime commands to their individual jobs --- qualification/composer-container-trust.json | 76 ++++++++++- tests/fixtures/sdk-php-unit-runtime.yml | 138 ++++++++++++++++++++ tests/test_composer_trust_contract.py | 36 +++++ 3 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/sdk-php-unit-runtime.yml diff --git a/qualification/composer-container-trust.json b/qualification/composer-container-trust.json index 30feb6d..c59d6fe 100644 --- a/qualification/composer-container-trust.json +++ b/qualification/composer-container-trust.json @@ -221,12 +221,84 @@ }, "workflows": { "ci.yml": [ - "test", + "test" + ] + }, + "commands": [ + "bash \"$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh\" \"${{ matrix.php }}\"" + ], + "environment": [ + "SDK_PHP_CI_IMAGE", + "SDK_PHP_CI_ROOT", + "SDK_PHP_CI_STATE" + ], + "absent_files": [ + "docker/php-ci/Dockerfile.dockerignore", + "docker/php-ci/Dockerfile.81.dockerignore" + ], + "files": { + ".dockerignore": { + "sha256": "77afbcb7937677f268f626472f8cf3fef5c8a7384e743df09e081d38279383e1", + "mode": 420 + }, + "docker/php-ci/Dockerfile": { + "sha256": "81d23fbbee7e4ee97ca14df9d17982aa664335b94499c5019662259fe0387eaa", + "mode": 420 + }, + "docker/php-ci/Dockerfile.81": { + "sha256": "c16c2c06252b1b179ccab41876ce76a30febf8a6fd5d92234a2bbed54d7be222", + "mode": 420 + }, + "docker/php-ci/ci-defaults.ini": { + "sha256": "656a644d0a5392488dd843614c40c763a950294e4af6eee124c919930a21a72f", + "mode": 420 + }, + "docker/php-ci/verify-php-trust.php": { + "sha256": "529c282e7422202bf25565431a6391de388f86dfe5bfd6c75cf21daa56ffa75e", + "mode": 420 + }, + "scripts/ci/php-container-images.json": { + "sha256": "389247efc6e551b553314bfe64f66aaef3a96c0bf659e6f78322b84b0dc9860b", + "mode": 420 + }, + "scripts/ci/setup-php-container.sh": { + "sha256": "37dd931f07749ebd29ec6700816e4c8b42ee16d52ae05f426647def250867553", + "mode": 493 + }, + "scripts/ci/run-php-container.sh": { + "sha256": "96727d850846f1bd0b96468e9de8d34c6f6131896b5a80157dc1b71ec39b9392", + "mode": 493 + }, + "tests/ci/test_php_container_tls.py": { + "sha256": "94decbb86d8b6112ba864f927463a41462fb6455707c19b6127f217007d82423", + "mode": 420 + }, + "docker/brotli/install-vendor.sh": { + "sha256": "d7eb8b2a3e0af1c3072a56bcb81632b22f061f211d8fe5e57ee901c8bf94c92f", + "mode": 420 + }, + "docker/brotli/verify-runtime.php": { + "sha256": "580cc465e349f3872eca592355294e7320fe7f1a4560ff5d4c83d5900921c00e", + "mode": 420 + }, + "docker/brotli/verify-system-brotli.c": { + "sha256": "3353927d206aaf5e63157e46be715c6cbee46bac777e29f8f9e25c4c1f656667", + "mode": 420 + }, + "docker/brotli/version": { + "sha256": "e064953e075ddb99fb7b48ad29e215f2dfa075812c127eb1d6ab680e9920fba2", + "mode": 420 + } + } + }, + "sdk-php-carbon-ci": { + "target": "sdk-php", + "workflows": { + "ci.yml": [ "carbon-2" ] }, "commands": [ - "bash \"$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh\" \"${{ matrix.php }}\"", "bash \"$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh\" \"8.1\"" ], "environment": [ diff --git a/tests/fixtures/sdk-php-unit-runtime.yml b/tests/fixtures/sdk-php-unit-runtime.yml new file mode 100644 index 0000000..fe3ae3b --- /dev/null +++ b/tests/fixtures/sdk-php-unit-runtime.yml @@ -0,0 +1,138 @@ +# Proposed SDK caller 960c23efb3d2d456bb70cada938c1d28517ad7d0; runtime jobs only. +jobs: + test: + name: PHP ${{ matrix.php }} + runs-on: ubuntu-24.04 + needs: action-policy + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + php: + - '8.1' + - '8.2' + - '8.4' + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 + with: + persist-credentials: false + - name: Check out the fixed APT prerequisite + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 + with: + repository: durable-workflow/.github + ref: 18783be6bef737cc778e4c7282a8797e39daa61a + path: .central-apt-source + persist-credentials: false + - name: Select Ubuntu's Python 3.12 + run: '/usr/bin/python3 -I -S -c ''import sys; assert sys.version_info[:2] == + (3, 12)'' + + echo /usr/bin >> "$GITHUB_PATH" + + ' + - name: Select authenticated dependency trust + uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 + with: + clients: pip,python,composer,curl,git + - name: Disable APT download retries (CVE-2026-109518) + run: echo 'Acquire::Retries "0";' | sudo tee /etc/apt/apt.conf.d/99-no-retries + >/dev/null + - name: Prepare the reviewed PHP container runtime + run: bash "$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh" "${{ matrix.php + }}" + - name: Verify Composer trust before requests + uses: durable-workflow/.github/.github/actions/verify-composer-trust@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 + - run: composer validate --strict + - name: Verify Composer trust before requests + uses: durable-workflow/.github/.github/actions/verify-composer-trust@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 + - run: composer install --no-interaction --prefer-dist + - name: Lint PHP + run: php scripts/ci/lint-php.php + - name: Verify Composer trust before requests + uses: durable-workflow/.github/.github/actions/verify-composer-trust@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 + - run: 'mkdir -p build + + php scripts/ci/reference-replay-clock-numbers.php > build/replay-clock-numbers.json + + composer test -- --log-junit build/phpunit.xml + + ' + - name: Retain source numeric observations + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: replay-clock-numbers-php-${{ matrix.php }} + path: build/replay-clock-numbers.json + if-no-files-found: error + retention-days: 90 + - name: Verify Composer trust before requests + uses: durable-workflow/.github/.github/actions/verify-composer-trust@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 + - run: composer check-boundary + - name: Verify Composer trust before requests + uses: durable-workflow/.github/.github/actions/verify-composer-trust@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 + - name: Preserve the HTTP-only PHP 8.1 platform + if: matrix.php == '8.1' + run: composer check-http-only-platform + carbon-2: + name: PHP 8.1 with Carbon 2 + runs-on: ubuntu-24.04 + needs: action-policy + timeout-minutes: 15 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 + with: + persist-credentials: false + - name: Check out the fixed APT prerequisite + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 + with: + repository: durable-workflow/.github + ref: 18783be6bef737cc778e4c7282a8797e39daa61a + path: .central-apt-source + persist-credentials: false + - name: Select Ubuntu's Python 3.12 + run: '/usr/bin/python3 -I -S -c ''import sys; assert sys.version_info[:2] == + (3, 12)'' + + echo /usr/bin >> "$GITHUB_PATH" + + ' + - name: Select authenticated dependency trust + uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 + with: + clients: pip,python,composer,curl,git + - name: Disable APT download retries (CVE-2026-109518) + run: echo 'Acquire::Retries "0";' | sudo tee /etc/apt/apt.conf.d/99-no-retries + >/dev/null + - name: Prepare the reviewed PHP container runtime + run: bash "$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh" "8.1" + - name: Constrain the manifest to the oldest supported Carbon major + run: "php -r '$path = \"composer.json\";\n $manifest = json_decode((string)\ + \ file_get_contents($path), flags: JSON_THROW_ON_ERROR);\n $manifest->require->{\"\ + nesbot/carbon\"} = \"^2.72.6\";\n file_put_contents($path, json_encode($manifest,\ + \ JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR).\"\\n\"\ + );'\n" + - name: Verify Composer trust before requests + uses: durable-workflow/.github/.github/actions/verify-composer-trust@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 + - run: composer install --no-interaction --prefer-dist --no-progress + - name: Require Carbon 2 + run: "php -r 'require \"vendor/autoload.php\";\n $version = ltrim((string)\ + \ Composer\\InstalledVersions::getPrettyVersion(\"nesbot/carbon\"), \"v\"\ + );\n fwrite(STDOUT, \"nesbot/carbon {$version}\\n\");\n exit(str_starts_with($version,\ + \ \"2.\") ? 0 : 1);'\n" + - name: Verify Composer trust before requests + uses: durable-workflow/.github/.github/actions/verify-composer-trust@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 + - run: 'mkdir -p build + + php scripts/ci/reference-replay-clock-numbers.php > build/replay-clock-numbers.json + + composer test + + ' + - name: Retain Carbon 2 source numeric observations + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: replay-clock-numbers-php-8.1-carbon-2 + path: build/replay-clock-numbers.json + if-no-files-found: error + retention-days: 90 diff --git a/tests/test_composer_trust_contract.py b/tests/test_composer_trust_contract.py index acf570f..6998361 100644 --- a/tests/test_composer_trust_contract.py +++ b/tests/test_composer_trust_contract.py @@ -526,6 +526,42 @@ def test_ambiguous_recipes_do_not_authorize_a_runtime(self): with self.assertRaisesRegex(PolicyError, "ambiguous"): self.check() + def test_sdk_unit_matrix_cannot_borrow_the_fixed_carbon_runtime_command(self): + repository = Path(__file__).resolve().parents[1] + catalog = json.loads((repository / "qualification/composer-container-trust.json").read_text()) + # This fixture is the two actual proposed SDK runtime jobs. File identity + # has separate controls; this regression exercises only job/command scope. + catalog["requests"] = {} + document = yaml.safe_load((repository / "tests/fixtures/sdk-php-unit-runtime.yml").read_text()) + policy = json.loads((repository / "qualification/policy.json").read_text()) + + def validate(candidate, recipes): + with (mock.patch("scripts.qualification_policy._composer_container_catalog", return_value=recipes), + mock.patch("scripts.qualification_policy._require_composer_runtime_sources")): + return verify_composer_network_sinks(policy, "sdk-php", {"ci.yml": yaml.safe_dump(candidate)}, self.root) + + self.assertTrue(validate(document, catalog)) + changed = deepcopy(document) + setup = next(step for step in changed["jobs"]["test"]["steps"] + if "setup-php-container.sh" in step.get("run", "")) + setup["run"] = catalog["runtime_setups"]["sdk-php-carbon-ci"]["commands"][0] + predecessor = deepcopy(catalog) + unit = predecessor["runtime_setups"]["sdk-php-unit-ci"] + carbon = predecessor["runtime_setups"].pop("sdk-php-carbon-ci") + unit["workflows"]["ci.yml"].append("carbon-2") + unit["commands"].extend(carbon["commands"]) + self.assertTrue(validate(changed, predecessor), "The predecessor accepted the wrong runtime in the unit job.") + with self.assertRaises(PolicyError): + validate(changed, catalog) + changed["jobs"]["test"]["strategy"]["matrix"]["php"] = ["8.2", "8.4"] + self.assertTrue(validate(changed, predecessor), "The predecessor also lost the represented PHP8.1 cell.") + with self.assertRaises(PolicyError): + validate(changed, catalog) + changed = deepcopy(document) + changed["jobs"]["test"]["strategy"]["matrix"]["php"] = ["8.2", "8.4"] + with self.assertRaises(PolicyError): + validate(changed, catalog) + def test_a_symlinked_build_input_directory_refuses_even_when_bytes_match(self): original = self.root / "docker/php" moved = self.root / "actual-build-input" From d88232dd39c80e788d48e818232d9f4bafcb392f Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Fri, 13 Nov 2026 14:35:12 +0000 Subject: [PATCH 3/3] Bind SDK CI recipes to read-only launcher controls --- qualification/composer-container-trust.json | 22 ++++++++++++++------- tests/fixtures/sdk-php-unit-runtime.yml | 4 ++-- tests/test_composer_trust_contract.py | 6 ++++++ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/qualification/composer-container-trust.json b/qualification/composer-container-trust.json index a3c5184..2705305 100644 --- a/qualification/composer-container-trust.json +++ b/qualification/composer-container-trust.json @@ -264,15 +264,15 @@ "mode": 420 }, "scripts/ci/setup-php-container.sh": { - "sha256": "37dd931f07749ebd29ec6700816e4c8b42ee16d52ae05f426647def250867553", + "sha256": "ae216b71a15ab40098e4341e6ab2ebe4f2a3cecc497cbb9eb412bfd19c446cf9", "mode": 493 }, "scripts/ci/run-php-container.sh": { - "sha256": "96727d850846f1bd0b96468e9de8d34c6f6131896b5a80157dc1b71ec39b9392", + "sha256": "3381acfe8ef0eddd2b88b6acce0ae1143357ff3a61884d2c7ae274ba3ee81bd9", "mode": 493 }, "tests/ci/test_php_container_tls.py": { - "sha256": "94decbb86d8b6112ba864f927463a41462fb6455707c19b6127f217007d82423", + "sha256": "d73a2a3c494bd31bd4f8b5215070aa7ff4807c3e4cb75e8b3eb51d2fe057c0ae", "mode": 420 }, "docker/brotli/install-vendor.sh": { @@ -290,6 +290,10 @@ "docker/brotli/version": { "sha256": "e064953e075ddb99fb7b48ad29e215f2dfa075812c127eb1d6ab680e9920fba2", "mode": 420 + }, + "tests/ci/test_php_container_isolation.py": { + "sha256": "209b1382e473f674dad3f6a1fbc447557dba647df2d23b70c8af1db2dab97697", + "mode": 420 } } }, @@ -301,7 +305,7 @@ ] }, "commands": [ - "bash \"$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh\" \"8.1\"" + "bash \"$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh\" \"8.1\" carbon2" ], "environment": [ "SDK_PHP_CI_IMAGE", @@ -338,15 +342,15 @@ "mode": 420 }, "scripts/ci/setup-php-container.sh": { - "sha256": "37dd931f07749ebd29ec6700816e4c8b42ee16d52ae05f426647def250867553", + "sha256": "ae216b71a15ab40098e4341e6ab2ebe4f2a3cecc497cbb9eb412bfd19c446cf9", "mode": 493 }, "scripts/ci/run-php-container.sh": { - "sha256": "96727d850846f1bd0b96468e9de8d34c6f6131896b5a80157dc1b71ec39b9392", + "sha256": "3381acfe8ef0eddd2b88b6acce0ae1143357ff3a61884d2c7ae274ba3ee81bd9", "mode": 493 }, "tests/ci/test_php_container_tls.py": { - "sha256": "94decbb86d8b6112ba864f927463a41462fb6455707c19b6127f217007d82423", + "sha256": "d73a2a3c494bd31bd4f8b5215070aa7ff4807c3e4cb75e8b3eb51d2fe057c0ae", "mode": 420 }, "docker/brotli/install-vendor.sh": { @@ -364,6 +368,10 @@ "docker/brotli/version": { "sha256": "e064953e075ddb99fb7b48ad29e215f2dfa075812c127eb1d6ab680e9920fba2", "mode": 420 + }, + "tests/ci/test_php_container_isolation.py": { + "sha256": "209b1382e473f674dad3f6a1fbc447557dba647df2d23b70c8af1db2dab97697", + "mode": 420 } } } diff --git a/tests/fixtures/sdk-php-unit-runtime.yml b/tests/fixtures/sdk-php-unit-runtime.yml index fe3ae3b..e125065 100644 --- a/tests/fixtures/sdk-php-unit-runtime.yml +++ b/tests/fixtures/sdk-php-unit-runtime.yml @@ -1,4 +1,4 @@ -# Proposed SDK caller 960c23efb3d2d456bb70cada938c1d28517ad7d0; runtime jobs only. +# Proposed SDK caller 0ee1d57b7debe081a6f65eff75763f9db3379d4b; read-only runtime jobs only. jobs: test: name: PHP ${{ matrix.php }} @@ -104,7 +104,7 @@ jobs: run: echo 'Acquire::Retries "0";' | sudo tee /etc/apt/apt.conf.d/99-no-retries >/dev/null - name: Prepare the reviewed PHP container runtime - run: bash "$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh" "8.1" + run: bash "$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh" "8.1" carbon2 - name: Constrain the manifest to the oldest supported Carbon major run: "php -r '$path = \"composer.json\";\n $manifest = json_decode((string)\ \ file_get_contents($path), flags: JSON_THROW_ON_ERROR);\n $manifest->require->{\"\ diff --git a/tests/test_composer_trust_contract.py b/tests/test_composer_trust_contract.py index 10a678c..c27d4d9 100644 --- a/tests/test_composer_trust_contract.py +++ b/tests/test_composer_trust_contract.py @@ -653,6 +653,12 @@ def validate(candidate, recipes): changed["jobs"]["test"]["strategy"]["matrix"]["php"] = ["8.2", "8.4"] with self.assertRaises(PolicyError): validate(changed, catalog) + changed = deepcopy(document) + setup = next(step for step in changed["jobs"]["carbon-2"]["steps"] + if "setup-php-container.sh" in step.get("run", "")) + setup["run"] = setup["run"].removesuffix(" carbon2") + with self.assertRaises(PolicyError): + validate(changed, catalog) def test_a_symlinked_build_input_directory_refuses_even_when_bytes_match(self): original = self.root / "docker/php"