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
163 changes: 163 additions & 0 deletions qualification/composer-container-trust.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,169 @@
"mode": 420
}
}
},
"sdk-php-unit-ci": {
"target": "sdk-php",
"matrix": {
"php": [
"8.1",
"8.2",
"8.4"
]
},
"workflows": {
"ci.yml": [
"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": "ae216b71a15ab40098e4341e6ab2ebe4f2a3cecc497cbb9eb412bfd19c446cf9",
"mode": 493
},
"scripts/ci/run-php-container.sh": {
"sha256": "3381acfe8ef0eddd2b88b6acce0ae1143357ff3a61884d2c7ae274ba3ee81bd9",
"mode": 493
},
"tests/ci/test_php_container_tls.py": {
"sha256": "d73a2a3c494bd31bd4f8b5215070aa7ff4807c3e4cb75e8b3eb51d2fe057c0ae",
"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
},
"tests/ci/test_php_container_isolation.py": {
"sha256": "209b1382e473f674dad3f6a1fbc447557dba647df2d23b70c8af1db2dab97697",
"mode": 420
}
}
},
"sdk-php-carbon-ci": {
"target": "sdk-php",
"workflows": {
"ci.yml": [
"carbon-2"
]
},
"commands": [
"bash \"$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh\" \"8.1\" carbon2"
],
"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": "ae216b71a15ab40098e4341e6ab2ebe4f2a3cecc497cbb9eb412bfd19c446cf9",
"mode": 493
},
"scripts/ci/run-php-container.sh": {
"sha256": "3381acfe8ef0eddd2b88b6acce0ae1143357ff3a61884d2c7ae274ba3ee81bd9",
"mode": 493
},
"tests/ci/test_php_container_tls.py": {
"sha256": "d73a2a3c494bd31bd4f8b5215070aa7ff4807c3e4cb75e8b3eb51d2fe057c0ae",
"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
},
"tests/ci/test_php_container_isolation.py": {
"sha256": "209b1382e473f674dad3f6a1fbc447557dba647df2d23b70c8af1db2dab97697",
"mode": 420
}
}
}
}
}
138 changes: 138 additions & 0 deletions tests/fixtures/sdk-php-unit-runtime.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Proposed SDK caller 0ee1d57b7debe081a6f65eff75763f9db3379d4b; read-only 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" 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->{\"\
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
42 changes: 42 additions & 0 deletions tests/test_composer_trust_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,48 @@ 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)
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"
moved = self.root / "actual-build-input"
Expand Down
Loading