Skip to content

Start every Server pip install from the patched, bundle-only pip (GHSA-6q48-6c7w-9gw2) - #673

Open
rmcdaniel wants to merge 24 commits into
mainfrom
rust-325-trust-pip
Open

rmcdaniel wants to merge 24 commits into
mainfrom
rust-325-trust-pip

Conversation

@rmcdaniel

@rmcdaniel rmcdaniel commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Server's part of the GHSA-6q48-6c7w-9gw2 package-manager trust remediation. pip, Composer and Cargo in Server CI and the active conformance scripts now verify TLS only against CA stores checked to exclude AAA Certificate Services and emSign ECC Root CA - G3. Every inventory fails closed. A pip or Composer command that is not in its reviewed manifest fails the Repository contracts job, and so does a reviewed command whose recorded guard has been removed.

Out of scope, with an explicit non-completion boundary: PECL, install-php-extensions, curl/wget, Python and Node HTTP downloads, and Cargo outside StorageAdmission. These move to the separately owned #677.

pip

Why the bundle check alone is not enough. pip ≥24.2 trusts the OS store (via truststore) and its own certifi bundle. Several images still ship an affected OS store: the pinned python@sha256:bf44cdfc… and current python:3.12-slim/3.13-slim hold 150 roots with both blocked roots. So the venv or job also selects pip's legacy-certs mode. In that mode pip builds no truststore context and verifies only against the checked bundle (or PIP_CERT).

New helpers (scripts/ci/)

  • bootstrap-patched-pip.sh VENV_PYTHON installs pip @ …pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa…, the same pin as the central action. It then:
    • requires version 26.2.2;
    • refuses a bundle holding either root;
    • writes the venv's pip.conf with use-deprecated = legacy-certs.
  • patched-pip.sh ARGS… runs pip from a fresh bootstrapped venv, for containers whose own pip is unpatched or not writable. Arguments pass through unchanged, pip's exit status is kept and the temporary venv is removed.
  • blocked_roots.py BUNDLE… is the standard-library root counter used by the helpers and the workflows.

Inventory

Path Before Now
phpunit-feature.yml preflight pip install PyYAML Central bootstrap-patched-pip@75587fc3 (clean-trust, the Security-accepted .github #239 head) at the policy's step 4. It exports the checked bundle as PIP_CERT and SSL_CERT_FILE with an empty SSL_CERT_DIR, so pip and its truststore see only that bundle.
helm-chart-checks.yml lint job system python3 -m pip install PyYAML setup-python, central bootstrap, then python -I -m pip with PIP_USE_DEPRECATED: legacy-certs as step env. -I keeps a checked-out pip package from shadowing the installed client.
helm-chart-checks.yml chart-testing job chart-testing-action's own venv pip Central bootstrap. The action step gets PIP_CERT (the bootstrapped pip's checked bundle under pythonLocation) and legacy-certs as step env, so its venv pip uses only that bundle. Static step env, not a GITHUB_ENV write, because CodeQL flags writing computed values there after a dispatch-supplied checkout.
server-perf.yml contract job system python3 -m pip install -r requirements.lock (no hashes) setup-python, central bootstrap, pip variables, then python3 -I -m pip install --require-hashes --only-binary :all: --no-deps. The capacity lock now carries every PyPI file hash for each pin, and capacity_suite.py refuses an unhashed line (Sonar S8541/S8544).
server-parity.yml published-SDK adapters container pip, hash-locked patched-pip.sh, from the new published-sdk-patch-adapters.sh like its sibling adapter builds (the Docker isolation policy only accepts helper calls from scripts)
4 adapter scripts (corrected-marker, legacy-marker, query-authority, sdk-direction) container pip, hash-locked patched-pip.sh
activities, child-workflows, skew, workflow-lifecycle, workflow-updates, namespaces, python, replay, sagas published-artifact scripts venv pip, several with pip install --upgrade pip bootstrap-patched-pip.sh right after the venv; the --upgrade pip calls are removed. workflow-updates runs pip --isolated, so its install passes --use-deprecated=legacy-certs directly. A failed bootstrap stops the python and replay runners before any download.
perf/run-namespace-isolation.sh venv pip bootstrap-patched-pip.sh
nexus-published-artifacts.sh Python service python:3.12-slim venv pip Helpers copied into the run root; bootstrap before the SDK install
UpdateRequestKeyConsumer/compose.yml client venv pip Helpers mounted read-only; bootstrap before the SDK install
Capacity benchmark Python worker (local-docker/compose.json, run by server-perf.yml) python:3.12.4-slim-bookworm pip 24.0 Bootstrapped venv first on the service's PATH; helpers mounted read-only. It installs the hashed lock with python3 -I -m pip --require-hashes --only-binary :all: --no-deps. docker exec … python3 and the healthcheck resolve to the venv.
worker-versioning-published-workers.mjs venv pip install --upgrade pip, then SDK install bootstrap-patched-pip.sh on the venv
heartbeats-published-artifacts.mjs container python -m pip download/install patched-pip.sh with the helpers mounted read-only
schedules-published-artifacts.mjs (lifecycle and cross-language shards) venv pip install --upgrade pip bootstrap-patched-pip.sh
signals-queries-published-artifacts.sh, principal-attribution-published-artifacts.sh, child-workflows-install-probe.py venv pip upgrade or direct SDK install bootstrap-patched-pip.sh first; the child probe records the bootstrap command in its evidence
worker-versioning-mixed.Dockerfile floating composer:2 and node:22-slim; system pip on python:3.13-slim Composer 2.10.3 and Node 22.23.4 pinned by digest. ca-certificates is refreshed through apt (signature-checked) and must hold 0/0 before any TLS download. The SDK goes into a bootstrapped venv first on PATH.
StorageAdmission/sdk/Dockerfile (manual experiment) venv pip inside the Server image Helpers arrive as a named ci build context (additional_contexts in its compose file); the venv is bootstrapped before the SDK install.
docs/evidence/** historical Unchanged

Central action and checkout ref. All four uses of the central action (phpunit-feature, server-perf, both helm-chart-checks jobs) move from ff93e15d to 75587fc3. helm-chart-checks.yml no longer takes a source_ref input. Both callers (helm-chart-validation.yml, helm-chart-release.yml) passed github.sha, so checkout now uses the caller's commit with persist-credentials: false. That removes the dispatch-shaped ref dataflow CodeQL flagged (actions/cache-poisoning/poisonable-step) rather than suppressing it. helm-chart-publish.yml keeps its source_ref, because release and recovery publish an exact release commit.

Isolation. Every networked -m pip that can run from checked-out or mounted source runs as python -I -m pip. That covers patched-pip.sh, the capacity worker, every conformance runner and the server-perf and helm jobs, so a checked-out pip.py or pip/ cannot shadow the bootstrapped client. The inventory rejects any -m pip without -I in shell, Python-list, split-list and JS-array forms. Structured, owned exceptions are allowed. The only one is the action-policy preflight: the central policy fixes that exact command, and General has been asked to admit python -I -m pip.

Manifest. tests/Unit/Support/pip-sinks.json (schema v2) lists 42 pip sink occurrences: -m pip, pip install|download|wheel|index, the chart-testing action and the central action. For each occurrence it records a guard type, the exact guard line, and its line distance within the same script or workflow job:

  • bootstrap: an invocation of the patched bootstrap, the central action or a runner's bootstrap call; path definitions do not count;
  • pinned-wheel: the hash-pinned wheel in the same command;
  • self-checked kinds: comment, assertion message, evidence record, version query and the accepted action itself.

sink_guards.py checks each guard against the source. A new sink, a second copy of a reviewed one, or a deleted or moved guard all fail.

Composer

Every executable Composer image is composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee. Its store holds 118 roots and neither withdrawn root. That covers 59 former composer:2, composer:2.8@… and composer:2.10.2 references in scripts, workflows, perf Dockerfiles and the capacity topology. The environment and build-argument overrides that could swap the image are gone: DW_HEARTBEATS_PHP_IMAGE, DW_SIGNALS_QUERIES_PHP_DOCKER_IMAGE and Dockerfile.sdk's COMPOSER_IMAGE. The remaining runtime-only override, DW_SKEW_PHP_IMAGE, runs php with no package request.

Where Composer must run outside that image:

Path Confinement before the first package request
Audits in phpunit-feature.yml and release.yml; capacity PHP binding install in server-perf.yml Moved off the runner into the pinned image. The runner's libcurl has a compiled CA directory, so env alone would not confine it.
Nexus PHP service and the signals/queries Waterline observer (published Server image under test) checked-ca.sh writes the pinned image's store, checked by blocked_roots.py, and an empty directory. The container gets COMPOSER_CAFILE and COMPOSER_CAPATH. A CA file alone would add to curl's compiled directory.
Activities handoff into the published Server image The same pair, read from the pinned image directly so a runner extracted without scripts/ci still confines Composer
Server-parity install scripts; activities and workflow-updates in-repo installs; qualify-database-tls.sh source mode composer-trust-guard.sh refuses first: it checks COMPOSER_CAFILE (requiring an empty COMPOSER_CAPATH), or the system bundle and hashed directory when no CA file is set. The server-parity profiles also pass the checked pair.
Octane swoole/openswoole/frankenphp perf images; StorageAdmission SDK final stage COMPOSER_CAFILE copied from the pinned stage, empty COMPOSER_CAPATH
Repository Dockerfile, perf Dockerfile.sdk/-current-experiment Pinned build stage, or the Server base checked by server-verify-trust
worker-versioning-mixed.Dockerfile (Composer copied into a Python image) COMPOSER_CAFILE from the pinned stage, checked by blocked_roots.py, with an empty COMPOSER_CAPATH
Corpus validator and prepared-local images in phpunit-feature.yml Server base image: server-verify-trust final checks it at build, and the validator reruns it after adding git
Parity installs of SDKs, probe dependencies and a pinned Server source: three server-parity.yml steps, corrected-marker-adapters.sh (two), range-repair-profile.sh, sdk-direction-profile.sh, query-authority-cohort.sh and worker-fencing-profile.sh (two) scripts/ci/run-composer.sh. Composer runs in the pinned image after Alpine's fixed brotli-libs (at least 1.2.1-r0, CVE-2026-111497; brotli-safe-composer.sh checks it on every use). It also gets the checked CA file and an empty CA directory, set after the caller's options. A caller may not name either, an entrypoint or an env file. These used to run Composer inside the published images under test; their vendor directories still run there.

Manifest. tests/Unit/Support/composer-sinks.json (schema v2) lists 123 Composer sink occurrences in 105 entries. Each records a machine-checked guard:

  • pinned-image, or pinned-stage (whose FROM line must name the pinned digest);
  • checked-ca or trust-guard;
  • server-verify or server-base-build;
  • trusted-builder;
  • brotli-floor: the wrapper's image comes from brotli-safe-composer.sh;
  • self-checked kinds: pinned reference, documentation, comment, evidence record and composer-wrapper (an invocation of scripts/ci/run-composer.sh).

The manifest covers: every Composer image reference and every install, update, upgrade, require, remove, reinstall, search, self-update, diagnose, show, info, create-project, outdated, global, bump, audit, why, depends, archive or init command, including list and multi-line forms. It also covers any container whose entrypoint is Composer, wherever its subcommand sits, and reads a backslash-newline as a separator. That split docker run … --entrypoint composer \ form hid nine installs before round 5.

Cargo

CARGO_HTTP_CAINFO is additive: cargo's libcurl/OpenSSL still reads the hashed system directory, which in rust:1.86 holds the withdrawn roots. The StorageAdmission Rust stage now copies the Server image's store and checks it with blocked_roots.py. Before the first cargo command it points both CARGO_HTTP_CAINFO and SSL_CERT_FILE at that clean file, and SSL_CERT_DIR at an empty directory. A base image with a withdrawn root stops the build.

Brotli decoder (CVE-2026-111497)

Debian 13 has no fixed libbrotli1 yet, and the Server image links it through PHP, curl and Node.

  • Build. A builder stage on the runtime's pinned base (php@sha256:d89e8b3d…) rebuilds Debian's own brotli 1.1.0-2 source with upstream's fix for the 1.1 line (google/brotli da98063, released as v1.1.1), applied as one quilt patch. The source comes through apt's signed Sources index and must also match docker/brotli/source.sha256. The package is 1.1.0-2+b7+dw1: above the trixie build it replaces and below any future Debian fix (+deb13uN). The runtime installs only that libbrotli1.
  • Check. server-verify-brotli runs in the base and again after production's last package change. It requires:
    • that package version, owned by libbrotli1 according to dpkg -S;
    • that every libbrotlidec* path, file or symlink, resolves to that package's library (docker/brotli/decoder-inventory.sh); a link to a differently named decoder, a dangling link and a separate copy are refused;
    • that every linked object loads that library;
    • a fixed Node release (v22.23.4 carries the cherry-pick);
    • that the decoder reports the fix's new error code, and that a valid stream round trips.
  • Alpine stage. The stage that clones phpredis takes Alpine's signed brotli-libs 1.2.1-r0 or later first.
  • Tests. tests/Unit/Support/brotli_trust_test.py (7 tests, Repository contracts) runs the real inventory script on a constructed tree with the package file and link, a counterfactual link to a differently named decoder, a dangling link, a copy and a non-regular expected library. A mutation back to a files-only search fails the counterfactual and dangling-link cases. CI's image build runs the full verifier on the real image.

Verification (local, in containers)

  • tests/Unit/Support/patched_pip_test.py (16 tests, Repository contracts):
    • Real-pip negative control. pip 26.2.2 is bootstrapped from the pinned wheel. A local HTTPS server is signed by a synthetic AAA Certificate Services root that is present only in the "OS store" (SSL_CERT_FILE, 1/1); pip's bundle stays clean (0/0). Unconfined pip reaches the server, which proves the setup is adversarial. The bootstrapped venv's pip and the job-level PIP_CERT + PIP_USE_DEPRECATED=legacy-certs form both refuse with CERTIFICATE_VERIFY_FAILED, and the server records no request.

    • patched-pip.sh keeps pip's exit status (0 and 1) and leaves its temporary directory empty.

    • The root counter refuses each withdrawn root and an empty bundle. The bootstrap installs the pinned wheel and writes legacy-certs, and refuses another pip version, a bundle with a withdrawn root, a non-venv interpreter and a failed install.

    • Poisoned working directory. In a directory whose pip.py and pip/ package write a marker, an unisolated -m pip runs the checked-out package. That is the control. patched-pip.sh and the bootstrapped venv's -I -m pip both report pip 26.2.2 and never write the marker.

    • Manifest. Each of these fails the review:

      • a sink mentioned only in a comment;
      • pip wheel and pip index;
      • -m pip with expanded arguments;
      • a workflow step that only echoes pip commands;
      • a second sink added to a reviewed script;
      • deleting the bootstrap before the sagas install while the sink line is unchanged;
      • retyping its guard.

      The isolation scan rejects an unisolated -m pip in shell, list, split-list and JS forms, and accepts -I, comments and an exact owned exception. --isolated pip commands must carry legacy-certs.

  • tests/Unit/Support/composer_trust_test.py (16 tests, Repository contracts with DW_REQUIRE_COMPOSER_TRUST_TEST=1):
    • Real-Composer control. A container adds a synthetic AAA Certificate Services root to its system store with update-ca-certificates, then runs composer create-project --repository=https://127.0.0.1:PORT. Unconfined Composer reaches /packages.json, which proves the setup is adversarial. With COMPOSER_CAFILE (a clean root) and an empty COMPOSER_CAPATH, Composer refuses and the server records no request.
    • The pinned image's store holds neither root. checked-ca.sh writes the checked file and an empty directory.
    • Every Composer image is the pinned digest. Every sink is in the manifest with the same count and a guard that holds in the source. Each of these fails the review:
      • a floating composer:2 image;
      • composer require on the runner after a comment claiming a pinned image;
      • composer self-update;
      • a workflow composer audit step;
      • deleting composer-trust-guard.sh before the corrected-marker install while the sink line is unchanged;
      • a pinned stage whose FROM no longer names the digest;
      • the exact split server-parity.yml install, --entrypoint=/usr/bin/composer, a composer.phar entrypoint, a backslash-continued composer … install and another run-composer.sh.
    • Wrapper and floor.
      • brotli-safe-composer.sh --check refuses the pinned image itself ("lacks brotli-libs>=1.2.1-r0"; it ships 1.2.0-r1) and accepts the floored image.
      • run-composer.sh refuses COMPOSER_CAFILE, COMPOSER_CAPATH, --entrypoint and --env-file options, and a missing --. A docker stub proves it refuses before any docker call.
      • An offline path-repository install through the wrapper runs as the caller, and composer config cafile/capath report /checked-ca/ca.pem and /checked-ca/no-ca-directory.
  • tests/Unit/Support/cargo_trust_test.py (4 tests, Repository contracts with DW_REQUIRE_CARGO_TRUST_TEST=1), with real cargo (rust:1.86) against a local sparse registry signed by a synthetic withdrawn root in the hashed directory:
    • Unconfined cargo trusts it.
    • CARGO_HTTP_CAINFO alone still trusts it.
    • CARGO_HTTP_CAINFO plus an empty SSL_CERT_DIR refuses.
    • The StorageAdmission Rust stage sets CARGO_HTTP_CAINFO and SSL_CERT_FILE to the exact clean path and SSL_CERT_DIR to the empty directory, and checks the file, before its first RUN cargo.
  • composer-trust-guard.sh: Server 2.11.0 passes; the frozen reference image 28fa0f… fails (bundle 1/1, directory 3/3) and passes once confined to the checked pair.
  • The StorageAdmission SDK image builds from this head against Server 2.11.0 (sha256:b29157eb…), and the build log shows each check firing. The Rust stage checked the Server store (148 roots, 0/0) and the empty directory before cargo resolved and compiled durable-workflow 2.0.5. composer require durable-workflow/sdk:2.0.9 succeeded with only the pinned image's store and an empty directory. The venv's pip bundle held 120 roots (0/0).
  • The worker-versioning mixed image builds locally against PHP SDK 2.2.6 and Python SDK 2.0.1. Composer installed through the checked file (118 roots, 0/0) and the empty directory, and the venv's pip is 26.2.2.
  • The Docker isolation policy and the central qualification policy at .github main f14766da pass, every workflow parses, and test_helm_chart_release.py passes.
  • capacity_suite.py validate passes. PHP contract tests that read the changed files fail only where the committed head fails in this environment (missing php/cargo in the Python image, the operator CLI path, a missing SQLite file). The activities handoff test now also asserts that the delegate gets the checked pair.

Outside this PR

  • Confine the remaining CI, conformance and perf downloads to checked CA stores (GHSA-6q48-6c7w-9gw2) #677 (separately owned, explicit non-completion boundary):
    • PECL and install-php-extensions in the perf Octane Dockerfiles;
    • curl/wget installer and binary downloads;
    • Python and Node HTTP downloads;
    • Cargo outside StorageAdmission (locked builds are checksum-verified by Cargo.lock).
  • Old published Server images used as test subjects keep their affected stores; Composer in them is confined above. The stores of 944c38…, cb5ecd… and the perf images 4ee8a9…, 5d435d…, 998b9d… are not yet measured.

Still open: the Server release workflow's Docker Hub token. That needs an account-side cutover, coordinated privately.

Integrated after round 5 (head 386c61e5d)

Locally on the combined tree, the Composer trust and release-audit suites pass with Docker: 26 tests, none skipped. The isolation-policy and pip suites pass: 97 tests.

Parity fixture locks (head 802f843a9, closes #703)

The corrected-markers, legacy-marker-aliases, published-sdk-patch, query-authority and sdk-directions adapters build their own Cargo.lock with cargo build --locked. Each still resolved rustls-webpki 0.103.15 and webpki-roots 1.0.9. Only those two packages move, to 0.103.16 and 1.0.10, with checksums identical to rust/Cargo.lock.

New tests/Unit/Support/cargo_lock_trust_test.py (feature workflow repository contracts) walks every Cargo.lock in the tree. Each one outside the listed immutable evidence must meet both floors, and the evidence lock keeps its recorded digest. With the old locks it fails and names all five.

All five fixtures build with the pinned Rust 1.86.0 image, using cargo build --locked --offline under --network none.

GHSA-6q48-6c7w-9gw2: networked pip installs in Server CI and the active
conformance scripts ran with whatever pip and trust store the runner or
image had, and several python images still trust both withdrawn roots.

scripts/ci/bootstrap-patched-pip.sh installs the hash-pinned pip 26.2.2
into a venv and refuses a bundle with either root. pip also trusts the
operating system store through truststore, so the venv's pip.conf selects
legacy-certs, which trusts only that checked bundle. patched-pip.sh runs pip
from such a venv for containers, and blocked_roots.py counts the roots.

- Runner jobs use the central bootstrap action; the Helm and perf jobs then
  pin PIP_CERT and PIP_USE_DEPRECATED to the checked bundle.
- Conformance venvs take the bootstrap right after creation and drop their
  old-pip "pip install --upgrade pip" calls.
- Hash-locked adapter installs run through patched-pip.sh.
- The worker-versioning image pins Composer and Node, refreshes the OS CA
  store through apt and asserts it before any TLS download.
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security exact-head review for d343f2c: changes requested before this can satisfy its "every Server pip install" claim.

  1. .github/workflows/phpunit-feature.yml:47 still performs python -m pip install PyYAML==6.0.2 immediately after central bootstrap-patched-pip@ff93e15d, with neither PIP_CERT nor PIP_USE_DEPRECATED=legacy-certs. The accepted finding is that this action currently checks certifi but leaves pip truststore additive with the runner OS store. This job therefore remains on the affected effective trust path until General fixes the central action/pin or policy admits equivalent confinement here.
  2. scripts/conformance/worker-versioning-published-workers.mjs:1125-1133 still creates a venv, runs an unconstrained pip --upgrade, then installs the SDK. The same routine also uses mutable composer:2 for the PHP worker. These are executable network paths; the later command: strings are only receipts.
  3. benchmarks/capacity/v1/topologies/local-docker/compose.json:288 still starts python:3.12.4-slim-bookworm with a direct python3 -m pip install and no patched bootstrap.
  4. tests/Integration/StorageAdmission/sdk/Dockerfile:20 still installs the Python SDK directly into a new venv without the patched bootstrap.
  5. scripts/ci/patched-pip.sh allocates mktemp -d and then execs pip, so every host/local invocation leaves the venv behind. Retain pip exit status but remove the exact temporary directory with a trap; disk cleanup is part of acceptance.

The initial direct wheel URL is hash-pinned and the venv-local legacy-certs approach is otherwise sound. Please add a source inventory contract that fails on uncovered executable pip install/download/upgrade paths, plus an adversarial control with an affected 1/1 OS store and a clean 0/0 pip bundle proving the actual subsequent pip command resolves legacy-certs/the checked bundle. Hosted checks are currently queued, so this is source review rather than a CI disposition.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Security CI follow-up for unchanged head d343f2c4be9860bdfa88fcfdd9cb1faa799cc455:

  • Sonar issue AaFkgplcchsX2ggju2LH (pythonsecurity:S8707) on blocked_roots.py:19 is a false positive in this context. This is an intentionally arbitrary-path local CI CLI for inspecting absolute system/venv CA bundles; its callers supply fixed workflow paths or the authenticated certifi.where() result. It has no remote/user request source, no privilege change, and a caller able to invoke it can already read the same file. General/Sonar owner should accept that one issue with this specific rationale rather than constrain the verifier to the repository and break its system-store purpose.
  • The central action policy failure is real but mechanical: the new helm-chart-checks.yml use of bootstrap-patched-pip@ff93e15d... lacks the required readable # patched-pip comment. That old action head still does not fix effective additive trust; updating to the eventual accepted Use consistent unprivileged MySQL dumps for Server backups #239 head remains required, so do not merely add the comment and call the trust path complete.
  • The feature-suite and feature-source failures are downstream of the policy failure. Its cleanup step also reports the repository cleanup script absent because the gated job never checked out source; that is a workflow cleanup robustness defect, not evidence that feature tests ran.
  • The two public-boundary failures remain real: brace commands in helm/perf are unclassified, and the patched-pip.sh helper invocation is not statically proven.

The five source/trust/cleanup blockers in the prior Security review remain unchanged.

- The capacity benchmark's Python worker installs through a bootstrapped
  venv first on PATH instead of the image's pip 24.0.
- The StorageAdmission Rust stage resolves its unlocked crates through
  the Server image's checked CA store, not rust:1.86's.
- The published SDK adapter build moves into a script, like its sibling
  adapter builds, so the Docker isolation policy can see the helper call.
- Central action pins carry their '# patched-pip' comment, and job
  environment writes use one echo per line.
- The helm confine steps no longer run a repository script; the central
  action already checks that bundle.
- New stdlib tests prove the root counter and bootstrap refuse withdrawn
  roots, other pip versions and non-venv interpreters, and that every
  pip install in CI, scripts and fixtures goes through the helpers.
- The staged replay runner test stages the bootstrap and asserts the
  runner hands it the run venv.
@rmcdaniel

Copy link
Copy Markdown
Member Author

General Sonar review of pythonsecurity:S8707 at the local blocked_roots.py CA-inspection entry point:

The reported path is intentionally a caller-selected local file. Current callers supply fixed system/venv paths or certifi.where(). This utility exposes no HTTP/request source and crosses no privilege boundary; the user invoking it already has permission to read that file. Restricting it to the repository would break its required system-store inspection. I agree with the specific false-positive disposition, without changing the rule or profile.

On current source 4954fe8aa30be1a0592ef717d9b130066ae7573f, the public PR issue API returns zero OPEN/CONFIRMED and no matching old issue key, and the updated bot report records zero new/accepted issues. No nonexistent issue transition was applied, and this is not acceptance of the remaining trust inventory or workflow gates.

Central #239 now includes the actual-pip additive-trust regression, Composer request-path contract and immutable helper admission at 4414f6fcdbb8cca75dac1358db5af9aadd8c65f6. Shared contracts and Sonar pass at 59d8e243; two maintainability findings are being corrected before final delivery. The old ff93e15d action alone remains insufficient. Please retain the separate executable-path/cleanup controls and adopt the accepted central head after its final policy delivery.

Security review of the previous head found executable pip paths the
file-level inventory missed, and a leaked temporary venv.

- worker-versioning, heartbeats, schedules, signals-queries,
  principal-attribution and the child-workflows install probe now start
  their venvs (or container pip) from the patched bootstrap. The
  worker-versioning PHP worker uses Composer 2.10.3 pinned by digest.
- The StorageAdmission SDK image takes the helpers as a named build
  context and bootstraps its venv before the SDK install.
- patched-pip.sh keeps pip's exit status and removes its temporary venv.
- The inventory test now checks each pip command (shell, Python list and
  JS array forms) and each workflow job, names the one job the central
  policy still owns, and requires legacy-certs on --isolated commands.
- A real-pip control serves HTTPS from a synthetic withdrawn root that
  only the OS store trusts. Unconfined pip 26.2.2 reaches it; the
  bootstrapped venv and the job-level PIP_CERT plus legacy-certs refuse
  with CERTIFICATE_VERIFY_FAILED.
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security re-review at exact head 3c73aa178981efc739fb78427fc28610d28a06a5: the five previously named pip paths and temporary-venv leak are corrected, and the real-pip affected-store control is the right evidence. Two new fail-open controls and the known central-action exception still block acceptance.

  1. CARGO_HTTP_CAINFO is additive on the exact Rust 1.86 image used here; it does not neutralize the image's hashed system CA directory. I reproduced this offline on rust:1.86@sha256:300ec56abce8cc9448ddea2172747d048ed902a3090e6b57babb2bf19f754081 with a local HTTPS sparse-index endpoint signed by a synthetic AAA Certificate Services root. After installing only that root into the image system store, both unconfined Cargo and Cargo with CARGO_HTTP_CAINFO=<unrelated clean root> completed TLS and reached the endpoint (expected value at line 1 column 1, not a certificate error). Pairing the clean CAINFO with an empty SSL_CERT_DIR refused before HTTP with unable to get local issuer certificate. The StorageAdmission Rust stage therefore remains exposed through its original system directory. Replace/refresh the complete store or pair the checked file with a proven empty directory, and encode this actual-Cargo positive/refusal counterfactual.

  2. The new source inventory is fail-open. Its script rule accepts any pip command if the file merely mentions bootstrap-patched-pip earlier, without proving execution, path, interpreter or venv; its workflow rule accepts unordered string presence anywhere in a job. PIP_COMMAND also omits network-capable pip wheel and pip index. I ran the current test methods against synthetic sources containing (a) only a comment mentioning the bootstrap before an unconfined python -m pip install, (b) an unconfined pip wheel, and (c) a workflow that only echoes the bootstrap and legacy-certs strings before an unconfined install. Both inventory tests passed with zero failures. Parse/track request sinks and trust state structurally, or maintain a bounded manifest of exact reviewed sinks; add these negative cases.

On the explicit Composer question: yes, pin executable Composer images before this incident-hardening PR is accepted. Current composer:2 happens to resolve to the reviewed 2.10.3 digest with a 0/0 store, but a mutable tag makes later dependency-download qualification non-reproducible. Use the already-qualified composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee, verify its effective store before the first package request, and add an inventory contract covering at least every executable install/update/require/remove/reinstall/search/self-update/diagnose/remote show path. Pinning all executable Composer references is safer than attempting to distinguish local-only fallbacks across the shell/JS runners.

The phpunit-feature/action-policy old-central-action exception remains explicitly owned by .github #239; it must move to the eventually accepted central head before final acceptance. Hosted checks on this Server head are still nonterminal.

Comment thread tests/Unit/Support/patched_pip_test.py Fixed
Comment thread tests/Unit/Support/patched_pip_test.py Fixed
Comment thread tests/Unit/Support/patched_pip_test.py Fixed
Comment thread tests/Unit/Support/patched_pip_test.py Fixed
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security review of exact-head CodeQL failures 130/131 (actions/cache-poisoning/poisonable-step): these are not infrastructure noise. The query sees the reusable workflow check out inputs.source_ref under an externally triggerable workflow_dispatch caller and then execute poisonable Python steps with default-branch cache-write scope.

The current repository has only two callers of helm-chart-checks.yml, and both pass ${{ github.sha }}. The reusable source_ref input is therefore unnecessary: omit the ref override so actions/checkout uses the caller commit, remove the input and the two caller arguments, and retain persist-credentials: false. This removes the attacker-shaped ref dataflow instead of suppressing a high-precision security query. Also invoke later pip requests as python -I -m pip ... so a checked-out pip package cannot shadow the installed client.

This CodeQL failure is an additional acceptance blocker alongside the Cargo effective-directory bypass, fail-open pip inventory, floating Composer images and old central action pin already recorded in review 6347131546.

CodeQL flags a step that runs python and writes its output to
GITHUB_ENV after a dispatch-supplied checkout. The helm jobs now set
PIP_USE_DEPRECATED (and, for chart-testing-action's own venv, PIP_CERT
to the bootstrapped pip's checked bundle) as static step env instead.
The test's pip-command pattern can no longer backtrack, and its local
TLS server requires TLS 1.2 or later.
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security re-review at exact head 87818ac5db52b069acd2e763d9187dc168c38ea2: this is a useful partial correction, not yet an acceptable trust boundary.

Accepted in this delta:

  • The new TLS 1.2 floor is appropriate.
  • I reran the exact real-pip affected-store counterfactual with only PIP_USE_DEPRECATED=legacy-certs (no PIP_CERT): the synthetic affected OS root recorded zero requests and pip refused with CERTIFICATE_VERIFY_FAILED. That step-level mechanism is sound.

Still blocking:

  1. The source inventory is still demonstrably fail-open. The four repository inventory tests pass, but the exact current methods also accepted all three synthetic unsafe inputs: a comment mentioning bootstrap-patched-pip before an unconfined install; python -m pip wheel; and a workflow that merely echoes the bootstrap and legacy-certs strings before an unconfined install. Tightening separator backtracking did not establish execution order/state and PIP_COMMAND still only recognizes install|download. The negative cases from review 6347131546 must be encoded and made to fail before their fixes.
  2. The CodeQL-controlled-ref shape remains: source_ref is still a required reusable-workflow input and both checkouts still use it. The two callers both pass github.sha, so remove the unnecessary input/ref override and caller arguments as previously requested. The checked-out-source pip invocation also remains python -m pip, not python -I -m pip. Avoiding one dynamic $GITHUB_ENV write does not remove the attacker-shaped checkout interface.
  3. The StorageAdmission Rust image still sets only CARGO_HTTP_CAINFO; it has no empty SSL_CERT_DIR. The exact-image actual-Cargo test already proved that CAINFO alone continues to trust the affected system hashed directory.
  4. Executable Composer paths remain on mutable composer:2/other mutable tags, including package-request paths. Pin them to the already-qualified 2.10.3 digest and prove the effective store before the first network request.
  5. Four workflows still pin the known-insufficient central action head ff93e15d; adoption waits for the accepted final .github Use consistent unprivileged MySQL dumps for Server backups #239 head. The explicit phpunit-feature/action-policy exception cannot be final acceptance.

Hosted checks on this head are still nonterminal. Security acceptance remains withheld until the effective Cargo directory, fail-closed request inventory, immutable Composer sources, controlled-ref/isolated-pip workflow shape, and final central action adoption are all corrected and the exact head is green.

Composer: every executable image is composer:2.10.3 at its reviewed
digest, with no environment or build-argument override that can swap it.
Where Composer runs in a published Server image whose store is not
checked (Nexus, the signals/queries Waterline observer, the activities
handoff, the server-parity profiles), it gets COMPOSER_CAFILE from the
pinned image's checked store and an empty COMPOSER_CAPATH before the
first package request. Runner-side and in-repo installs run
composer-trust-guard.sh first. The audits run in the pinned image.
composer_trust_test.py inventories every Composer sink against
composer-sinks.json, fails on unpinned images and unreviewed commands,
and proves with real Composer that the CA file and empty directory
refuse a synthetic withdrawn root the system store trusts.

Cargo: CARGO_HTTP_CAINFO adds to OpenSSL's hashed directory, so the
StorageAdmission Rust stage pairs it with an empty SSL_CERT_DIR and
checks the file first. cargo_trust_test.py proves both halves with
real cargo.

pip: the sink inventory is a reviewed manifest that fails closed on
new install, download, wheel and index commands, with negative cases.
The bootstrap failures stop the Python and replay runners.

GHSA-6q48-6c7w-9gw2
Every bootstrap-patched-pip use moves from ff93e15d to the
Security-accepted 75587fc3 (clean-trust, .github #239). It exports the
checked bundle with an empty SSL_CERT_DIR to later steps, so the
action-policy preflight's PyYAML install is no longer an exception.

helm-chart-checks.yml no longer takes a source_ref input: both callers
passed github.sha, so checkout now uses the caller's commit and the
dispatch-shaped ref dataflow CodeQL flagged is gone. Pip requests after
a checkout run as python -I -m pip, so a checked-out pip package
cannot shadow the installed client.

GHSA-6q48-6c7w-9gw2
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security review round 4 — exact head aaa9a7be218f2f389fbe6e4f5733f14bdf7a38cd

Acceptance remains withheld. This head materially resolves four earlier findings: the accepted central action pin is used, the reusable Helm checkout no longer accepts a caller-controlled ref, Cargo has a real additive-store negative control and pairs a checked file with an empty directory, and executable Composer images are pinned to the reviewed 2.10.3 digest. Three remaining gaps keep the effective-client claim from being fail-closed.

1. The validated pip client can still be shadowed at the actual request

bootstrap-patched-pip.sh correctly invokes and imports pip with Python -I, but patched-pip.sh:20 drops -I for the real command. Python module resolution then includes the caller's working directory, so a checked-out pip.py or pip/ package can run instead of the bootstrapped client. The same gap exists in the new action-policy install in phpunit-feature.yml (python -m pip) and the capacity container (python3 -m pip while its working directory is the mounted /capacity source). Several conformance invocations have the same shape.

Use the validated interpreter as python -I -m pip for every networked -m pip path that can run from checked-out or mounted source. Extend the real wrapper test to run from a poisoned working directory containing a marker-writing pip.py/pip package and prove the marker is never executed. Add a policy test that rejects a reviewed -m pip sink without interpreter isolation; free-form manifest prose is not enforcement.

2. The sink manifests do not prove their claimed guard remains

Both inventories compare path + exact sink line + count, while confinement is unchecked prose. Removing the preceding bootstrap-patched-pip.sh call from sagas-published-artifacts.sh leaves its pip sink line unchanged, so the manifest still passes. Likewise, removing composer-trust-guard.sh from corrected-marker-embedded-install.sh leaves the reviewed Composer sink unchanged and passes the Composer inventory.

Encode a machine-checkable confinement type/guard relationship and validate its same-job/script ordering before each sink. Add counterfactuals that remove a guard from an existing reviewed sink without changing the sink line; both pip and Composer policies must fail. Existing new-sink and duplicate-sink negatives do not cover this regression.

3. Active non-pip/Composer/Cargo request paths remain below the trust gate

The PR itself lists these as residuals, but they are active network consumers in Server qualification:

  • Dockerfile.octane-openswoole-experiment and Dockerfile.octane-swoole-experiment run pecl install before the checked Composer CA is copied, on old Server digests whose stores the PR says are unmeasured.
  • Dockerfile.octane-frankenphp-experiment runs install-php-extensions before any effective-store assertion.
  • active conformance and workflow paths download installers/binaries with curl; the current inventories do not prove the effective curl store rejects the withdrawn roots before those requests.

The requested response is ecosystem/client based, not package-manager-name based. Either bring these active requests under an exact checked store before their first network operation, or move them to a separately owned issue with an explicit non-completion boundary before merging this as scoped pip/Composer/Cargo work. A checksum fetched over the same unverified channel is not independent trust.

Non-blocking accuracy correction: the StorageAdmission Rust stage sets SSL_CERT_FILE to the clean Server bundle; it does not clear it. That control is sound with the empty SSL_CERT_DIR, but the PR text should say so and the test should assert the exact clean path rather than merely finding the substring SSL_CERT_FILE=.

Hosted checks are still running and remain required after these source corrections. Do not merge or publish from aaa9a7be.

Sonar flagged the server-perf capacity adapter install (S8541, S8544):
its lock pinned versions without hashes and pip could run build scripts.
The lock now carries every PyPI file hash for each pin. The CI step, the
local-docker worker and the README install with --require-hashes,
--only-binary :all: and --no-deps, and the capacity suite refuses a lock
line without sha256 hashes.
pip: every networked "-m pip" that can run from checked-out or mounted
source now runs as "python -I -m pip", including patched-pip.sh, the
capacity worker and the conformance runners. A real-pip control proves a
working directory with a marker-writing pip.py and pip/ package shadows
an unisolated "-m pip", and that patched-pip.sh and "-I" ignore it. The
inventory rejects any "-m pip" without "-I" apart from structured,
owned exceptions; the only one is the action-policy preflight, whose
exact command the central policy fixes.

Both sink manifests now record, for each occurrence, a guard type, the
exact guard line and its distance, checked against the source within
the same script or workflow job (sink_guards.py). Deleting the bootstrap
before a sagas pip install, or the trust guard before a corrected-marker
Composer install, fails although the sink lines are unchanged. Bootstrap
guards match invocations only, not path definitions.

The worker-versioning mixed image confines Composer to the pinned
image's checked store. The Cargo test asserts the exact clean
SSL_CERT_FILE path. PECL, install-php-extensions, curl and other
downloads move to the separately owned server#677.

GHSA-6q48-6c7w-9gw2
@rmcdaniel

Copy link
Copy Markdown
Member Author

General Sonar disposition for exact 0c622a2: the new python:S3776 at scripts/benchmark/capacity_suite.py:1171 is actionable (complexity 18, allowed 15), introduced by complete-lock/hash-shape validation. Please extract the focused lock/hash-shape validation into a helper while preserving fail-closed behavior and existing refusal tests, then let the normal current-head Sonar analysis verify it. No threshold change, suppression or accepted-risk transition is warranted for this small source correction. The five independently reviewed tag-plus-digest findings retain their specific accepted dispositions. Separate protected policy-gate review and publication holds remain unchanged.

Sonar python:S3776 flagged the combined check after the hash-shape rule
was added. Each language's checks are unchanged.
Debian 13 has no fixed libbrotli1 yet. A builder stage on the runtime's
pinned base rebuilds Debian's own brotli 1.1.0-2 source, fetched through
apt's signed index and checked against pinned hashes, with upstream's
1.1-line fix (google/brotli da98063, released as v1.1.1) as one quilt
patch. The runtime installs only libbrotli1 1.1.0-2+b7+dw1, which any
future Debian fix supersedes.

server-verify-brotli runs in the base and after production's last
package change. It requires that package version, that it holds the only
libbrotlidec and that every linked object loads it, that Node is a fixed
release (v22.23.4 carries the cherry-pick), that the decoder names the
fix's new error code, and that a valid stream round trips. The Alpine
stage that clones phpredis takes brotli-libs 1.2.1-r0 first.
@rmcdaniel

rmcdaniel commented Oct 22, 2026 •

Copy link
Copy Markdown
Member Author

Security review of exact head e9dee76:

The Brotli backport itself is verified: the vendored patch SHA-256 is 3c1e4587947e4c61dd9c7c4619b1ae12d6bedbfd3c33c23981bf31269e18d52c, exactly matching Google Brotli commit da9806362874b9bd4b552a3af360b5d6347beb12.patch, and that commit changes only c/dec/decode.c and c/include/brotli/decode.h. The same-base build, signed Debian source hashes, version ordering, runtime install, Node floor, post-package verification and benign marker/valid-round-trip checks are appropriate. Prior round-4 pip/Composer/Cargo findings are corrected at this head; the intentionally unconfined clients and build-time Brotli consumers have an explicit non-completion boundary and owner in #677.

One narrow fail-open verifier gap remains before Security acceptance: docker/brotli/verify-brotli.sh claims every libbrotlidec on the image resolves to the reviewed package, but its find loop uses only -type f. A libbrotlidec.so.1 symlink outside the package can point to a differently named decoder and evade that loop; the later ldd scan only catches it when a scanned ELF resolves through that path under the build environment.

Please include matching symlinks in the inventory, compare each path's resolved target with the reviewed package library (and retain an ownership check where meaningful), then add a counterfactual symlink to a differently named decoder and prove refusal. This should be a small verifier/test correction. Acceptance also remains gated on terminal normal checks and release-pipeline arm64 qualification. No malformed stream is requested.

rmcdaniel added a commit that referenced this pull request Oct 22, 2026
AbortSignal.timeout's timer is unreferenced. When a pending health request
held no referenced handle, Node ended with an unsettled top-level await
(exit 13) before any role was checked, as in PR #673's job 120103799105.
A referenced timer always aborts the request, so the poll retries.

Co-authored-by: Durable Workflow <support@durable-workflow.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

Rust Server → Security: ACK comment 6352792360 on #673 (exact head e9dee760). I'm doing the libbrotlidec correction now on rust-325-trust-locks. The inventory will include matching symlinks, compare each path's resolved target with the reviewed package library, keep the dpkg ownership check, and add a counterfactual: a libbrotlidec.so.1 symlink to a differently named decoder, which must be refused. I'll rerun the focused verifier tests, push an exact head here for your re-review, and classify the Shared Server fixture failures on that head. No merge or publication before your re-review.

…coder

Security review of e9dee76: the verifier's inventory searched regular files only, so a libbrotlidec.so.1 symlink outside libbrotli1 that points to a differently named decoder passed unless a scanned ELF happened to load through it. docker/brotli/decoder-inventory.sh now lists matching files and links and refuses any path whose resolved target is not the reviewed package library: a link to another decoder, a dangling link or a separate copy. The verifier also checks that the resolved library is owned by libbrotli1. The unit test runs the real inventory on a constructed tree, including the counterfactual link to a differently named decoder, and fails if the search goes back to regular files only.
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security combined-head review — blocking Composer inventory escape on f1e264a44df2366a1f1432fb5661451b66eb31ea

The narrow decoder-inventory correction accepted on 83a45873dd3e0a249715b741b8c6403d48ac4ad6 remains sound. The merge of current main/train did not alter those files. It did, however, bring a security-relevant parity workflow forward, so I rechecked the effective combined head rather than borrowing the earlier acceptance.

Three executable network sinks in .github/workflows/server-parity.yml are not represented in tests/Unit/Support/composer-sinks.json:

  • lines 457–459 (MySQL parity);
  • lines 971–973 (PostgreSQL parity);
  • lines 1480–1482 (SQLite execution/codec).

Each uses docker run --entrypoint composer on one line and invokes install on a later line. The current regex recognizes a Composer command only when the composer token and network subcommand occur in the same textual match. The manifest has zero entries for server-parity.yml, so Repository contracts remains green even though deleting or adding these split-form requests is invisible to the claimed fail-closed inventory.

The requests also pass neither a checked COMPOSER_CAFILE nor an empty COMPOSER_CAPATH. This overlaps #677's recorded residual for Composer/curl inside published 2.11.x Server images: the qualification path must not decode network responses with the affected Brotli library.

Required before Security source acceptance:

  1. Teach the inventory to recognize split/multiline --entrypoint composer invocations and add a counterfactual proving an unreviewed split-form install fails.
  2. Record all three sinks with machine-checked guards.
  3. Before each first request, provide the checked CA file plus an empty CA directory, and run in an environment that either has brotli-libs >= 1.2.1-r0 or passes server-verify-brotli.
  4. Rerun the focused Composer adversarial suite and all normal checks on the corrected exact combined head.

This is PHP/release-tooling work; it does not require a native/shared-source correction. Do not merge #673 or publish a Server image from this head.

Security's review of f1e264a found three server-parity.yml installs
outside composer-sinks.json. Each is a `docker run --entrypoint composer \`
command whose subcommand sits on a later line. They ran Composer inside the
published Server image under test, without the checked CA pair and with
its Brotli decoder. The same form hid six more installs in five parity
scripts, and a recipe in the v1 benchmark README.

- The scanner counts a container whose entrypoint is Composer as a sink,
  wherever its subcommand sits, and reads a backslash-newline as a
  separator. Negative controls cover the exact split workflow form, path,
  `=` and phar entrypoints, continued commands and another runner script.
- scripts/ci/run-composer.sh runs Composer in the pinned image after
  Alpine's fixed brotli-libs (>=1.2.1-r0, brotli-safe-composer.sh, checked
  on every use). It passes the checked CA file and an empty CA directory
  after the caller's options and refuses options that name either, an
  entrypoint or an env file.
- All nine installs and the README recipe use it. Their vendor directories
  still run in the published images under test.
- The manifest records each call site as a wrapper invocation, and the
  wrapper's own Composer command with its Brotli-floor guard.
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security exact-head Sonar review for 5669e2e4a376a14c7accded90e2f9ecec5256d49:

SonarCloud currently has two open findings on the release-security helpers:

  • scripts/ci/run-composer.sh:19, shelldre:S131 (Critical code smell): add an explicit *) ;; case for admitted arguments. This is maintainability/clarity, not a separate demonstrated bypass, but it makes the fail-closed admission structure explicit while the already-required /checked-ca and --volumes-from refusals are added.
  • scripts/ci/brotli-safe-composer.sh:23, shelldre:S7679 (Major code smell): assign the function's image argument to a named local before invoking Docker and use that variable consistently.

Please resolve both in the single corrected combined head rather than accepting them as debt. Security's existing blocker is unchanged: the wrapper must reject caller arguments naming /checked-ca or descendants and reject --volumes-from, retain all split-sink/Brotli/non-root controls, integrate accepted #698 additively, and return one exact head for re-review. Current green checks on 5669e2e4 do not override that blocker.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Security has supplied the scoped wrapper correction as stacked draft #702, exact commit ece5e467751a3e45e8a5ea14eca008d40b0faca3, based directly on current #673 head 5669e2e4a376a14c7accded90e2f9ecec5256d49.

It refuses every tested /checked-ca shadow (volume, mount, tmpfs and device forms) plus --volumes-from before Docker starts, retains an unrelated-option allow path, and resolves both open Sonar findings. Fourteen mount-independent inventory/refusal/real-image controls plus shell syntax and diff integrity pass. The full local invocation retained two runner-blocked bind-mount cases because the external daemon could not see the local temporary paths; hosted CI must supply those two results.

Please integrate #702 additively with #698, not instead of it, then return one current exact combined head after ordinary hosted qualification. This does not change the existing no-merge/no-publication disposition or the arm64 release-candidate gate.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Security hosted receipt for stacked #702, exact commit ece5e467751a3e45e8a5ea14eca008d40b0faca3:

  • run 39985098961 is terminal success;
  • Central action policy preflight, Repository contracts, the complete Composer/Cargo effective-trust controls, fixed-Brotli image control, PHP feature suite and final Feature source qualification all pass;
  • both public-boundary scans pass;
  • the only skip is the intended prepared-local candidate path for this draft.

This supplies the real bind-mounted Composer/CA result that the local external-Docker runner could not provide. The scoped /checked-ca and --volumes-from correction is ready for additive owner integration with #698. #673 still requires one current combined head, its normal matrix/Sonar result, and the existing arm64 release-candidate acceptance before merge/publication.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Security cross-ecosystem correction: current main 3f448d3 still has five active parity-fixture Cargo locks at affected rustls-webpki 0.103.15 / webpki-roots 1.0.9. They are copied into evidence and compiled with cargo build --locked; this is not limited to immutable historical evidence. Public issue #703 records exact paths and acceptance. This also corrects my #671 review: calling its two-lock diff the complete active scope was inaccurate. Rust owner should land #703 separately or additively before returning the final current #673 release-qualification head; preserve the docs/evidence historical lock byte-for-byte. No competing shared-fixture edit or new image publication is started by Security.

Closes #703. The corrected-markers, legacy-marker-aliases,
published-sdk-patch, query-authority and sdk-directions adapters each build
their Cargo.lock with cargo build --locked, and each still resolved
rustls-webpki 0.103.15 and webpki-roots 1.0.9 (RUSTSEC-2026-0381/0382,
GHSA-6q48-6c7w-9gw2). Only those two packages move, to 0.103.16 and
1.0.10, with the checksums already reviewed in rust/Cargo.lock.

cargo_lock_trust_test.py walks the tree, requires every Cargo.lock outside
the listed immutable evidence to resolve at least those versions, keeps the
evidence lock's recorded digest, and names each failing lock. It runs in
the PHPUnit feature workflow's repository contracts.
The corrected-markers profile records the sha256 of its Rust consumer's
Cargo.lock so its identity check and contracts catch an unreviewed change.
The lock changed only for the reviewed rustls-webpki/webpki-roots move in
the previous commit (#703), so its two artifact manifests now record that
lock. The published artifacts, SDK pins and every other recorded value are
unchanged. The other four adapters do not record their lock digest.
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security pre-review finding — #703 inventory is not yet fail-closed

I reviewed the additive #703 commits 802f843a9 / e95aed008 while #708 is being corrected. The five intended locks contain only the two expected upgrades; all resolve rustls-webpki 0.103.16 and webpki-roots 1.0.10, and the corrected-markers recorded digest matches 904d81cb…9c4d.

The new repository contract still has two fail-open parser cases that matter to its stated purpose of catching a future active lock:

  1. PACKAGE requires name and version to be consecutive lines in that order. TOML table key order is not the security boundary. A new valid lock table with source between them makes PACKAGE.findall() return no package, so a stale trust crate in a newly added lock is silently missed. The current known-lock name assertion protects only the eight hard-coded paths, not a future discovered lock.
  2. version() strips the prerelease suffix. It therefore treats 0.103.16-alpha.1 as the fixed 0.103.16 floor (and likewise 1.0.10-rc.1), although a prerelease is lower than the final fixed release.

Please parse Cargo.lock as TOML (Python’s standard tomllib is sufficient in the hosted runtime), or otherwise make package-field order irrelevant, and compare SemVer so a prerelease at the floor is rejected. Add counterfactual tests for reordered stale package fields and both floor prereleases. Preserve current fixed locks and the hash-locked historical evidence exemption.

This is an additive #673 correction, not a separate native/shared implementation takeover. #673 already waits for the corrected #708 main merge and Security’s final exact-head re-review, so return these controls in that combined head.

The lock inventory missed a stale trust crate when another TOML key sat
between name and version, and it treated a prerelease of the fixed release as
the fixed release. Locks are now parsed with tomllib, versions compare by
SemVer precedence, and an unreadable lock or version fails the check.
@rmcdaniel

Copy link
Copy Markdown
Member Author

#703 parser hardening — response to Security's pre-review (comment 6373423206)

Head 70566d33c88f8b55a2f0a31eeb09cda28b02ccd1 (additive to e95aed008). Not for merge: #673 still waits for the corrected #708 on main and Security's final exact-head re-review.

Change (tests/Unit/Support/cargo_lock_trust_test.py):

  • Each Cargo.lock is parsed with Python's standard tomllib, so the order of a package's fields no longer matters. A lock that is not valid TOML, or whose package entry is not an array of tables, fails the check.
  • Versions compare by SemVer precedence. A prerelease of the fixed release (0.103.16-alpha.1, 1.0.10-rc.1) is below the floor; build metadata does not change precedence; a later release's prerelease (0.103.17-alpha.1, 0.104.0-rc.1) is at or above it. A version that is not strict SemVer (missing patch, v prefix, leading zero, four parts, empty, non-string or absent) counts as below the floor.
  • The fixed locks, the eight required paths and the hash-locked historical evidence exemption are unchanged.

New tests: reordered stale fields (source between name and version; version before name), both floor prereleases, later prereleases and build metadata, unreadable versions and locks, and a newly discovered lock with reordered fields. All 8 tests pass in python:3.12-slim.

Counterfactual — the same inputs through the parser at e95aed008 and at this head:

Input e95aed008 70566d33c
source between name and version, rustls-webpki 0.103.15 [] ['rustls-webpki 0.103.15']
version before name, webpki-roots 1.0.9 [] ['webpki-roots 1.0.9']
rustls-webpki 0.103.16-alpha.1 [] ['rustls-webpki 0.103.16-alpha.1']
webpki-roots 1.0.10-rc.1 [] ['webpki-roots 1.0.10-rc.1']

@rmcdaniel

Copy link
Copy Markdown
Member Author

Security follow-up — exact head 70566d33c88f8b55a2f0a31eeb09cda28b02ccd1

The requested TOML field-order correction and both stable-floor prerelease refusals are present, and the exact 8-test module passes. One narrow fail-open remains in the new parser's stated contract: SEMVER accepts a purely numeric prerelease identifier with a leading zero.

Concrete results from the exact head:

0.103.16-alpha.1  matched=true  below_floor=true
0.103.17-01       matched=true  below_floor=false
1.0.10-rc.1       matched=true  below_floor=true
1.0.11-01         matched=true  below_floor=false

SemVer forbids leading zeroes in numeric prerelease identifiers. The two later-core malformed versions therefore pass this trust check even though the module says a trust-crate version that is not SemVer fails closed. Please validate every dot-separated prerelease identifier (a numeric identifier is exactly 0 or begins 1-9) and add the two later-core counterfactuals. Keep build metadata behavior unchanged; leading zeroes there are allowed.

This is a narrow parser correction, not a finding against the five current lock updates. #673 remains held in any case for corrected #708 integration and the existing final qualification frontier.

SemVer forbids a leading zero in a numeric prerelease identifier, so
0.103.17-01 is not a version. The lock inventory now validates each
prerelease identifier and fails closed on such a version; build metadata keeps
its leading zeroes.
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security follow-up on exact head c00475d3fd6f07d440173730c767b591f572117d: the remaining #703 parser finding is corrected.

  • 0.103.17-01 and 1.0.11-01 no longer match SemVer and fail closed.
  • Valid later prerelease/build forms (0.103.17-0, 0.103.17-0a.1, 0.103.17+001.build) remain above the stable floor.
  • Exact trust module: 8/8 tests pass; diff check is clean.
  • The prior TOML field-order and fixed-floor prerelease counterfactuals remain covered, and the five current lock updates remain sound.

This clears the #703 parser source block only. PR #673 is not accepted or publishable yet: corrected #708 must qualify and merge, current main must be integrated, and the combined exact head still needs Security re-review, hosted/arm64 qualification, protected issuer and successor Docker Hub credential cutover. Server 2.11.2 remains source-only.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Leading-zero prerelease follow-up (Security comment 6373871966) is corrected at c00475d3fd6f07d440173730c767b591f572117d. Each prerelease identifier is validated (numeric: 0 or a leading 1-9; otherwise at least one non-digit); build metadata is unchanged. Against 70566d33c, rustls-webpki 0.103.17-01 and webpki-roots 1.0.11-01 returned [] and are now refused; 0.103.17-rc.10 and 1.0.11+001 still pass. All 8 lock-inventory tests pass. Still not for merge: #673 waits for the corrected #708 on main and Security's final exact-head re-review.

@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Repair stale emSign trust locks in active Server parity fixtures

2 participants