Emergency build path: compiles lsphp from the official php.net tarball for when a PHP security release is out and rpms.litespeedtech.com/debian (noble) is behind. Today's case is php2shell (CVE-2026-109618/-109619). php.net has 8.2.35, 8.3.36, 8.4.27 and 8.5.12; LiteSpeed still ships 8.2.34, 8.3.35, 8.4.26 and 8.5.11.
Do not merge without review. The emergency workflow can only run from trunk once this is merged.
Design
Dockerfile.lsphp and Dockerfile.litespeed get a new lsphp-src stage, FROM the same LiteSpeed base. It does nothing unless PHP_SRC_VERSION is set. When it is not set, the overlay steps are no-ops. Normal php85 lsphp and php83 litespeed builds were measured identical to the published images on module list, file manifest, ldd, ini and dpkg.
scripts/lsphp-src-build.sh does the build:
Checks the tarball sha256 against the php.net releases JSON, and checks the OpenPGP signature with gpgv against the committed php.net keyring. The signer's primary fingerprint must be pinned for that branch.
Replays the packaged lsphp's own Configure Command. No flags are kept in the repo.
Uses LiteSpeed's php-litespeed-8.3 SAPI, which is what the packages compile, not php-src's sapi/litespeed. This was measured: the packaged binaries have LSAPI_MAX_CMD_SCRIPT_PATH_LEN, print "1997-2016", and phpversion('litespeed') is empty. The SAPI is pinned by sha256 and checked against the packaged binary's LSAPI_* name set.
Reproduces the packaged compiled-in ini defaults. 8.2 and 8.3 carry Debian's session.save_path=/var/lib/php/sessions patch. Any other php -n difference fails the build.
Runs an ABI gate (scripts/lsphp-abi-gate.sh, using pahole) before keeping LiteSpeed's packaged PECL .so files (igbinary, imagick, memcached, msgpack, redis). Those are the same binaries the primary image ships. The php2shell fix does add struct fields: one in padding (CG), and appended fields in EG and PS (*_globals only). No existing member moved.
scripts/lsphp-src-overlay.sh replaces only files owned by php-src packages; dpkg decides which. It then fails unless, under both the CLI and litespeed SAPIs, all of these match the packaged lsphp: extensions and their versions, effective ini, gd/curl/openssl/icu/imagick/redis/memcached/password/hash/stream features, and Configure Command (except CFLAGS).
Labels: org.whp.php-version is the php.net version and org.whp.lsphp-source=php.net. /etc/cac-lsphp-source records the sha256, signer and the packaged version it replaced.
CI:
New lsphp-source-emergency.yaml, workflow_dispatch only. It always pushes :phpNN-phpnet-X.Y.Z, and pushes :phpNN only when push_production_tags=true.
build-push.yaml now refuses to overwrite a newer php.net image with an older packaged one.
Shell-Checks runs the ABI-gate regression test, which proves the gate can fail.
Runbook and trigger command: docs/lsphp-source-emergency.md.
Tested (local docker, plus one build on 192.168.1.193)
Built php85 and php82 cac-lsphp and php83 cac-litespeed with 8.5.12, 8.2.35 and 8.3.36. All in-build gates passed.
Compared with the published packaged images: php -m, file manifest (type, mode, owner, path), per-ELF ldd library set, ini files (md5) and dpkg list are identical. Remaining lsphp -i differences: version strings, build host and date, the new memory_hard_limit_percent, Olson DB 2026.5 vs 2026.3 (8.5 only), and LDAP header version 20610 vs 20607.
Hardening matches: PIE, full RELRO, BIND_NOW, IBT/SHSTK, FORTIFY.
Ran with the WHP hardening flags, WHP_* env and a /home/<user> bind mount:
cac-lsphp behind a throwaway shared-ols over LSAPI :9000; cac-litespeed over https.
23/23 WordPress-like checks pass on the web path and the CLI, as uid 1050. Output is identical to the packaged images apart from the version line.
lsphp workers run as the customer uid. Restart and stop work.
Fix is present: NEWS lists the CVEs. php-src's new Zend/tests/oom_*.phpt fail 4–5 tests on the packaged builds and pass 60/60 on the php.net builds (8.5, 8.3, 8.2).
Negative tests for the trust checks: wrong sha256, unpinned signer, wrong branch and not-newer version are all refused.
Not tested: the Gitea workflow itself (it needs trunk), php84/php81 images, a real php2shell exploit, and php-src LSAPI SAPI tests (php-src has none).
Security fixes from review (commit 42993f4, rebased onto trunk after #29)
No ${{ }} inside any run: block in build-push.yaml or lsphp-source-emergency.yaml. Matrix values, step outputs, inputs and run_id now reach scripts only through env:. This also covers the existing php.net and LiteSpeed resolve steps from #24 and #29.
Every resolved version is checked against a regex before it is written to GITHUB_OUTPUT. PHP versions must match ^[0-9]+\.[0-9]+\.[0-9]+$ and deb versions ^[0-9][0-9A-Za-z.+~:-]*$; anything else fails the step. A simulated 8.5.12;id was rejected.
The LiteSpeed Packages index is now fetched over HTTPS (--proto '=https'). I checked that https://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages answers HTTP/2 200. InRelease gpg verification is not added.
Build args are only used as quoted env data in the Dockerfiles. lsphp-src-build.sh and lsphp-src-overlay.sh also validate PHPVER, PHP_SRC_VERSION and PHP_SRC_SHA256.
Re-checked: yamllint (relaxed, no line-length) is clean; shellcheck -S warning is clean; lsphp-info-probe.test.sh passes 36/36. Each modified run block was run locally with real fetches.
Emergency build path: compiles lsphp from the official php.net tarball for when a PHP security release is out and `rpms.litespeedtech.com/debian` (noble) is behind. Today's case is php2shell (CVE-2026-109618/-109619). php.net has 8.2.35, 8.3.36, 8.4.27 and 8.5.12; LiteSpeed still ships 8.2.34, 8.3.35, 8.4.26 and 8.5.11.
**Do not merge without review.** The emergency workflow can only run from trunk once this is merged.
## Design
- `Dockerfile.lsphp` and `Dockerfile.litespeed` get a new `lsphp-src` stage, FROM the same LiteSpeed base. It does nothing unless `PHP_SRC_VERSION` is set. When it is not set, the overlay steps are no-ops. Normal php85 lsphp and php83 litespeed builds were measured identical to the published images on module list, file manifest, ldd, ini and dpkg.
- `scripts/lsphp-src-build.sh` does the build:
- Checks the tarball sha256 against the php.net releases JSON, and checks the OpenPGP signature with `gpgv` against the committed php.net keyring. The signer's primary fingerprint must be pinned for that branch.
- Replays the packaged lsphp's own `Configure Command`. No flags are kept in the repo.
- Uses **LiteSpeed's php-litespeed-8.3 SAPI**, which is what the packages compile, not php-src's `sapi/litespeed`. This was measured: the packaged binaries have `LSAPI_MAX_CMD_SCRIPT_PATH_LEN`, print "1997-2016", and `phpversion('litespeed')` is empty. The SAPI is pinned by sha256 and checked against the packaged binary's `LSAPI_*` name set.
- Reproduces the packaged compiled-in ini defaults. 8.2 and 8.3 carry Debian's `session.save_path=/var/lib/php/sessions` patch. Any other `php -n` difference fails the build.
- Runs an ABI gate (`scripts/lsphp-abi-gate.sh`, using pahole) before keeping LiteSpeed's packaged PECL .so files (igbinary, imagick, memcached, msgpack, redis). Those are the same binaries the primary image ships. The php2shell fix *does* add struct fields: one in padding (CG), and appended fields in EG and PS (`*_globals` only). No existing member moved.
- `scripts/lsphp-src-overlay.sh` replaces only files owned by php-src packages; dpkg decides which. It then fails unless, under both the CLI and litespeed SAPIs, all of these match the packaged lsphp: extensions and their versions, effective ini, gd/curl/openssl/icu/imagick/redis/memcached/password/hash/stream features, and `Configure Command` (except CFLAGS).
- Labels: `org.whp.php-version` is the php.net version and `org.whp.lsphp-source=php.net`. `/etc/cac-lsphp-source` records the sha256, signer and the packaged version it replaced.
- CI:
- New `lsphp-source-emergency.yaml`, workflow_dispatch only. It always pushes `:phpNN-phpnet-X.Y.Z`, and pushes `:phpNN` only when `push_production_tags=true`.
- `build-push.yaml` now refuses to overwrite a newer php.net image with an older packaged one.
- Shell-Checks runs the ABI-gate regression test, which proves the gate can fail.
- Runbook and trigger command: `docs/lsphp-source-emergency.md`.
## Tested (local docker, plus one build on 192.168.1.193)
- Built php85 and php82 `cac-lsphp` and php83 `cac-litespeed` with 8.5.12, 8.2.35 and 8.3.36. All in-build gates passed.
- Compared with the published packaged images: `php -m`, file manifest (type, mode, owner, path), per-ELF ldd library set, ini files (md5) and dpkg list are identical. Remaining `lsphp -i` differences: version strings, build host and date, the new `memory_hard_limit_percent`, Olson DB 2026.5 vs 2026.3 (8.5 only), and LDAP header version 20610 vs 20607.
- Hardening matches: PIE, full RELRO, BIND_NOW, IBT/SHSTK, FORTIFY.
- Ran with the WHP hardening flags, WHP_* env and a `/home/<user>` bind mount:
- cac-lsphp behind a throwaway shared-ols over LSAPI :9000; cac-litespeed over https.
- 23/23 WordPress-like checks pass on the web path and the CLI, as uid 1050. Output is identical to the packaged images apart from the version line.
- lsphp workers run as the customer uid. Restart and stop work.
- Fix is present: NEWS lists the CVEs. php-src's new `Zend/tests/oom_*.phpt` fail 4–5 tests on the packaged builds and pass 60/60 on the php.net builds (8.5, 8.3, 8.2).
- Negative tests for the trust checks: wrong sha256, unpinned signer, wrong branch and not-newer version are all refused.
- **Not tested:** the Gitea workflow itself (it needs trunk), php84/php81 images, a real php2shell exploit, and php-src LSAPI SAPI tests (php-src has none).
## Security fixes from review (commit 42993f4, rebased onto trunk after #29)
- No `${{ }}` inside any `run:` block in `build-push.yaml` or `lsphp-source-emergency.yaml`. Matrix values, step outputs, inputs and run_id now reach scripts only through `env:`. This also covers the existing php.net and LiteSpeed resolve steps from #24 and #29.
- Every resolved version is checked against a regex before it is written to `GITHUB_OUTPUT`. PHP versions must match `^[0-9]+\.[0-9]+\.[0-9]+$` and deb versions `^[0-9][0-9A-Za-z.+~:-]*$`; anything else fails the step. A simulated `8.5.12;id` was rejected.
- The LiteSpeed `Packages` index is now fetched over **HTTPS** (`--proto '=https'`). I checked that `https://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packages` answers HTTP/2 200. InRelease gpg verification is not added.
- Build args are only used as quoted env data in the Dockerfiles. `lsphp-src-build.sh` and `lsphp-src-overlay.sh` also validate `PHPVER`, `PHP_SRC_VERSION` and `PHP_SRC_SHA256`.
- Re-checked: yamllint (relaxed, no line-length) is clean; shellcheck `-S warning` is clean; `lsphp-info-probe.test.sh` passes 36/36. Each modified run block was run locally with real fetches.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
For when a PHP security release is out and the LiteSpeed apt repo lags
(2026-10-21: php2shell CVE-2026-109618/-109619 fixed in 8.2.35/8.3.36/
8.4.27/8.5.12, LiteSpeed still on 8.2.34/8.3.35/8.4.26/8.5.11).
Dockerfile.lsphp / Dockerfile.litespeed gain an `lsphp-src` stage that is
idle unless PHP_SRC_VERSION is set; normal builds are unchanged (measured:
module list, file manifest, ldd, ini and dpkg identical to the published
images).
When set (lsphp-src-build.sh):
- tarball sha256 (php.net releases JSON) + OpenPGP signature against the
committed php.net keyring, signer pinned per branch
- configure flags replayed from the packaged lsphp's own Configure Command
- LiteSpeed's php-litespeed-8.3 SAPI (what the packages actually compile,
measured) instead of php-src's sapi/litespeed; sha256-pinned and checked
against the packaged binary's LSAPI_* names
- packaged compiled-in ini defaults reproduced (session.save_path on 8.2/
8.3) and gated (`php -n` diff)
- ABI gate (lsphp-abi-gate.sh, pahole) before reusing LiteSpeed's packaged
PECL .so files; regression-tested in Shell-Checks
lsphp-src-overlay.sh swaps only php-src-owned files (decided by dpkg) and
fails the build unless extensions, versions, effective ini and feature
lines match the packaged lsphp under both CLI and litespeed SAPIs.
CI: lsphp-source-emergency.yaml (workflow_dispatch only). build-push.yaml
refuses to overwrite a newer php.net image with an older packaged one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Security review of #30: step outputs derived from the LiteSpeed apt index
(fetched over plain HTTP) were spliced into shell via ${{ }} in jobs that
hold registry push secrets — a MITM'd "version" would be shell injection.
- every run: block in build-push.yaml and lsphp-source-emergency.yaml now
receives matrix values, step outputs, inputs and run_id via env: only
(incl. the pre-existing php.net / LiteSpeed resolve steps from #24/#29)
- resolved versions are regex-checked before GITHUB_OUTPUT: PHP
^[0-9]+\.[0-9]+\.[0-9]+$, deb ^[0-9][0-9A-Za-z.+~:-]*$
- LiteSpeed Packages index fetched over HTTPS (--proto =https); measured
rpms.litespeedtech.com serves it (HTTP/2 200)
- lsphp-src-build.sh / lsphp-src-overlay.sh validate PHPVER too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Emergency build path: compiles lsphp from the official php.net tarball for when a PHP security release is out and
rpms.litespeedtech.com/debian(noble) is behind. Today's case is php2shell (CVE-2026-109618/-109619). php.net has 8.2.35, 8.3.36, 8.4.27 and 8.5.12; LiteSpeed still ships 8.2.34, 8.3.35, 8.4.26 and 8.5.11.Do not merge without review. The emergency workflow can only run from trunk once this is merged.
Design
Dockerfile.lsphpandDockerfile.litespeedget a newlsphp-srcstage, FROM the same LiteSpeed base. It does nothing unlessPHP_SRC_VERSIONis set. When it is not set, the overlay steps are no-ops. Normal php85 lsphp and php83 litespeed builds were measured identical to the published images on module list, file manifest, ldd, ini and dpkg.scripts/lsphp-src-build.shdoes the build:gpgvagainst the committed php.net keyring. The signer's primary fingerprint must be pinned for that branch.Configure Command. No flags are kept in the repo.sapi/litespeed. This was measured: the packaged binaries haveLSAPI_MAX_CMD_SCRIPT_PATH_LEN, print "1997-2016", andphpversion('litespeed')is empty. The SAPI is pinned by sha256 and checked against the packaged binary'sLSAPI_*name set.session.save_path=/var/lib/php/sessionspatch. Any otherphp -ndifference fails the build.scripts/lsphp-abi-gate.sh, using pahole) before keeping LiteSpeed's packaged PECL .so files (igbinary, imagick, memcached, msgpack, redis). Those are the same binaries the primary image ships. The php2shell fix does add struct fields: one in padding (CG), and appended fields in EG and PS (*_globalsonly). No existing member moved.scripts/lsphp-src-overlay.shreplaces only files owned by php-src packages; dpkg decides which. It then fails unless, under both the CLI and litespeed SAPIs, all of these match the packaged lsphp: extensions and their versions, effective ini, gd/curl/openssl/icu/imagick/redis/memcached/password/hash/stream features, andConfigure Command(except CFLAGS).org.whp.php-versionis the php.net version andorg.whp.lsphp-source=php.net./etc/cac-lsphp-sourcerecords the sha256, signer and the packaged version it replaced.lsphp-source-emergency.yaml, workflow_dispatch only. It always pushes:phpNN-phpnet-X.Y.Z, and pushes:phpNNonly whenpush_production_tags=true.build-push.yamlnow refuses to overwrite a newer php.net image with an older packaged one.docs/lsphp-source-emergency.md.Tested (local docker, plus one build on 192.168.1.193)
cac-lsphpand php83cac-litespeedwith 8.5.12, 8.2.35 and 8.3.36. All in-build gates passed.php -m, file manifest (type, mode, owner, path), per-ELF ldd library set, ini files (md5) and dpkg list are identical. Remaininglsphp -idifferences: version strings, build host and date, the newmemory_hard_limit_percent, Olson DB 2026.5 vs 2026.3 (8.5 only), and LDAP header version 20610 vs 20607./home/<user>bind mount:Zend/tests/oom_*.phptfail 4–5 tests on the packaged builds and pass 60/60 on the php.net builds (8.5, 8.3, 8.2).Security fixes from review (commit
42993f4, rebased onto trunk after #29)${{ }}inside anyrun:block inbuild-push.yamlorlsphp-source-emergency.yaml. Matrix values, step outputs, inputs and run_id now reach scripts only throughenv:. This also covers the existing php.net and LiteSpeed resolve steps from #24 and #29.GITHUB_OUTPUT. PHP versions must match^[0-9]+\.[0-9]+\.[0-9]+$and deb versions^[0-9][0-9A-Za-z.+~:-]*$; anything else fails the step. A simulated8.5.12;idwas rejected.Packagesindex is now fetched over HTTPS (--proto '=https'). I checked thathttps://rpms.litespeedtech.com/debian/dists/noble/main/binary-amd64/Packagesanswers HTTP/2 200. InRelease gpg verification is not added.lsphp-src-build.shandlsphp-src-overlay.shalso validatePHPVER,PHP_SRC_VERSIONandPHP_SRC_SHA256.-S warningis clean;lsphp-info-probe.test.shpasses 36/36. Each modified run block was run locally with real fetches.🤖 Generated with Claude Code
16c8bcc46eto42993f42bd