fix(images): Remi origin over HTTPS first; fail builds older than php.net

Security review of #28: plain-http repo metadata could be replayed to keep a
build on old (validly signed) vulnerable PHP. The origin is now tried over
HTTPS first, with http only as a fallback (its HTTPS timed out/reset under the
2026-10-21 release load). CI resolves php.net's current release per branch
(over HTTPS) and the build fails if the installed PHP is older, so neither a
lagging mirror nor replayed metadata can ship a vulnerable PHP.

Tested on 192.168.1.193 (almalinux/10-base): rewritten baseurl installs PHP
8.5.12; the gate passes at 8.5.12 and fails at 8.5.99.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shadowdaoandClaude Opus 5.5 committed 2026-10-21 08:29:31 -07:00
1 parent e8e3bfd490
commit af7fd193e5
3 files changed
+56 -4

No files matched your search

+30
View File
@@ -84,6 +84,20 @@ jobs:
id: bust
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
# The minimum PHP version this build must ship: php.net's current release for
# the branch (over HTTPS). The Dockerfile fails if Remi gave us anything older,
# so lagging mirrors or replayed repo metadata can't ship a vulnerable PHP.
- name: Resolve current PHP release from php.net
id: phpmin
run: |
set -euo pipefail
v="${{ matrix.phpver }}"; branch="${v:0:1}.${v:1}"
json=$(curl -fsS --retry 5 --max-time 60 "https://www.php.net/releases/index.php?json&max=1&version=${branch}")
min=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(next(iter(d)) if isinstance(d,dict) and d else "")')
if [ -z "$min" ]; then echo "::error::no php.net release found for ${branch}"; exit 1; fi
echo "php.net current ${branch}: ${min}"
echo "min=${min}" >> "$GITHUB_OUTPUT"
- name: Login to Gitea
uses: docker/login-action@v3
with:
@@ -99,6 +113,7 @@ jobs:
build-args: |
PHPVER=${{ matrix.phpver }}
CACHE_BUST=${{ steps.bust.outputs.value }}
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
tags: |
repo.anhonesthost.net/cloud-hosting-platform/cac:php${{ matrix.phpver }}
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac:latest' || '' }}
@@ -129,6 +144,20 @@ jobs:
id: bust
run: echo "value=$(date -u +%Y%m%dT%H%M%SZ)-${{ gitea.run_id }}" >> "$GITHUB_OUTPUT"
# The minimum PHP version this build must ship: php.net's current release for
# the branch (over HTTPS). The Dockerfile fails if Remi gave us anything older,
# so lagging mirrors or replayed repo metadata can't ship a vulnerable PHP.
- name: Resolve current PHP release from php.net
id: phpmin
run: |
set -euo pipefail
v="${{ matrix.phpver }}"; branch="${v:0:1}.${v:1}"
json=$(curl -fsS --retry 5 --max-time 60 "https://www.php.net/releases/index.php?json&max=1&version=${branch}")
min=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(next(iter(d)) if isinstance(d,dict) and d else "")')
if [ -z "$min" ]; then echo "::error::no php.net release found for ${branch}"; exit 1; fi
echo "php.net current ${branch}: ${min}"
echo "min=${min}" >> "$GITHUB_OUTPUT"
- name: Login to Gitea
uses: docker/login-action@v3
with:
@@ -145,6 +174,7 @@ jobs:
build-args: |
PHPVER=${{ matrix.phpver }}
CACHE_BUST=${{ steps.bust.outputs.value }}
EXPECT_PHP_MIN=${{ steps.phpmin.outputs.min }}
tags: |
repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:php${{ matrix.phpver }}
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-fpm:latest' || '' }}
+13 -2
View File
@@ -4,6 +4,8 @@ ARG PHPVER=83
# never be a layer-cache hit, so every build gets the repos' current packages.
# Local builds: --build-arg CACHE_BUST=$(date +%s) or --no-cache.
ARG CACHE_BUST=
# php.net's current release for this branch (CI); the build fails on older PHP.
ARG EXPECT_PHP_MIN=
# Install repos, update, install only needed packages, clean up in one layer
RUN dnf install -y \
@@ -11,8 +13,12 @@ RUN dnf install -y \
https://rpms.remirepo.net/enterprise/remi-release-10.rpm && \
# Use Remi's origin, not the mirrorlist: during the 2026-10-21 PHP security
# release the mirrors lagged hours behind (still 8.2.34 when 8.2.35 was out),
# so builds shipped unpatched PHP. Packages stay GPG-checked (gpgcheck=1).
sed -i -e 's/^#baseurl=/baseurl=/' -e 's/^mirrorlist=/#mirrorlist=/' /etc/yum.repos.d/remi*.repo && \
# so builds shipped unpatched PHP. HTTPS first; plain http only as a fallback
# because the origin's HTTPS dropped/timed out under that day's load. A
# replayed (old) http index can't ship old PHP: packages stay GPG-checked and
# the EXPECT_PHP_MIN gate below fails the build on anything older than php.net.
sed -i -e 's|^#baseurl=http://\(.*\)$|baseurl=https://\1 http://\1|' -e 's/^mirrorlist=/#mirrorlist=/' /etc/yum.repos.d/remi*.repo && \
printf 'retries=10\ntimeout=60\n' >> /etc/dnf/dnf.conf && \
dnf update -y && \
dnf install -y httpd mod_ssl openssl wget procps cronie iproute postgresql-devel microdnf less git \
nano rsync unzip zip mariadb bind-utils jq patch nc tree dos2unix && \
@@ -31,6 +37,11 @@ RUN dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \
RUN openssl req -newkey rsa:2048 -nodes -keyout /etc/pki/tls/private/localhost.key -x509 -days 3650 -subj "/CN=localhost" -out /etc/pki/tls/certs/localhost.crt && \
mkdir -p /run/php-fpm/ && \
/scripts/install-php$PHPVER.sh && \
# CI passes php.net's current release for the branch; never ship older PHP.
if [ -n "$EXPECT_PHP_MIN" ]; then \
php -r 'exit(version_compare(PHP_VERSION, getenv("EXPECT_PHP_MIN"), ">=") ? 0 : 1);' || \
{ echo "FATAL: built PHP $(php -r 'echo PHP_VERSION;') is older than php.net $EXPECT_PHP_MIN"; exit 1; }; \
else echo "WARNING: no EXPECT_PHP_MIN (local build) - PHP version not gated"; fi && \
echo "PHP version report:" && php -v && rpm -q php-cli php-common php-fpm && \
rm -rf /tmp/*
+13 -2
View File
@@ -4,6 +4,8 @@ ARG PHPVER=83
# never be a layer-cache hit, so every build gets the repos' current packages.
# Local builds: --build-arg CACHE_BUST=$(date +%s) or --no-cache.
ARG CACHE_BUST=
# php.net's current release for this branch (CI); the build fails on older PHP.
ARG EXPECT_PHP_MIN=
# Install repos, update, install only needed packages (no httpd/mod_ssl), clean up in one layer
RUN dnf install -y \
@@ -11,8 +13,12 @@ RUN dnf install -y \
https://rpms.remirepo.net/enterprise/remi-release-10.rpm && \
# Use Remi's origin, not the mirrorlist: during the 2026-10-21 PHP security
# release the mirrors lagged hours behind (still 8.2.34 when 8.2.35 was out),
# so builds shipped unpatched PHP. Packages stay GPG-checked (gpgcheck=1).
sed -i -e 's/^#baseurl=/baseurl=/' -e 's/^mirrorlist=/#mirrorlist=/' /etc/yum.repos.d/remi*.repo && \
# so builds shipped unpatched PHP. HTTPS first; plain http only as a fallback
# because the origin's HTTPS dropped/timed out under that day's load. A
# replayed (old) http index can't ship old PHP: packages stay GPG-checked and
# the EXPECT_PHP_MIN gate below fails the build on anything older than php.net.
sed -i -e 's|^#baseurl=http://\(.*\)$|baseurl=https://\1 http://\1|' -e 's/^mirrorlist=/#mirrorlist=/' /etc/yum.repos.d/remi*.repo && \
printf 'retries=10\ntimeout=60\n' >> /etc/dnf/dnf.conf && \
dnf update -y && \
dnf install -y openssl wget procps cronie iproute postgresql-devel microdnf less git \
nano rsync unzip zip mariadb bind-utils jq patch nc tree dos2unix fcgi && \
@@ -30,6 +36,11 @@ RUN dnf install -y ImageMagick ImageMagick-libs ImageMagick-heic && \
# Create needed dirs, install PHP, clean up (no SSL cert, no httpd)
RUN mkdir -p /run/php-fpm/ && \
/scripts/install-php$PHPVER.sh && \
# CI passes php.net's current release for the branch; never ship older PHP.
if [ -n "$EXPECT_PHP_MIN" ]; then \
php -r 'exit(version_compare(PHP_VERSION, getenv("EXPECT_PHP_MIN"), ">=") ? 0 : 1);' || \
{ echo "FATAL: built PHP $(php -r 'echo PHP_VERSION;') is older than php.net $EXPECT_PHP_MIN"; exit 1; }; \
else echo "WARNING: no EXPECT_PHP_MIN (local build) - PHP version not gated"; fi && \
echo "PHP version report:" && php -v && rpm -q php-cli php-common php-fpm && \
rm -rf /tmp/*