The initial Ubuntu 26.04/x64/PHP 8.3 source recipe uses the exact asset and
metadata recorded in php-linux-initial-archive.json, with the closed map in
php-linux-initial-normalization.json. scripts/php_linux_normalize.py copies
only contained regular targets, including their file metadata, into a sealed
regular-only archive. The strict root-input inspector continues to reject raw
links, sparse members, special files and path collisions. The producer writes
a complete archive-derived manifest before installation; receipt v3 binds its
object and bytes, original and derived archive identities, and alias target
provenance. Terminal qualification retains and checks that same manifest before
and after both PHAR probes, then removes exact owned state before publishing PASS.
The separate Ubuntu 24.04/x64/PHP 8.3 normalization source is recorded in
php-linux-ubuntu24-archive.json and php-linux-ubuntu24-normalization.json.
Selection requires the complete pinned asset identity, not a caller-supplied
map or path. Its 685 links have 637 contained regular targets and 48 explicit
directory or unresolved omissions; the derived inventory has 3,215 files and
373 directories. The source setup gate requires the exact Ubuntu 24.04 or
26.04 x64 host and matching complete archive identity, PHP 8.3, both literal
PHARs, coverage none and no extension override. Other catalog tuples refuse.
The accepted installed runs recorded in
#297 covered the
earlier Ubuntu 26.04 and Ubuntu 24.04 assets, which upstream has since replaced.
Each consumer still needs its own source binding and actual caller qualification.
The scheduled Check PHP setup pins upstream workflow
(php-linux-upstream-pins.yml, daily and manual) fails when any pinned archive
ID is no longer published with the same name, size and digest, or when
install.sh bytes change. Its step summary lists each affected row, whether it
is a setup-profile row, and the current same-name candidate ID, size and digest.
shivammathur/php-ubuntu replaces its builds release assets in place, and a
replaced asset ID returns 404. Setup never falls back to a newer asset. To
re-pin, download the new asset by ID, check its size and SHA-256 against the
release API digest, and update php-linux-bootstrap.json. The catalog pins
only the archives setup uses (HOST_ARCHIVES), so every row needs re-pinning. Then rebuild each
setup profile's records in place from that exact local file:
python3 -I -S -B scripts/php_linux_profile_records.py 24.04 < ARCHIVEThe command refuses bytes that differ from the catalog. It prints the record
hashes and counts for SOURCE_PROFILES. Copy the new metadata hash into the
matching alternatives inventory, then update INVENTORIES, PROFILES and
HOST_ARCHIVES to the new asset ID. Before merging, review the member and
link differences against the previous records.
Receipt v4 additionally binds the initial CLI configuration. It derives the
production INI from the held archive's php.ini-production.cli and the exact
pinned PHP, xdebug and JIT configuration fragments. The bounded input document
is captured before configuration writes. Only CLI php.ini, CLI 99-pecl.ini
and the explicit production marker are written; only inventory-bound CLI
xdebug/pcov startup entries are removed. The derived upstream helpers leave
other SAPIs unchanged and do not consult an ambient current-INI marker.
Terminal verification independently recomputes these outputs from the held preimage and pinned source, checks the complete active CLI inventory, and holds the configuration document through both PHAR probes and cleanup. Current live qualification requires v4. Retained legacy readers and source controls do not qualify a new installed original. A changed effect packet and distinct run selection remain necessary before executing this source.
The fixed updater's writer inventories are in php-linux-initial-alternatives.json
and php-linux-ubuntu24-alternatives.json. They bind their respective metadata
hashes and the same exact stock writers. The generated installer bytes remain
unchanged. Setup and terminal verification pass the selected original asset
through the CLI configuration and alternatives checks; a document's profile
label cannot choose its own expected configuration. Post-probe alternatives
verification retains a copy of that same asset identity.
Nine archive-materialized alternatives paths are master links created by
fix_alternatives. Extraction excludes those exact paths, leaving their
creation to the existing updater after extraction. Both link hops and the
held versioned target bytes are verified before PHP and again before and after
the terminal probes. Registration alone retains higher-priority automatic or
preselected manual alternatives. After registration completes, the initial
recipe explicitly selects only those nine held versioned targets with the
stock updater and refuses any selection failure. Other registered options and
unrelated groups remain intact. The other eight alternatives aliases remain regular
copies: the library alias and seven plain manpage aliases have no writer in
that function. Its seven gzip-manpage slave operations use different names;
the socket operation has no contained regular target. Those existing operations
are retained without treating them as authority for another archive alias.
The focused source controls use inert archives, the stock alternatives updater and temporary paths. They reproduce the regular-alternatives failure and check the corrected extraction/updater/selection order, including higher-priority, manual and already-selected choices, but do not establish installed PHP configuration, module, loader or PHAR behavior. Historical installed originals remain NONPASS. The live manual workflow keeps its immutable helpers; successor adoption and a fresh installed qualification require their separate source and effect decisions. Other host, PHP, tool and consumer tuples do not inherit this initial profile.
policy.json is the reviewed organization policy for GitHub Actions references
and workflow trust boundaries. Product repositories load it during pull-request
checks and validate their own checked-out workflow files.
The policy binds allowed Action releases to immutable commits and records the supported JavaScript runtimes. It also defines the restrictions applied to privileged workflows, artifact handoffs, token permissions, caches, containers, and pull-request execution.
Validate any checked-out public repository without GitHub credentials:
python scripts/qualification_policy.py validate \
--target server \
--workflow-directory ../server/.github/workflowsEvery governed product repository runs this validator in GitHub Actions. It fails when a workflow uses an unknown or mutable Action reference or crosses a trust boundary that the policy does not allow.
Workflows must declare read-only or empty top-level token permissions. Write
access is job-local, and pull-request jobs cannot use environments, secrets, or
privileged caches. The sole secret exception is an isolated Sonar analysis job
on a branch in the same repository: a credential-free checkout and the official
scanner, with the analysis token only on the final scanner step. It may produce
coverage first using trusted repository code and approved actions. Other steps
cannot reference secrets; existing cache restrictions still apply. Forks do
not run this job.
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, mutable container
images, unreviewed workflow_run consumers, and privileged artifact consumers
without exact producer and digest provenance.
A privileged workflow_dispatch job must be limited to the protected branch.
The only exceptions are listed in privileged_tag_dispatch_workflows: one named
workflow, its named jobs and one environment. Each listed job needs an
unconditional, unprivileged plan job and carries the exact
github.ref == format('refs/tags/{0}', inputs.<tag>) predicate as a top-level
condition, so a release runs from its own tag ref and a wrong ref fails in the
plan job. The workflow may have no other trigger.
An explicitly optional binding can be absent; every present job still satisfies
its full trust binding. optional_jobs allows only named, registered jobs to be
absent. These presence exceptions support the Server alias-promotion rollout:
accepted Server retains its legacy release alias job while the successor moves
that job into a separate protected workflow. Tighten the bindings after Server
accepts the successor. Neither option admits an unregistered privileged job or
relaxes ref, environment, plan verifier or failed-plan checks.
Product repositories own their tests, documentation deployment, publication, and deployment workflows. Editorial wording is outside this policy.
A host verifier cannot establish the trust used by Composer inside a build or
running container. The container must run verify-composer-trust/verify.php
against its own Composer binary and final project configuration, immediately
before each Composer command. Use the authenticated CA file and an empty
additional-root directory inside that same request boundary.
verify-composer-container-trust prepares inputs for registered container
requests. It checks the actual Git checkout and selected commit, compares the
Dockerfile and preparation files with an explicitly reviewed recipe, and checks
the bootstrap's CA digest against the actual file. It returns the verifier
context directory, CA file and digest, source commit, and Dockerfile digest. Its
provenance contains identities and hashes without environment or credentials.
The entire build context must match the selected Git tree, including raw file
bytes and executable modes. Tracked, staged, untracked and ignored changes
refuse. Docker exclusion files must be part of the reviewed recipe. New clean
product commits can use the same recipe while its build instructions remain
unchanged.
Give the action the actual compose-directory. It renders the registered local
Compose files with --env-file /dev/null and verifies every registered service's
build context, Dockerfile, stage, verifier context, source arguments and CA secret.
The immediately following build uses the same directory and the action's outputs
for all trust/source variables and COMPOSE_FILE. The central caller contract
requires that exact command and refuses an omitted, conditional, ignored or stale
guard. No rendered application configuration is printed or retained.
Its ca-file and ca-sha256 inputs must reference outputs of the same earlier,
unconditional reviewed bootstrap-patched-pip step. Ambient or step-local CA
values cannot supply that identity. Additional Docker, Compose or Buildx build
commands in the registered job also refuse, including calls in shell helpers.
The guard stages only that authenticated public CA into a new owned mode-0700
directory inside the Compose tree. It verifies a single regular, non-symlink
source object and the original SHA-256, writes a mode-0400 copy, and binds the
rendered secret to that copy. Staging stays outside the Server and verifier
image contexts. This satisfies Bake's workspace filesystem boundary without
granting extra filesystem access or changing which store is trusted.
The authenticated original must remain under the owned RUNNER_TEMP; staging
and cleanup stay under the owned GITHUB_WORKSPACE. Both Python entry points
ignore workspace imports and site configuration. Directory descriptors hold
the selected parents while reading or removing the copy; aliases and changed
parent, state or file identities refuse before removal.
After the registered stack is stopped, an always-run caller cleanup step uses the same guard's source and outputs, when present, to remove only the copy:
if [[ -n "$STAGED_CA" ]]; then
python3 -I -S -B "$VERIFIER_CONTEXT/../../../scripts/composer_container_trust.py" cleanup-ca \
--ca-file "$STAGED_CA" --ca-sha256 "$CA_SHA256" --compose-directory .
fiHere VERIFIER_CONTEXT, STAGED_CA and CA_SHA256 are the guard's
context-directory, ca-file and ca-sha256 outputs. Cleanup refuses another
path, modified bytes, symlinks, hard links or unrelated directory contents.
The bootstrap's original selected bundle remains intact. A guard failure
cleans its staged copy before publishing any outputs.
Container verification uses only its dedicated composer-container-trust
release. The isolated verifier at 06efa32de898cff5766ef9f3a9db6a368fced981
replaces the retired d12a1e2 container release. Older clean-trust releases,
including 699d09d, retain their other approved action paths but cannot supply
the container verifier. Callers must adopt the corrected verifier and complete
their actual build qualification.
The sdk-php-cooperative-server entry in composer-container-trust.json
applies only to its registered Compose request and reviewed Server recipes.
It supplies no trust for other build or runtime paths.
Its additional Server 3.8.0 recipe comes from published source
28f084513235f6647e740c4b3d9e3aace7a323b7. It binds the fixed APT parent
Dockerfile, lockfile, copied/executed docker/apt/require-fixed-apt.sh and
docker/apt/parent.v1.json proof alongside the existing preparation inputs.
The older recipe remains available. Source matching alone does not qualify
the caller: its clean predecessor, live in-container verifier and actual
registered Compose request remain required.
The composer-container-trust action-release role approves only the container
verifier path. It cannot supply the clean bootstrap or host Composer verifier.
The container request still binds its CA file and digest to an earlier,
independently approved clean bootstrap.
The container control runs the actual digest-pinned Composer image against a local synthetic TLS registry. The ambient store accepts its synthetic blocked root. The verified clean store refuses before an HTTP request. Wrong bundle digests, extra roots and a missing verifier also refuse. This exercises the container client; BuildKit mount lifetime and final-image inspection remain part of the Server hook's combined qualification.
The catalog admits only Waterline's corrected PHP 8.2 build launcher in
runtime_setups, with read-only controls, twelve exact source bindings and
canonical Git modes. Its final hosted caller qualification remains separate.
The earlier writable Sample App, CLI and Waterline records were withdrawn after the
writable control-path finding.
Their repository/state mounts allowed container code to replace a host launcher
or wrapper before a later nominal verifier invocation. Historical source or TLS
passes do not qualify that boundary. Whole-target Composer enforcement remains
in place, so the withdrawn setup cannot authorize dependency requests.
Replacement source must mount repository/control and wrapper/launcher paths read-only, expose only required data/cache/output paths as writable, and pass actual-container predecessor-failing replacement controls. Each corrected recipe needs exact file hashes/canonical Git modes, closed jobs/commands/matrix, selector and exclusion boundaries, live in-container Composer verification and its own caller qualification. Existing container-build recipes and the bound host PHP helper are separate and remain unchanged.
For the initial Ubuntu 24.04 or 26.04 x64 PHP 8.3 production profile, the policy
recognizes this dependency-request order after checkout. Use a literal supported
runner. The immutable setup entry approves only the setup-php-linux action path;
it does not approve other actions at that revision.
- uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 # clean-trust
with:
clients: python,composer,curl,git
- uses: durable-workflow/.github/.github/actions/setup-php-linux@411a14ac07d32073eed6ab4977f2825eba4739a7 # bound-linux-php
with:
php-version: '8.3'
tools: composer:2.10.3,phpunit:11.5.57
coverage: none
- uses: durable-workflow/.github/.github/actions/verify-composer-trust@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 # clean-trust
- run: composer install --prefer-dist --no-interactionThe bound setup requires the Python trust client as well as Composer, curl and Git. Its source contract requires those literal PHP/tool/coverage values, production INI and no extension or INI-value override. The live verifier remains required immediately before each dependency-request step. This policy entry is source support for migration, not an installed caller result: retain independent post-product setup revalidation and both setup/private-pip cleanup, and qualify the actual consumer before release or publication.
The bootstrap authenticates the pip wheel by SHA-256 before exporting its
verified CA bundle. It selects both a file and an empty directory because pip's
default truststore can otherwise add roots from the operating system even when
PIP_CERT points to a clean file. An offline localhost regression checks that
counterfactual with the actual pip process.
The live verifier loads the installed Composer PHAR without executing project
plugins. It checks Composer's version, selected file and directory, configuration,
bundle hash, parsed certificates and enabled TLS before the next dependency
request. Use its working-directory input when the project is in a subdirectory.
It reports trust identities without dumping configuration or credentials.
Select only the clients a job uses. npm and cargo add their own package-client
overrides. The action does not make raw Node's compiled-in store safe, and it
does not use additive NODE_EXTRA_CA_CERTS. Node runtimes and native Windows
certificate backends need their own qualified release or trust configuration.
The pinned action download and hosted runner bootstrap remain a trust boundary.
The real npm and Cargo controls use a synthetic blocked root on localhost. They
check that the raw/additive configuration reaches the test registry and that
the exported trust refuses before an HTTP request. Cargo uses
CARGO_HTTP_CAINFO together with the common file and empty directory. The
bootstrap does not set CARGO_HTTP_PROXY_CAINFO: Cargo 1.86 refuses that setting
when its proxy is not configured. Unused Go overrides are not advertised.
For migrated targets, composer_trust_required makes the normal validator scan
every job in every workflow, including referenced repository shell helpers. A
Composer command requires the reviewed bootstrap and fixed Composer setup, with
an unconditional live verifier immediately before its request step. Only explicit
version/help/list/about operations are treated as local. New subcommands and
script aliases require verification by default. Literal shell helpers, including
extensionless and nested helpers, are inspected for requests and trust mutations.
Python/PHP test data is not interpreted as shell code.
Unknown pins, omitted jobs, ignored guards, intervening steps and trust overrides fail. The check covers authored literal Composer calls and repository shell helpers. It is not a shell sandbox, a multi-language program analyzer or a claim about third-party actions' internal requests. Maintainers still review dynamic request paths and their actual configuration. Enable the target setting after migrating all its request paths so partial rollouts remain visible.
Before that whole-target migration, composer_trust_scopes can name a workflow
and job for the bound initial PHP profile. The job is checked when it adopts
the reviewed setup action, including every enumerated dependency request and
its immediate live verifier. Other jobs retain their previous scope; a verifier
outside the named migration still enables the existing whole-repository checks.
Targets already requiring whole-target Composer trust cannot use scoped settings
to reduce that enforcement.
Workflow's release-verification.yml / published-package entry now uses
required: true after the reviewed caller wiring reached maintained Workflow
main at 2de438d4dcff8f213253603e608324272eeac8e5 (PR1055). This is source
presence/enumeration enforcement, not installed caller acceptance; selecting an
actual caller still requires its separate qualification and terminal cleanup.
A required entry refuses a missing workflow, missing job or missing reviewed
setup. Ambiguous
workflow identities, invalid scopes and a migrated job without an enumerated
dependency request also refuse. The remaining Laravel matrix stays separately
unqualified until its own applicable profiles and callers are delivered.
The selective successor to the reverted pilot
keeps ordinary consumer preflight and Composer helper interpretation at the
accepted rollback behavior. Its shared-policy changes allow only the pinned
App-token action and the byte-exact protected workflow. The stricter input
language lives in scripts/protected_policy_context.py, called only by the
protected reader before the shared validator. Candidate files remain bounded
nonexecutable data; the reader and issuer run from the protected base.
The pilot accepts new run text only as newline-separated literal commands with
fixed interpreter forms. Shell expansion, operators, escapes, inline environment
assignments, evaluator options and constructed command names refuse. Literal
data flags such as --source remain supported. Referenced shell helpers obey the
same language, including when bash reads a file with a misleading extension.
Python helper text remains data; this is not whole-program behavior analysis.
Five existing control workflows contain complex shell programs. Their complete raw file SHA-256 identities are a finite compatibility catalog in the protected checker source, not candidate-supplied assertions or a general Bash parser. A changed file, changed execution context, relocated copy or new complex program cannot inherit that exception; updating the catalog requires exact source review. The catalog does not exempt referenced shell-helper bytes, root-directory checks or interpreter/startup environment checks. The retained cancellation workflow is a source compatibility identity, not admission to run its fixtures.
Relative helper references require the workspace root and a reviewed shell. YAML traversal bounds depth and expanded alias work and refuses recursive aliases, duplicate keys and merges. These conservative protected-pilot rules do not change ordinary caller behavior. Run the dedicated controls with:
python -m unittest tests.test_protected_policy_context tests.test_protected_policy_check tests.test_protected_policy_inputs tests.test_protected_policy_issueIntegration also requires the Rust analytics checker's source-root correction and actual old/new execution controls. The visual workflow keeps the candidate build in its existing directory, then invokes that checker from the workspace root with an explicit build directory. The SDK owner must prove that the checker resolves its own source assets and preserves all content assertions. A passing central context test does not substitute for that upstream compatibility proof.
The source remains inactive. No App/key, protected variable or secret, environment, event-policy setting, required rule or live check is provisioned. The existing AI contribution issue waits for accepted source and a separately qualified one-repository pilot. Human account/key authorization and the live positive/negative, expected-issuer, concurrency and rollback controls remain separate prerequisites.