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
49 changes: 1 addition & 48 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,11 @@ concurrency:
cancel-in-progress: true

jobs:
policy-dependencies:
name: Locked policy wheels (${{ matrix.runner }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 5
strategy:
fail-fast: false
matrix:
runner: [ubuntu-latest, ubuntu-24.04-arm]
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 policy-client venv
run: |
python -I -m venv "$RUNNER_TEMP/policy-venv"
printf '%s\n' "$RUNNER_TEMP/policy-venv/bin" >> "$GITHUB_PATH"
- uses: ./.github/actions/bootstrap-patched-pip # local
- name: Prove isolated installation and wheel integrity
run: python -I tests/test_policy_dependency_install.py

contracts:
name: Shared contract tests
permissions:
contents: read
pull-requests: read
needs: [policy-dependencies]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Require both native policy-wheel controls
env:
POLICY_RESULT: ${{ needs.policy-dependencies.result }}
run: test "$POLICY_RESULT" = success
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6

- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
Expand Down Expand Up @@ -81,15 +49,12 @@ jobs:

- name: Install Python test dependencies
run: >-
python -I -m pip install --only-binary :all: jsonschema==4.23.0 PyYAML==6.0.2
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_inputs
tests.test_protected_policy_check
tests.test_protected_policy_issue
tests.test_regression_corpus
tests.test_regression_evidence_schema
tests.test_server_codec_counterfactual_schema
Expand All @@ -103,18 +68,6 @@ jobs:
tests.test_composer_container_trust
tests.test_composer_container_effective_trust

- name: Exercise the read-only policy client on this public PR
if: github.event_name == 'pull_request'
env:
GITHUB_TOKEN: ${{ github.token }}
POLICY_PULL_REQUEST: ${{ github.event.pull_request.number }}
POLICY_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
python -I scripts/protected_policy_check.py \
--pull-request "$POLICY_PULL_REQUEST" --base-sha "$POLICY_BASE_SHA" \
> "$RUNNER_TEMP/protected-policy-read.json"
cat "$RUNNER_TEMP/protected-policy-read.json"

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

Expand Down
77 changes: 0 additions & 77 deletions .github/workflows/protected-policy.yml

This file was deleted.

4 changes: 2 additions & 2 deletions .github/workflows/sonar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ jobs:
--coverage-clover build/composer-trust-coverage.xml
- name: Produce source coverage
run: |
python -I -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,.github/actions/bootstrap-patched-pip -m unittest tests.test_qualification_policy tests.test_protected_policy_inputs tests.test_protected_policy_check tests.test_protected_policy_issue 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_sdk_metrics_contract 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
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,.github/actions/bootstrap-patched-pip -m unittest tests.test_qualification_policy 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_sdk_metrics_contract 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
coverage xml -o coverage.xml
- uses: SonarSource/sonarqube-scan-action@d209202bc7d53ff1cc128f7f907dac145c9d6ae9 # v8.3.0
env:
Expand Down
129 changes: 1 addition & 128 deletions qualification/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ Cache restores in pull-request workflows must also exclude external forks at
the job or step condition. An earlier revision can populate the same PR cache,
so event-partitioned keys alone do not protect a later revision's build. An exact
shared key remains available for guarded restores and protected-main-only saves.
The scanner rejects `pull_request_target` outside the single content-bound
public pilot described below, mutable container
The scanner rejects `pull_request_target`, mutable container
images, unreviewed `workflow_run` consumers, and privileged artifact consumers
without exact producer and digest provenance.

Expand All @@ -49,132 +48,6 @@ plan job. The workflow may have no other trigger.
Product repositories own their tests, documentation deployment, publication,
and deployment workflows. Editorial wording is outside this policy.

## Isolated, hash-locked policy bootstrap

Server's policy preflight requires action revision
`fea245682582d0620054e8547a5f3c968f9ef0f0` and exactly:

```sh
python -I -m pip --isolated install --require-hashes --only-binary=:all: --no-deps -r .central-action-policy/qualification/requirements.lock
```

The reviewed central checkout supplies the lock. It permits only the reviewed
PyYAML 6.0.2 CPython 3.13 glibc Linux wheels for amd64 and arm64. Native CI checks
both wheel installations, a tampered hash, and working-directory `pip.py` and
`pip/` shadows. Missing isolation/integrity/binary/no-dependency flags, another
requirements path, extra options, a missing bootstrap or an older bootstrap
revision fail the Server contract. The same command is available for staged
adoption in other targets. Their legacy forms remain explicit migration work,
not evidence of the locked-policy contract. This source check does not establish
an independent protected execution boundary; that gate requires its own issuer
and repository-rule acceptance.

### Protected gate input layer

`scripts/protected_policy_inputs.py` captures an open public pull request against
the expected repository ID and exact `main` commit. It reads the candidate's
exact commit/tree identities and regular workflow blobs, plus literal referenced
helper blobs needed by the existing Composer inspection. Paths, file counts,
individual sizes, total bytes and encoding are bounded. Symlinks, submodules,
truncated trees and a changed PR identity refuse. Materialized files are data
with mode `0600`; candidate commands are never executed by this layer.

The caller must supply a bounded read-only GitHub API client from protected
source, keep candidate data outside the interpreter's import path, and call
`require_current` again immediately before issuing a result. This module does
not create a Check Run, grant merge authority, configure an Environment or prove
an issuer binding. Those live integration requirements remain open. Private
repositories are explicitly refused until their separate gate is qualified.

### Read-only protected-policy pilot client

`scripts/protected_policy_check.py` connects that input layer to the reviewed
validator for the public `.github` pilot. It binds the immutable repository ID,
requires the expected protected `main` SHA, and rechecks main and PR identity
after validation. Candidate workflow/helper files are temporary nonexecutable
data outside Python's import path. The policy always comes from the reviewed
central checkout; there is no candidate policy-path override.

After the authenticated bootstrap exports its CA file, empty additional-root
directory and digest, a read-only caller uses:

```sh
python -I scripts/protected_policy_check.py \
--pull-request "$POLICY_PULL_REQUEST" --base-sha "$POLICY_BASE_SHA"
```

The client requires curl 8.4 or newer, an explicit matching CA bundle and no
additional roots. Transfers are HTTPS GETs to the fixed GitHub API host, with
no redirects or automatic content decoding. Each response is capped at 8 MiB;
there is a 15-second request budget and a shared 120-second API budget. JSON
depth/node counts, endpoint shapes and request counts are also bounded. A read
token, when supplied as `GITHUB_TOKEN`, travels on stdin rather than process
arguments. The caller grants only Contents read and Pull requests read. Never
give the reader the App signing key or its Checks-write token.

Success prints a bounded identity/digest report. Failure prints escaped JSON
and exits nonzero; candidate text cannot become a GitHub workflow command.
Ordinary PR CI exercises the real read client and validator. That candidate
controlled job remains a component test. Protected base execution, the App
issuer, immediate identity recheck, environment-denial tests and issuer-bound
repository rules still require separate live acceptance before enforcement.

### Prepared App issuer component

`scripts/protected_policy_issue.py` prepares the checks-only issuer for that
same public pilot. It runs the validator itself rather than accepting a
candidate-supplied report. The read-only `GITHUB_TOKEN` and separate
`POLICY_CHECKS_TOKEN` installation token have different roles; the issuer never
accepts or reads an App signing key. `POLICY_GATE_APP_ID` must name the reviewed
installation's App. The App must have Checks write and implicit Metadata read
only, installed solely on the pilot repository.

The intended protected caller uses:

```sh
python -I scripts/protected_policy_issue.py \
--pull-request "$POLICY_PULL_REQUEST" --base-sha "$POLICY_BASE_SHA" \
--expected-head "$POLICY_HEAD_SHA"
```

It checks the protected-main workflow context and exact event head, validates
bounded candidate data, creates one incomplete Check Run, and verifies the
returned check name, original head and App ID. Only then does it re-read the
protected main and complete PR identity immediately before completing that
same check successfully. Read and write credentials use stdin, with explicit
reviewed TLS roots and the existing transfer/time/JSON bounds. The App client
has no arbitrary endpoint or repository input. Refusal exits nonzero and never
reports success; a check already created may remain incomplete. A later head
cannot inherit an earlier head's successful check.

Caller environment markers are defense in depth, not proof of secret denial.
The prepared workflow checks out `github.sha` from protected main, verifies
both the workflow SHA and actual checkout match it, installs the hash-locked
parser, then uses the pinned official App-token action. Its token is restricted
to Checks write on the one public pilot. The key is an Environment secret and
never enters the reader/issuer program. The token action selects only the
verified OpenSSL CA file and empty additional-root directory; bundled, extra
and additional system roots are excluded for that credentialed Node step.

The policy permits this one exact UTF-8 workflow SHA-256, target and path.
Another path/repository, changed checkout/permission/credential handling,
different content or additional exception refuses. Updating that root requires
another explicit source review. All other `pull_request_target` workflows
remain forbidden. This does not validate arbitrary changes to central policy
code; root-of-trust changes still require independent maintainer/Security review.

This bootstrap revision is prepared source, not a live enforcement claim. It
cannot authorize its own adoption. Security must accept the exact source;
the account holder must establish the App and key; and the live pilot must
prove Environment denial, wrong-issuer nonqualification, candidate nonexecution,
stale-head refusal and a safe successful check before the required-check rule
is bound to that App. The known Environment configuration alone is not proof.
No App key, live write or private-repository adoption is exercised by the
constructed-response tests. Existing Server/publication holds remain.

The transport uses GitHub's [create and update Check Run API](https://docs.github.com/en/rest/checks/runs)
and checks the documented [workflow environment variables](https://docs.github.com/en/actions/reference/workflows-and-actions/variables).

## Clean trust for dependency requests

### Composer inside a container
Expand Down
13 changes: 1 addition & 12 deletions qualification/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@
"actions/configure-pages": {
"45bfe0192ca1faeb007ade9deae92b16b8254a0d": "v6"
},
"actions/create-github-app-token": {
"64ab729a6294000f5aa4a4bc0b73d1f32bc45510": "v3.3.0"
},
"actions/deploy-pages": {
"cd2ce8fcbc39b97be8ca5fce6e763baed58fa128": "v5"
},
Expand Down Expand Up @@ -85,7 +82,6 @@
"699d09d602fc0353160bdecaa2b23f07ef4c7c21": "clean-trust",
"75587fc33ff40c34f32ef777293dad56bb6f48c7": "clean-trust",
"76f333804e68725059039fd54df241d40d855cd3": "clean-trust",
"fea245682582d0620054e8547a5f3c968f9ef0f0": "clean-trust",
"ff93e15dd3d3aa0b5ba8991fe5101a9e1be49c10": "patched-pip"
},
"helm/chart-testing-action": {
Expand Down Expand Up @@ -169,13 +165,7 @@
}
},
"privileged_workflow_run_consumers": {},
"pull_request_target_exceptions": [
{
"target": "github-control-plane",
"path": ".github/workflows/protected-policy.yml",
"sha256": "642d204ea939ca5eba7ef51be9e4ae56a35177413caabe849315086d3d9d2d31"
}
]
"pull_request_target_exceptions": []
},
"targets": {
"ai": {
Expand Down Expand Up @@ -306,7 +296,6 @@
"workflows": [
{
"action_policy_preflight": true,
"locked_policy_dependency_required": true,
"matrix_independent": false,
"path": "phpunit-feature.yml",
"required_check": "PHPUnit feature suite"
Expand Down
5 changes: 0 additions & 5 deletions qualification/requirements.lock

This file was deleted.

15 changes: 1 addition & 14 deletions qualification/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -316,17 +316,7 @@
"type": "object"
},
"pull_request_target_exceptions": {
"items": {
"additionalProperties": false,
"properties": {
"target": {"const": "github-control-plane"},
"path": {"const": ".github/workflows/protected-policy.yml"},
"sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}
},
"required": ["target", "path", "sha256"],
"type": "object"
},
"maxItems": 1,
"maxItems": 0,
"type": "array"
}
},
Expand Down Expand Up @@ -377,9 +367,6 @@
"workflow": {
"additionalProperties": false,
"properties": {
"locked_policy_dependency_required": {
"type": "boolean"
},
"action_policy_preflight": {
"type": "boolean"
},
Expand Down
Loading
Loading