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
62 changes: 62 additions & 0 deletions qualification/composer-container-trust.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,68 @@
}
}
},
"waterline-php82-build": {
"target": "waterline",
"workflows": {
"php.yml": [
"build"
]
},
"commands": [
"bash \"$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh\" \"8.2\""
],
"environment": [
"WATERLINE_PHP_CI_IMAGE",
"WATERLINE_PHP_CI_ROOT",
"WATERLINE_PHP_CI_STATE"
],
"files": {
"docker/php-ci/Dockerfile": {
"sha256": "81d23fbbee7e4ee97ca14df9d17982aa664335b94499c5019662259fe0387eaa",
"mode": 384
},
"docker/php-ci/Dockerfile.dockerignore": {
"sha256": "a26d7f1f5ae2cac900e5e32bfaa0f08bd7a8435847095990c9bfe09d0edbf264",
"mode": 384
},
"docker/php-ci/verify-php-trust.php": {
"sha256": "529c282e7422202bf25565431a6391de388f86dfe5bfd6c75cf21daa56ffa75e",
"mode": 384
},
"docker/brotli/install-vendor.sh": {
"sha256": "d7eb8b2a3e0af1c3072a56bcb81632b22f061f211d8fe5e57ee901c8bf94c92f",
"mode": 384
},
"docker/brotli/verify-runtime.php": {
"sha256": "580cc465e349f3872eca592355294e7320fe7f1a4560ff5d4c83d5900921c00e",
"mode": 384
},
"docker/brotli/verify-system-brotli.c": {
"sha256": "3353927d206aaf5e63157e46be715c6cbee46bac777e29f8f9e25c4c1f656667",
"mode": 384
},
"docker/brotli/version": {
"sha256": "e064953e075ddb99fb7b48ad29e215f2dfa075812c127eb1d6ab680e9920fba2",
"mode": 384
},
"scripts/ci/php-container-images.json": {
"sha256": "3314ecfd6c32641af1d5475f8e57f4421979f67e05db1f98c79ef7b08d41b944",
"mode": 384
},
"scripts/ci/run-php-container.sh": {
"sha256": "456f9716edbfaa8429edba92f958c4e1b3551bcbfd849a722764ae47daffb8fd",
"mode": 448
},
"scripts/ci/setup-php-container.sh": {
"sha256": "5064d48df69940cdf609be7bccd82ee71624d1017a8486a29028c765bb311f13",
"mode": 448
},
"tests/ci/test_php_container_tls.py": {
"sha256": "7e365ff0f27c4efea0f7cf8ec3778f48fb1fe03e80a5b5b0c9500a6935f3d767",
"mode": 384
}
}
},
"cli-php-ci": {
"target": "cli",
"matrix": {
Expand Down
1 change: 1 addition & 0 deletions qualification/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@
]
},
"waterline": {
"composer_trust_required": true,
"branch": "main",
"repository": "waterline",
"workflows": [
Expand Down
60 changes: 60 additions & 0 deletions tests/fixtures/waterline-php82-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Proposed caller for the Waterline PHP 8.2 runtime admission contract.
jobs:
build:
needs:
- classify
- frontend
if: ${{ needs.classify.outputs.qualification-class == 'complete' }}
runs-on: ubuntu-24.04
timeout-minutes: 12
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
with:
fetch-depth: 10
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
with:
python-version: '3.13'
- name: Select authenticated dependency trust
uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@75587fc33ff40c34f32ef777293dad56bb6f48c7
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: 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: Install the bound PHP 8.2 build runtime
run: bash "$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh" "8.2"
- name: Verify Composer configuration before requests
uses: durable-workflow/.github/.github/actions/verify-composer-trust@75587fc33ff40c34f32ef777293dad56bb6f48c7
- name: Validate Composer manifest
run: composer validate --strict --no-check-all
- name: Cache Composer packages
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name
== github.repository }}
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25
with:
path: vendor
key: ${{ github.event_name }}-${{ runner.os }}-php-8.2-${{ hashFiles('composer.json')
}}
restore-keys: '${{ github.event_name }}-${{ runner.os }}-php-8.2-

'
- name: Verify Composer after cache restoration
uses: durable-workflow/.github/.github/actions/verify-composer-trust@75587fc33ff40c34f32ef777293dad56bb6f48c7
- name: Install maintained qualification dependencies
run: composer install --prefer-dist --no-progress --no-interaction
- name: Verify Composer before the dependency audit
uses: durable-workflow/.github/.github/actions/verify-composer-trust@75587fc33ff40c34f32ef777293dad56bb6f48c7
- name: Audit qualification dependencies
run: composer audit --no-interaction
- name: Verify Composer before Workbench scaffolding
uses: durable-workflow/.github/.github/actions/verify-composer-trust@75587fc33ff40c34f32ef777293dad56bb6f48c7
- name: Build Laravel 12 Workbench scaffold
run: composer build
45 changes: 45 additions & 0 deletions tests/test_composer_trust_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,51 @@ def test_an_unregistered_setup_or_another_action_at_that_pin_refuses(self):
self.check(steps)


class WaterlineBuildMandatoryTrustContractTest(unittest.TestCase):
def setUp(self):
repository = Path(__file__).resolve().parents[1]
self.policy = json.loads((repository / "qualification/policy.json").read_text())
self.document = yaml.safe_load((repository / "tests/fixtures/waterline-php82-build.yml").read_text())
self.catalog = json.loads((repository / "qualification/composer-container-trust.json").read_text())
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name)
# Source hashing is covered independently. Bind harmless local bytes so
# this regression tests the real target policy and proposed caller offline.
recipe = self.catalog["runtime_setups"]["waterline-php82-build"]
for name, expected in recipe["files"].items():
path = self.root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("# reviewed runtime fixture\n")
path.chmod(expected["mode"])
expected["sha256"] = hashlib.sha256(path.read_bytes()).hexdigest()
patch = mock.patch("scripts.qualification_policy._composer_container_catalog", return_value=self.catalog)
patch.start()
self.addCleanup(patch.stop)

def check(self, document):
return verify_composer_network_sinks(
self.policy, "waterline", {"php.yml": yaml.safe_dump(document)}, self.root,
)

def test_proposed_build_enumerates_all_guarded_requests(self):
self.assertEqual(4, len(self.check(self.document)))

def test_deleting_every_live_verifier_cannot_disable_enumeration(self):
document = deepcopy(self.document)
steps = document["jobs"]["build"]["steps"]
document["jobs"]["build"]["steps"] = [
step for step in steps if "verify-composer-trust@" not in step.get("uses", "")
]
with self.assertRaises(PolicyError):
self.check(document)

def test_guardless_build_cannot_opt_out_before_loading_the_catalog(self):
document = {"jobs": {"build": {"steps": [{"run": "composer install --no-interaction"}]}}}
with self.assertRaises(PolicyError):
self.check(document)


class ComposerRuntimeSetupContractTest(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
Expand Down
Loading