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
7 changes: 7 additions & 0 deletions scripts/protected_policy_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
REVIEWED_BOUND_PHP_SOURCE_CI_SUCCESSORS = {
CI_WORKFLOW: "363540f9e81e4842099f0cad394ce3fc2a83835ce1355ffef5b16722547699b4",
}
# Exact reviewed successors add the response-loss socket controls to CI and
# their actual source to coverage. Existing workflow identities remain valid.
REVIEWED_RESPONSE_LOSS_SOURCE_CI_SUCCESSORS = {
CI_WORKFLOW: "ee4ddc9ad665490681b3f1a2676cdd4ce791d90677e2a931bdb4fea7cc9deb15",
SONAR_WORKFLOW: "4eed5d82d6e5decf47d4aab1d1d203eb1060f8892216360b9308c3e710bbcd77",
}
# The successor remains inert until its separate runtime pin and manual-only
# workflow integration. Keep the consumed predecessor's identity unchanged.
REVIEWED_INITIAL_PROFILE_SUCCESSORS = {
Expand Down Expand Up @@ -116,6 +122,7 @@ def reviewed_context_hashes(policy_target: str) -> dict[str, set[str]]:
REVIEWED_PHP_CONFIGURATION_SOURCE_CI_SUCCESSORS,
REVIEWED_APT_NO_RETRIES_SOURCE_CI_SUCCESSORS,
REVIEWED_BOUND_PHP_SOURCE_CI_SUCCESSORS,
REVIEWED_RESPONSE_LOSS_SOURCE_CI_SUCCESSORS,
REVIEWED_INITIAL_PROFILE_SUCCESSORS, REVIEWED_NORMALIZED_PROFILE_SUCCESSORS,
REVIEWED_ALTERNATIVES_PROFILE_SUCCESSORS,
REVIEWED_SELECTED_ALTERNATIVES_PROFILE_SUCCESSORS,
Expand Down
123 changes: 123 additions & 0 deletions tests/fixtures/ci-response-loss-source.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: Repository checks

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: repository-checks-${{ github.ref }}
cancel-in-progress: true

jobs:
contracts:
name: Shared contract tests
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false

- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.13"

- uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust
with:
clients: pip,python,composer,curl,git,npm,cargo

- 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

- uses: durable-workflow/.github/.github/actions/setup-php-linux@411a14ac07d32073eed6ab4977f2825eba4739a7 # bound-linux-php
with:
php-version: '8.3'
ini-file: production
tools: composer:2.10.3,phpunit:11.5.57
coverage: none

- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: 24.22.1
cache: npm

- uses: dtolnay/rust-toolchain@4d407b29186a635f0cc27475ef0bc0ae605a8866 # 1.86.0
with:
toolchain: 1.86.0

- uses: ./.github/actions/verify-composer-trust # local

- name: Install Python test dependencies
run: >-
python -m pip install --only-binary :all: jsonschema==4.23.0 PyYAML==6.0.2

- name: Test retained shared contracts
run: >-
python -m unittest
tests.test_qualification_policy
tests.test_protected_policy_context
tests.test_protected_policy_check
tests.test_protected_policy_inputs
tests.test_protected_policy_issue
tests.test_native_policy_check
tests.test_native_policy_workflow
tests.test_regression_corpus
tests.test_regression_evidence_schema
tests.test_server_codec_counterfactual_schema
tests.test_visual_evidence
tests.test_temporal_runtime
tests.test_cancellation_workflow_policy
tests.test_sdk_metrics_contract
tests.test_command_response_loss
tests.test_ci_trust
tests.test_pip_effective_trust
tests.test_cli_effective_trust
tests.test_composer_trust_contract
tests.test_composer_container_trust
tests.test_composer_container_effective_trust
tests.test_php_linux_bootstrap
tests.test_php_linux_archive
tests.test_php_linux_normalize
tests.test_php_linux_recipe
tests.test_php_linux_tools
tests.test_php_linux_alternatives
tests.test_setup_php_linux
tests.test_php_linux_qualification
tests.test_php_linux_qualification_workflow
tests.test_php_linux_configuration

- name: Test actual Composer trust selection
run: COMPOSER_BINARY="$(command -v composer)" phpunit --configuration phpunit-composer-trust.xml --do-not-cache-result

- name: Install Node dependencies
run: npm ci --ignore-scripts

- name: Test browser evidence boundary
run: npm run test:visual-capture

protected-parser-install:
name: Protected parser installer controls
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.13'
- name: Create a disposable installer runtime
run: |
python -I -m venv "$RUNNER_TEMP/protected-parser-install"
printf '%s\n' "$RUNNER_TEMP/protected-parser-install/bin" >> "$GITHUB_PATH"
- uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust
with:
clients: pip,python
- name: Exercise the actual isolated hash-locked installer
run: python -I tests/test_policy_dependency_install.py
62 changes: 62 additions & 0 deletions tests/fixtures/sonar-response-loss-source.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: SonarQube

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

concurrency:
group: sonar-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
analysis:
name: SonarQube analysis
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.13'
- uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust
with:
clients: pip,python,composer,curl,git,npm,cargo
- 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
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: '8.3'
tools: composer:2.10.3, phpunit:11.5.57
coverage: xdebug
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: 24.22.1
- uses: dtolnay/rust-toolchain@4d407b29186a635f0cc27475ef0bc0ae605a8866 # 1.86.0
with:
toolchain: 1.86.0
- uses: ./.github/actions/verify-composer-trust # local
- name: Produce actual Composer control coverage
run: |
mkdir -p build
COMPOSER_BINARY="$(command -v composer)" XDEBUG_MODE=coverage \
phpunit --configuration phpunit-composer-trust.xml --do-not-cache-result \
--coverage-clover build/composer-trust-coverage.xml
- name: Produce source coverage
run: |
python -m pip install --only-binary :all: coverage==7.10.7 jsonschema==4.23.0 PyYAML==6.0.2
coverage run --source=scripts,conformance/cancellation/temporal,conformance/command-keys,.github/actions/bootstrap-patched-pip -m unittest tests.test_qualification_policy tests.test_protected_policy_context tests.test_protected_policy_check tests.test_protected_policy_inputs tests.test_protected_policy_issue tests.test_native_policy_check tests.test_native_policy_workflow tests.test_regression_corpus tests.test_regression_evidence_schema tests.test_server_codec_counterfactual_schema tests.test_visual_evidence tests.test_temporal_runtime tests.test_cancellation_workflow_policy tests.test_sdk_metrics_contract tests.test_command_response_loss tests.test_ci_trust tests.test_pip_effective_trust tests.test_cli_effective_trust tests.test_composer_trust_contract tests.test_composer_container_trust tests.test_composer_container_effective_trust tests.test_php_linux_bootstrap tests.test_php_linux_archive tests.test_php_linux_normalize tests.test_php_linux_recipe tests.test_php_linux_tools tests.test_php_linux_alternatives tests.test_setup_php_linux tests.test_php_linux_qualification tests.test_php_linux_qualification_workflow tests.test_php_linux_configuration
coverage xml -o coverage.xml
- uses: SonarSource/sonarqube-scan-action@d209202bc7d53ff1cc128f7f907dac145c9d6ae9 # v8.3.0
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: -Dsonar.qualitygate.wait=true -Dsonar.qualitygate.timeout=300
35 changes: 35 additions & 0 deletions tests/test_protected_policy_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,41 @@ def test_unknown_context_target_refuses_without_interpreting_a_workflow(self):
with self.assertRaisesRegex(InputRefused, "context target"):
context.validate_context({}, policy_target="elsewhere")

def test_response_loss_ci_successors_preserve_predecessors_and_require_exact_review(self):
predecessors = {
context.CI_WORKFLOW: "363540f9e81e4842099f0cad394ce3fc2a83835ce1355ffef5b16722547699b4",
context.SONAR_WORKFLOW: "f7fad8367df4a4e9565d97e13aa120d55038ed9c749fdfcd86e1312bfca3cced",
}
for path, previous_digest in predecessors.items():
name = Path(path).stem
raw = (ROOT / f"tests/fixtures/{name}-response-loss-source.yml").read_bytes()
module = (b" tests.test_command_response_loss\n" if path == context.CI_WORKFLOW
else b" tests.test_command_response_loss")
self.assertEqual(1, raw.count(module))
previous = raw.replace(module, b"", 1)
if path == context.SONAR_WORKFLOW:
self.assertEqual(1, previous.count(b",conformance/command-keys"))
previous = previous.replace(b",conformance/command-keys", b"", 1)
self.assertEqual(previous_digest, hashlib.sha256(previous).hexdigest())
self.assertIn(previous_digest, context.reviewed_context_hashes("github-control-plane")[path])
context.validate_context({path: previous})
self.assertEqual(context.REVIEWED_RESPONSE_LOSS_SOURCE_CI_SUCCESSORS[path],
hashlib.sha256(raw).hexdigest())
with self.subTest(path=path):
context.validate_context({path: raw})
with patch.object(context, "REVIEWED_RESPONSE_LOSS_SOURCE_CI_SUCCESSORS", {}), \
self.assertRaises(InputRefused):
context.validate_context({path: raw})
for changed in (raw + b"\n# unreviewed\n",
raw.replace(b"tests.test_command_response_loss", b"tests.unreviewed", 1)):
self.assertNotEqual(raw, changed)
with self.assertRaises(InputRefused):
context.validate_context({path: changed})
with self.assertRaises(InputRefused):
context.validate_context({WORKFLOW: raw})
with self.assertRaises(InputRefused):
context.validate_context({path: raw}, policy_target="ai")

def test_bound_php_source_ci_successor_preserves_checks_and_requires_exact_review(self):
path = context.CI_WORKFLOW
raw = (ROOT / "tests/fixtures/ci-bound-php-source.yml").read_bytes()
Expand Down
Loading