docker-library

Activity

Stars and mentions · Last 30 days, Oct 5 – Nov 3

GitHub stars58
Mentions171
03774111148Oct 5Oct 19Nov 3
UTC days · repositories docker-library owns3 repositories

Top repositories

docker-library’s repositories ranked by new stars, as on trending.

Loading

Contributes to

Repositories outside docker-library’s account where docker-library opened or reviewed pull requests in the past 365 days.

Who noticed docker-library

Everyone who starred or forked docker-library’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

172posts
Nov 41 post
  1. postgres:18 exits right after starting? The data directory moved If you copy a Compose file from almost any PostgreSQL + Docker tutorial and change the tag to 18, the container stops a few seconds after it starts: $ docker run -d --name pgold -e POSTGRES_PASSWORD=secret -v pgold:/var/lib/postgresql/data postgres:18 b480eb9c72b29fa99f1ed8df17972e2bfc2d6da4b86c058a36cf21fd63579bfa {% raw %}$ docker ps -a --filter name=pgold --format "{{.Status}}"{% endraw %} Exited (1) 5 seconds ago Nothing is wrong with your password or your volume. The official image changed where it keeps the data. Everything below was run with postgres:18 (PostgreSQL 18.6) on Docker Engine 29.9. What the log says $ docker logs pgold 2>&1 | head -n 12 Error: in 18+, these Docker images are configured to store database data in a format which is compatible with "pg_ctlcluster" (specifically, using major-version-specific directory names). This better reflects how PostgreSQL itself works, and how upgrades are to be performed. See also https://github.com/docker-library/postgres/pull/1259 Counter to that, there appears to be PostgreSQL data in: /var/lib/postgresql/data (unused mount/volume) This is usually the result of upgrading the Docker image without upgrading the underlying database using "pg_upgrade" (which requires both Up to version 17, the image used PGDATA=/var/lib/postgresql/data, so everyone mounted their volume there. From 18 on, the default is a version-specific directory: | Image | Default data directory | Where to mount the volume | | --- | --- | --- | | postgres:17 and older | /var/lib/postgresql/data | /var/lib/postgresql/data | | postgres:18 | /var/lib/postgresql/18/docker | /var/lib/postgresql | With the old mount, the real data directory would sit outside your volume, inside the container's writable layer, and disappear the next time you recreate the container. The entrypoint notices the unused mount and refuses to start. Annoying, but it stops you from losing data silently. The fix Mount the parent directory instead: $ docker run -d --name pg -e POSTGRES_PASSWORD=secret -p 127.0.0.1:5432:5432 -v pgdata:/var/lib/postgresql postgres:18 $ docker exec pg psql -U postgres -c "SHOW data_directory;" data_directory ------------------------------- /var/lib/postgresql/18/docker (1 row) In Compose: services: db: image: postgres:18 environment: POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - pgdata:/var/lib/postgresql volumes: pgdata: Because the directory name includes the major version, a future major version can live next to the old one in the same volume, which is what the change is designed for. Upgrading an existing 16 or 17 database Changing the tag on an existing database doesn't work either, for a different reason: data files are not compatible across major versions. Pointing 18 at a 16 data directory (forcing the old path with PGDATA) gives: $ docker run --name pg18 -e POSTGRES_PASSWORD=secret -e PGDATA=/var/lib/postgresql/data -v pg16data:/var/lib/postgresql/data postgres:18 2>&1 | tail -n 3 2026-10-13 00:54:09.740 UTC [1] FATAL: database files are incompatible with server 2026-10-13 00:54:09.740 UTC [1] DETAIL: The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 18.6 (Debian 18.6-1.pgdg13+2). The simplest safe path is dump and restore into a new volume, keeping the old one untouched until you've checked the result: $ docker exec pg16 pg_dumpall -U postgres > upgrade.sql $ docker run -d --name pg18 -e POSTGRES_PASSWORD=secret -v pg18data:/var/lib/postgresql postgres:18 $ docker exec -i pg18 psql -U postgres -q < upgrade.sql ERROR: role "postgres" already exists That one error is expected: the new container already created the postgres role, and the dump tries to create it again. Everything else is restored. Check the version and your data, then switch your app to the new container. Two things to watch on Windows: PowerShell doesn't support < for input, and Windows PowerShell 5.1's > writes UTF-16, which breaks the dump. There, write the file inside the container with pg_dumpall -f and move it with docker cp. Checklist for postgres:18 - Mount the volume at /var/lib/postgresql, not /var/lib/postgresql/data. - If you set PGDATA yourself, make sure it is inside the mounted volume. - Minor updates (18.5 → 18.6): just pull and recreate. Major upgrades: dump and restore (or pg_upgrade), into a new volume. - POSTGRES_PASSWORD and the other variables only apply when the data directory is empty; changing them later does not change the existing password. The full chapter, with the Compose setup, a health check that doesn't report "healthy" too early, backups and the complete 16 → 18 upgrade output, is part of a free Docker tutorial: Run PostgreSQL in Docker.

  2. PostgreSQL 14 End of Life on 12 November 2026: How to Upgrade Postgres in Docker PostgreSQL 14 gets its last update on 12 November 2026. The PostgreSQL versioning policy lists that date as the final release for version 14, and the rule behind it is short: each major version is supported for five years, then it gets one final minor release and becomes unsupported. Any security bug found in 14 after that day stays unfixed. The calendar around it is tight. The PostgreSQL 19 RC 1 announcement of 15 October says the 12 November update release "will include a large number of fixes", and it puts general availability of PostgreSQL 19 on 19 November 2026, one week after 14 drops out of support. If you are still on 14, you have to choose a target now, before 19 has any track record. Managed databases get a little more room. The Amazon RDS for PostgreSQL release calendar keeps 14 in standard support until 28 February 2027, with paid Extended Support starting on 1 March 2027. A self-hosted container gets no grace period at all. The postgres:14 image you pulled in 2022 keeps running exactly the code it shipped with. Why so many stacks are still on 14 Postgres is the line in a compose file people are most nervous about touching, and with reason. Changing the image tag across a major version does not upgrade the data, because the on-disk format changes between majors. The versioning page says it plainly: a dump/reload or pg_upgrade is required. So a stack set up in 2021 or 2022 tends to get its app images rebuilt every month while the database tag sits still. The official Docker image moves on regardless. The versions.json file in the docker-library/postgres repository, which lists the versions the image maintainers build, currently covers 14 through 19. PostgreSQL 13, which reached end of life in November 2025, is no longer in it. Step 1: find every Postgres 14 you run Image names are not reliable. A container may run PostGIS, TimescaleDB, a vendor image or something built in-house, all with Postgres inside. Ask the binary instead: {% raw %} for c in $(docker ps --format '{{.Names}}'); do v=$(docker exec "$c" postgres --version 2>/dev/null) && echo "$c: $v" done {% endraw %} That only sees running containers, so also search compose files on disk for stacks that are stopped or live on other hosts: grep -rnE 'image: *"?postgres' --include='*compose*.y*ml' /srv /opt 2>/dev/null For every 14 instance, write down three facts before planning anything. Each one decides a step later on (examples below use a container called pg14): {% raw %} # data checksums: "version: 0" means off docker exec -u postgres pg14 pg_controldata | grep -i checksum # bootstrap superuser: empty output means "postgres" docker exec pg14 printenv POSTGRES_USER # where the data volume is mounted docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' pg14 {% endraw %} Step 2: pick the target version Our recommendation is PostgreSQL 18, not 19. Version 18 is the current stable line (18.6 on the versioning page today) and is supported until 14 November 2030. 19.0 will be a week old when 14 expires, and a migration forced by an end-of-life date is a poor moment to also be an early adopter. Move to 18 now and plan 19 for next year, once a few minor releases are out. You do not need to step through 15, 16 and 17. The pg_upgrade documentation says it supports upgrades "from 9.2.X and later", and the versioning page confirms you can skip versions, while recommending that you read the release notes of every version in between. For 14 to 18 that means the notes for 15, 16, 17 and 18, starting with the incompatibilities list at the top of each. Step 3: take a backup you have restored once docker exec pg14 pg_dumpall -U app | gzip > pg14-$(date +%F).sql.gz Restore it into a scratch container and run a few queries against it. Until you have done that, it is a file, not a backup. Option A: dump and restore For databases up to a few gigabytes this is the simplest path, and it clears out table bloat as a side effect. Start a postgres:18 container next to the old one with its own empty volume, then stream the data across: docker exec pg14 pg_dumpall -U app | docker exec -i pg18 psql -U app -q When we ran this, psql printed two errors: ERROR: role "app" already exists ERROR: database "app" already exists Both are harmless. The new container created that role and database on first start, and the rest of the dump loads into them. The cost of this method is downtime: writes have to stop for the whole dump and restore, so it grows with the size of the data. Option B: pg_upgrade with hard links For larger databases, pg_upgrade --link recreates the schema in the new cluster and hard-links the data files instead of copying them. The pg_upgrade documentation says link mode is "much faster (no file copying)" and uses less disk space. It needs the binaries of both versions in one place, which the official image does not provide. The tianon/docker-postgres-upgrade project publishes images tagged OLD-to-NEW, including 14-to-18. Its README calls it a proof of concept to "learn from" and adapt, not a finished tool, and that matches what we saw. We upgraded a throwaway 14.24 cluster created with POSTGRES_USER=app, which is how most compose files set it up, to 18.6. It failed three times before it worked, and none of the three is specific to our setup. Error 1: data checksums old cluster does not use data checksums but the new one does Failure, exiting The PostgreSQL 18 release notes list this under incompatibilities: initdb now enables data checksums by default, and "pg_upgrade requires matching cluster checksum settings". A cluster created by the postgres:14 image has checksums off unless you asked for them. The fix is to create the new cluster with --no-data-checksums. If you want checksums, turn them on afterwards with pg_checksums on the stopped cluster. Its documentation warns that on a large cluster this "can potentially take a long time", and nothing else may write to the data directory while it runs. Error 2: the superuser name FATAL: role "postgres" does not exist could not connect to source postmaster started with the command: ... The helper image runs initdb as the default postgres superuser and connects to the old cluster with that name. If your compose file set POSTGRES_USER=app, the old cluster has no role called postgres. Pass the real name to both initdb and pg_upgrade. With both fixes in place, run the check mode first. It changes no data: # old cluster stopped, its files moved to /srv/pg/14/docker docker run --rm \ --mount type=bind,src=/srv/pg,dst=/var/lib/postgresql \ -e PGDATAOLD=/var/lib/postgresql/14/docker \ -e PGDATANEW=/var/lib/postgresql/18/docker \ -e POSTGRES_INITDB_ARGS='--no-data-checksums --username=app' \ tianon/postgres-upgrade:14-to-18 --check --username=app When it prints *Clusters are compatible*, run the same command with --link --username=app in place of --check --username=app. Error 3: the app can no longer connect The upgrade finished and postgres:18 started on the new data. Then a client in another container got this: FATAL: no pg_hba.conf entry for host "172.26.0.3", user "app", database "app", no encryption The official image adds a line to pg_hba.conf that allows password logins from other hosts, but only when it initializes an empty data directory. Here initdb ran inside the helper image, so the new pg_hba.conf only trusts local connections. The pg_upgrade documentation warns about this in its "Restore pg_hba.conf" step and adds that postgresql.conf and postgresql.auto.conf may need the same treatment. Copy your old files over if you changed them. If you only had the image defaults, add the line back: docker exec pg18 sh -c 'echo "host all all all scram-sha-256" >> "$PGDATA/pg_hba.conf"' docker exec pg18 psql -U app -c 'SELECT pg_reload_conf()' The volume path changed in the 18 image Starting with 18, the official image keeps data in a version-specific directory, /var/lib/postgresql/18/docker, and declares its volume at /var/lib/postgresql. The image documentation says mounts should target the new location, and pull request #1259 explains why: with one mount for both versions, pg_upgrade --link works without crossing filesystems. Update the service definition to match: db: image: postgres:18 volumes: - /srv/pg:/var/lib/postgresql # not /var/lib/postgresql/data If you just change the tag and leave the old mount, the 18 entrypoint script refuses to start and prints: Error: in 18+, these Docker images are configured to store database data in a format which is compatible with "pg_ctlcluster" ... That is a safe failure. It is still better to see it in a test run than at 2 a.m. After the upgrade PostgreSQL 18's pg_upgrade keeps optimizer statistics, according to its release notes, but our run still ended with "Some statistics are not transferred by pg_upgrade" and suggested two vacuumdb commands. Run them, then look for passwords stored as MD5: docker exec pg18 vacuumdb -U app --all --analyze-in-stages --missing-stats-only docker exec pg18 vacuumdb -U app --all --analyze-only docker exec pg18 psql -U app -Atc "SELECT rolname FROM pg_authid WHERE rolpassword LIKE 'md5%'" The PostgreSQL 14 release notes switched the default password hashing to SCRAM-SHA-256, but roles carried over from 13 or older can still hold MD5 hashes. PostgreSQL 18 deprecates MD5 authentication and says support will be removed in a future major release, so reset those passwords while you are already in a maintenance window. Two more things. In link mode the old cluster is unsafe to start once the new one has run, as the pg_upgrade docs warn, so your rollback is the backup from step 3, not the old directory. And keep that old directory until the application has run a full business cycle on 18, then delete it with the delete_old_cluster.sh script pg_upgrade leaves behind. The same habit that keeps Postgres current applies to every base image you run. We covered it for PHP in PHP2Shell: patch the PHP underneath WordPress, and the planning side of a hard end-of-support date in our Exchange 2016/2019 exit plan. If you are cleaning up a compose stack anyway, sending container logs to stdout is another small fix worth doing in the same pass. Frequently asked questions Will my PostgreSQL 14 database stop working on 12 November 2026? No. It keeps running. End of life means the project stops releasing fixes, so security and data corruption bugs found after the final 14 release will not be patched. Can I upgrade from 14 straight to 18? Yes. pg_upgrade supports any source from 9.2 onward, and dump/restore works across any versions. Read the release notes of 15, 16, 17 and 18 for incompatibilities first. Should I wait for PostgreSQL 19? We would not. 19 is planned for 19 November 2026, after 14 loses support. Upgrading to 18 now and to 19 later is two planned jobs instead of one rushed one. Is it enough to change the image tag from 14 to 18? No. The data directory has to be upgraded with pg_upgrade or dump/restore. With an old-style mount, the 18 image refuses to start and points you to pg_upgrade. Sources: PostgreSQL versioning policy, PostgreSQL 19 RC 1 announcement, PostgreSQL 18 release notes, PostgreSQL 14 release notes, pg_upgrade documentation, Amazon RDS for PostgreSQL release calendar, postgres image documentation and tianon/docker-postgres-upgrade. Commands tested on Docker with postgres:14 (14.24) and postgres:18 (18.6) on 1 November 2026. Originally published on fanpino.com.

  3. PHP2Shell で、対応をオープンにしている会社 はじめに 2026 年 10 月 21 日に修正版が出た PHP の深刻な脆弱性 PHP2Shell では、修正版が出る前からサービスを止める会社も出るなど、緊急の対応が相次ぎました。 前回は、レンタルサーバー各社で、脆弱性のある状態で利用者のサイトに外部からアクセスできた時間を並べ、自前でサービスを動かしている所も 5 つ図に入れました。 前回の終わりに書いたとおり、自社でサービスとインフラの両方を持っていれば、止めるか、配布元を待たずに自分でビルドするか、いつ再開するかを、すべて自社で決められます。 今回は、自前で動かしている会社のうち、対応の経緯をオープンにしていた 3 社、GREE・pixiv・コドモンを紹介します。 自分たちで判断して手を動かし、その時刻や経緯を記録しているから、ここまでオープンにできるのだと考えています。 3 社がオープンにしていること 3 社とも、公式のお知らせやブログに次の 3 つを書いています。 - サービスを止めた時刻 - サービスを戻した時刻(一部だけ戻したのか、全部戻したのかも分けて) - 止めた理由が PHP の脆弱性であること 攻撃を避けるためにソフトウェアの名前を伏せる所や、始めた時刻だけをお知らせする所も多い中で、3 社はここまで公開していました。 3 社の止めた時間 gantt title 3 社がサービスを止めていた時間(日本時間) dateFormat YYYY-MM-DD HH:mm axisFormat %m/%d %H時 section 予告と公開 予告(oss-security) :milestone, 2026-10-21 05:22, 0m 修正版の公開 :milestone, 2026-10-21 22:34, 0m section GREE サービス全体を停止 :active, 2026-10-21 23:30, 2026-10-22 06:55 SNS と決済は復旧、一部のゲームは復旧中 : 2026-10-22 06:55, 2026-10-22 15:00 section pixiv メンテナンスモード :active, 2026-10-21 19:00, 2026-10-21 23:16 section コドモン 主要サービスを停止 :active, 2026-10-21 14:49, 2026-10-22 03:22 一部の機能は停止を続ける : 2026-10-22 03:22, 2026-10-23 09:00 PHP 公式は日本時間の 10/21 早朝、修正版を 23:00 に出すと予告しました(oss-security への投稿は 5:22)。実際に出たのは 30 分ほど早い 22:34 です(GitHub の security advisory の公開時刻)。 GREE GREE はサービス全体を止めました。 - 10/21 23:30 ごろ: サービス全体を止めて緊急メンテナンスを開始 - 10/22 6:55 ごろ: メンテナンスを終了し、SNS や決済などを復旧(一部のゲームは復旧作業を続ける) - 10/22 15:30: 全体復旧の見込み(15 時ごろ)のあと、タイトルに「【復旧】」と付けてお知らせを更新 修正版が出てから 1 時間ほどでサービス全体を止め、7 時間半ほどで SNS や決済などのプラットフォームの機能から先に戻しています。 お知らせでは、PHP の脆弱性が公表されたことを受けてシステムの安全性を確保するための対応だと、理由を説明しています。使っているソフトウェアの名前を伏せずに示しています。 復旧作業が続いていた一部のゲームについては、各ゲームのお知らせを見るよう案内しています。全体の状況はこのお知らせ、個別のゲームは各ゲームのお知らせ、と案内先を分けています。 全体が復旧する見込みは 10/22 15 時ごろと示し、15:30 にはお知らせのタイトルに「【復旧】」と付けて更新しました。被害やデータの流出は確認されていないことも明記し、問い合わせフォームも案内しています。停止から一部の復旧、全体の復旧まで、段階ごとに時刻を追える書き方になっています。 pixiv pixiv は修正版が出る前にサービスを止めて待機し、予定(23:00)より早く 22:34 に出た修正版を 22:41 に確認して、そこから 35 分で再開しました。 利用者向けのお知らせとは別に、公式ブログ(pixiv inside)に作業の時刻が分単位で残っています。 - 10/21 19:00: 影響を受けうるサービスをメンテナンスモードにする - 22:41: 予定(23:00)より早く出た修正版(22:34 公開)を確認 - 22:42〜22:56: コンテナイメージのビルド - 22:56〜23:16: Kubernetes へのデプロイ(ステージングでの確認を含む) - 23:16: メンテナンスモードを解除 35 分で戻せたのは、修正版が出る前に準備を済ませていたからです。 Docker 公式イメージのビルドスクリプトを使って、PHP のバージョンだけを差し替えられるようにしていました。公式イメージの公開を待たずに、ソースから自前でビルドする経路も用意していました。 Docker 公式イメージの更新がマージされたのは 10/22 0:16 で、pixiv が再開した 23:16 の 1 時間後です。配布元を待たずに自前でビルドしたことで、この 1 時間の待ち時間を減らせています。 またブログでは、メンテナンスモードが入っていないエンドポイントがあったことも課題として挙げています。今回は条件がそろっていただけで、毎回この手順で済むとは限らない、とも振り返っています。 コドモン コドモンは、保育施設と保護者が使うサービスです。 修正版が出る 8 時間ほど前の 14:49 までに、対象となる全サービスを止めました。 - 10/21 9:01: 社内で把握 - 11:37: 23:00 の公開に合わせて、夜に止める方針で合意 - 12:37: 情報が広まってきたのを受けて、前倒しで止めると決定 - 13:00 ごろ〜14:49: 順に止め、全対象サービスの停止を完了 - 22:38: 修正版の公開を検知 - 10/22 3:22: 職員用の管理画面、保護者向けの連絡帳アプリなどを再開 - 10/23 9:00: 最後まで止めていた所在確認アラートを再開 止めると決めた理由も書いています。お預かりしているお子さまの情報を守ることを最優先にした、という判断です。 一部のサービスだけ止める案、お迎えの時間まで動かす案、監視を強めて検知したら止める案も検討し、採らなかった理由を一つずつ書いています。 反省点も並べています。修正版が届く時刻が PHP の配布元ごとに違うことを事前に把握していなかったこと、作業と判断が深夜に集中したこと、予防のための停止だと利用者に伝わりにくかったこと、などです。 何を重視するかで、止め方は変わる 3 社とも自社で決められる立場でしたが、止めた時刻と戻し方はそれぞれ違っていました。コドモンは修正版が出る 8 時間ほど前の 14:49 までに、pixiv は出る前の 19:00 に、GREE は出てから 1 時間ほどあとの 23:30 ごろに止めています。 自分のサービスで同じ判断をするなら、何を重視するかで、次のように選び方が変わります。 | 何を重視するか | 止め方 | 近い対応をした会社 | |---|---|---| | 利用者の情報を守ることを最優先 | 修正版が出る前に、日中でも前倒しで止める | コドモン | | 止める時間を短くしたい | 準備を済ませてから止めて待ち、出たらすぐ当てて戻す | pixiv | | 公開まではふだんどおり動かし、戻すときは確実に | 出てからサービス全体を止め、段階的に戻す | GREE | - コドモン は、お迎えの時間まで動かす案も比べたうえで、お子さまの情報を守ることを最優先にして日中に止めました。主要なサービスを 10/22 3:22 に戻し、残りの機能も翌日に再開しています - pixiv は、修正版をすぐ当てられるように準備してから 19:00 に止めました。修正版が出たあと自前でビルドして当て、23:16 に再開しています - GREE は、修正版が出たあとの 23:30 ごろにサービス全体を止めました。翌朝 6:55 ごろに SNS や決済などのプラットフォームを先に戻し、各ゲームの復旧はそれぞれのお知らせで案内しています 修正版が出る前から止めて安全を優先するやり方も、準備を整えて止める時間を短くするやり方も、公開後に全体を止めて段階的に戻すやり方もあります。唯一の正解はなく、サービスの性質と何を守りたいかで、選び方は変わります。 自分たちで決めて記録しているから、オープンにできる 3 社に共通するのは、サービスもインフラも自社で持っていることです。止めるかどうか、どこまで止めるか、いつ戻すかを、配布元やほかの会社の判断を待たずに自分たちで決められます。 自分たちで決めて自分たちで作業しているので、いつ何をしたか、なぜそうしたかを、自社の記録だけで正確に書けます。 - GREE は、全体を止めるタイミングも、SNS や決済から先に戻す順番も自社で決めています。そのため、停止から一部の復旧、全体の復旧まで、段階ごとの時刻をそのままお知らせに出せています - pixiv は、Docker 公式イメージの更新を待たずに、コンテナイメージのビルドから Kubernetes へのデプロイまでを自分たちで動かしています。作業を自社で完結させているから、分単位の記録をブログに残せています - コドモン は、社内で把握した 9:01 から方針を変えた 12:37 まで、判断した時刻と理由を記録していました。自分たちで比較して決めているから、採らなかった案とその理由まで公開できています 判断から作業までが自社の中で完結していて、その記録も残っているから、ここまで具体的な時刻や経緯をオープンにできます。 前回のレンタルサーバーは、この点で条件が違います。インフラは業者のものでも、その上で動くサイトは利用者のものです。業者が利用者のサイトを止めれば、利用者の業務も止まるため、業者だけの判断では止めにくい事情があります。また、自前でビルドするのではなく配布元のパッケージを待つ構成なら、対応を始める時刻も配布元しだいになります。利用者との関係や作業工程の制約があるので、すべてを自社だけで決めて動ける 3 社と同じようにはいかない所があるのは仕方ありません。 おわりに 今回紹介した 3 社は、対応を自社で決めるだけでなく、サービスを止めた時刻と再開した時刻、そして PHP の脆弱性への対応であることを明記していました。さらに pixiv はビルドからデプロイまでの分単位の記録を、コドモンは方針変更の時刻や採らなかった案の理由まで公開しています。 自分たちで判断して対応し、その記録を残しているから、いつ止めていつ戻したかを正確に出せるだけでなく、判断や作業の経緯までオープンにできる。そこがこの 3 社の良さです。 本記事の情報は 10/29 時点のものです。 参考文献 - GREE「【復旧】緊急メンテナンスについて」 games.gree.net - pixiv お知らせ pixiv.net - pixiv inside「PHP2Shell」対応の記録 inside.pixiv.blog - コドモン Product Team Blog「PHP2Shellへの対応を振り返って」 tech.codmon.com - oss-security(予告) openwall.com - Docker official-images #22541 official-images#22541 - GitHub の security advisory(修正版の公開の時刻 13:34 UTC)github.com/php/php-src/security/advisories/GHSA-w… ・ github.com/php/php-src/security/advisories/GHSA-6…

    1 like
  4. PHP2Shell で、サイトが無防備だった時間 はじめに PHP2Shell について、Qiita にもいろいろな記事が出ています。 みなさんの記事を読んでいてふと思いました。 利用者のサイトが脆弱性のある状態で外部からアクセスできるままになっていた時間を、各社のお知らせから測ってみようか、と。 ちなみに、私が昔いた現場はパッチの適用が数日遅れていました。さもありなん、です。 PHP の脆弱性の話になると、PHP を使う開発者だけでなく、レンタルサーバーの業者までいい加減なのでは、と言われがちです。私も最初はそう思っていました。 脆弱性のある状態で利用者のサイトを外部にさらしていたこと自体は、インフラを預かる業者の責任です。ただ、それが「いい加減だったから」なのか、作業の工程上しかたない部分があったのかを、公式のお知らせの時刻で確かめます。 予告から公開まで PHP 公式は日本時間の 10/21 早朝、深刻度 9.2 の脆弱性 2 件の修正版を 23:00 に出すと予告しました(oss-security への投稿は 5:22)。実際に出たのは 22:34 です(GitHub の security advisory の公開時刻)。 gantt title 予告から修正版の公開まで(日本時間) dateFormat YYYY-MM-DD HH:mm axisFormat %m/%d %H時 section 予告と公開 予告(oss-security) :milestone, 2026-10-21 05:22, 0m 修正版がない時間(17 時間あまり) : 2026-10-21 05:22, 2026-10-21 22:34 修正版の公開 :milestone, 2026-10-21 22:34, 0m 一方で、悪用の試みは公表前の 10/17 から見つかっていたことが判明しています。 予告より早く出た理由は公表されていません。 各社の対応時刻と停止期間 安全になるための条件は、パッチを当て終えることです。 ただ、当て終えた時刻だけでは、危険だった時間は分かりません。先にサイトを止めていれば、そのあいだは外部からアクセスできないからです。 そこで、止めた時間と当て終えた時刻の両方を図にしました。 図の最上段は予告と公開の時刻です。その下がレンタルサーバー、いちばん下が自前でサービスを動かしている所で、バーの色は次のとおりです。 - 赤いバー(露出): 予告のあと、サイトの停止や緩和策、パッチの適用がまだで、脆弱性のある状態で外部からアクセスできた時間 - 薄い青のバー(停止): サイトやサービスを止めていた時間 - 青紫のバー: 緩和策を入れていた時間、またはパッチを順番に当てていた時間 - 灰色のバー: パッチを当て終えた後、または表示を再開した後 「自前」は、レンタルサーバーを借りずに、自社のサービスを自社で用意したサーバー(クラウドを含む)で動かしている所のことです。外部から見ても PHP を使っているかどうか分からないので、止めた時間だけを描いています。 gantt title 予告から公開後までの状態(日本時間) dateFormat YYYY-MM-DD HH:mm axisFormat %m/%d %H時 section 予告と公開 予告(oss-security) :milestone, 2026-10-21 05:22, 0m 修正版の公開 :milestone, 2026-10-21 22:34, 0m section レンタルサーバー エックスサーバー 露出 :crit, 2026-10-21 05:22, 2026-10-21 12:00 エックスサーバー 緩和策のみ・順次適用(全部の完了は 10/26) : 2026-10-21 12:00, 2026-10-23 12:30 コアサーバー V1 ほか 露出 :crit, 2026-10-21 05:22, 2026-10-22 00:50 コアサーバー V1 ほか 適用済み :done, 2026-10-22 00:50, 2026-10-23 12:30 mixhost 露出 :crit, 2026-10-21 05:22, 2026-10-22 10:18 mixhost 適用済み :done, 2026-10-22 10:18, 2026-10-23 12:30 ヘテムル 露出 :crit, 2026-10-21 05:22, 2026-10-22 03:05 ヘテムル サイトを停止 :active, 2026-10-22 03:05, 2026-10-22 11:24 ヘテムル 表示を再開 :done, 2026-10-22 11:24, 2026-10-23 12:30 お名前.com 露出 :crit, 2026-10-21 05:22, 2026-10-22 20:45 お名前.com 適用済み :done, 2026-10-22 20:45, 2026-10-23 12:30 KAGOYA 露出 :crit, 2026-10-21 05:22, 2026-10-22 14:40 KAGOYA 順次適用 : 2026-10-22 14:40, 2026-10-23 00:00 KAGOYA 適用済み :done, 2026-10-23 00:00, 2026-10-23 12:30 section 自前で動かすサービス BASE 停止 :active, 2026-10-21 15:50, 2026-10-22 08:00 ジンジャー 停止(終わりは予定の時刻) :active, 2026-10-21 17:00, 2026-10-22 05:00 freee の 4 サービス 停止 :active, 2026-10-21 19:04, 2026-10-22 12:56 Garoon クラウド版 停止 :active, 2026-10-21 21:00, 2026-10-22 03:01 LoGo フォームの PDF 印刷 停止 :active, 2026-10-21 21:00, 2026-10-22 02:09 図に入れた自前の 5 つは songchong さんの記事から取ったもので、どれも修正版が出る前に止めています(同じ記事にあるほかの 6 つは、公開のあとに止めています)。 レンタルサーバー各社の動きは次のとおりです。 - ヘテムル・ロリポップ!: パッチが当たるまで利用者のサイトごと止めた 2 社です(ロリポップ!は止めた時刻の記載がないので図にはありません) - エックスサーバー: サイトは止めませんでしたが、公開前の 12:00 までに緩和策を入れ、公開後は順次パッチを当てています - コアサーバー V1 など: 止めずに、公開から 2 時間ほどで当て終えています 図に入れていない所は次のとおりです。 | 事業者 | 止めた・緩和策 | パッチの適用 | |---|---|---| | ロリポップ! | 10/21 からサイトの表示を停止 | 10/22 15:33 の更新で適用済み | | さくらのレンタルサーバ | 10/21 22:30 から会員関連の機能を緊急メンテナンス | 10/23 に適用済み | | WebARENA | 10/21 に緩和策と、海外からのアクセスの停止 | 10/22 17:30 から緊急メンテナンス | | ConoHa WING | ファイルマネージャーと Web メールを停止 | 10/22 時点で順次適用中 | | CPI | — | 8.2〜8.4 は 10/22、7.4・8.0 は 10/27〜10/29 | レンタルサーバーの時刻は、各社の公式のお知らせから拾いました(10/27 に確認)。並べたのはお知らせに書かれた時刻(またはお知らせの更新時刻)で、必ずしも全部のサーバーに当て終えた時刻ではありません。 ただ、こうして並べても「各社が何時間、脆弱性のある状態で外部にさらしていたか」は一つの数字に決まりません。 10/17(悪用の試みが見つかった日)と 10/21(公式の予告)のどちらから数えるかで 4 日ずれますし、サイトを止めたのか、お知らせに書いていないだけなのか、区別のつかない所があるからです。 !どこから数えるか(10/17〜10/29) 当て終えるまでに時間がかかった理由(推定) - 公式の修正版は 8.2〜8.5 だけで、古いバージョンはバックポートか独自の修正待ち - 一度に当てず順番に当てたので、全体が終わるまで時間がかかった(KAGOYA は 14:40〜24:00 の 9 時間あまり。順番にした理由はお知らせに書かれていません) - パッケージで PHP を入れている場合は、配布元(CloudLinux や remi など)のビルドを待つ必要があった(下の表) - 公開直後は配布元にアクセスが集中し、つながりにくくなった所があった(Ubuntu 向けパッケージの配布に使われる Launchpad が 503 エラー、php.net が 504 エラー) !修正版がサーバーに当たるまでの 3 つの経路 配布元が修正版のパッケージを出した時刻は次のとおりです(比較のため、先頭に PHP 公式の公開時刻を入れています)。 | 配布元 | 出た日時(日本時間) | 修正版の公開から | |---|---|---| | PHP 公式(ソースコード) | 10/21 22:34 | — | | Debian(DSA 6574-1) | 10/21 22:45 | 11 分 | | Ubuntu(USN-8948-1) | 10/21 22:56 | 22 分 | | Debian LTS(DLA 4848-1) | 10/21 23:11 | 37 分 | | Ondřej Surý の PPA(Launchpad) | 10/21 23:45 | 1 時間 11 分 | | Docker 公式イメージ(official-images へのマージ) | 10/22 0:16 | 1 時間 42 分 | | Red Hat Enterprise Linux 8〜10(AlmaLinux 9 も同じ日) | 10/21(UTC・時刻なし) | — | | remi(RHEL 系で広く使われるリポジトリ) | 10/22 21:58 | 23 時間 24 分 | | Red Hat の延長サポート向け(EUS など) | 10/23(UTC・時刻なし) | — | 多くの配布元は、公開から 2 時間以内にパッケージを出しています。待ちが長かったのは remi と、Red Hat の延長サポート向けです。 レンタルサーバー各社が修正をどこから得たかは、お知らせに次のように書かれています。 | 事業者 | 修正の出どころ(お知らせの記載) | |---|---| | エックスサーバー | 8.2〜8.5 は修正版に更新、5.1〜8.1 はバージョンを変えずに修正パッチを適用。出どころの記載なし | | コアサーバー V1 | 記載なし | | mixhost | 採用している CloudLinux の修正パッケージ。サポートが終わったバージョンも CloudLinux が独自に修正 | | ヘテムル | 公式の修正版がない古いバージョンは、自社で修正を作成 | | ロリポップ! | 公式の修正版が出たバージョンはそれを適用し、ほかは自社で独自に修正 | | お名前.com | バージョンを変えずにパッチを適用。出どころの記載なし | | KAGOYA | 修正版に更新。出どころの記載なし | | さくらのレンタルサーバ | 記載なし | | WebARENA | 記載なし | | ConoHa WING | バージョンを変えずにパッチを適用。出どころの記載なし | | CPI | 8.2〜8.4 は修正パッチを適用、7.4・8.0 はパッチ適用版を準備。出どころの記載なし | 配布元のパッケージを待っていたと書いている所はありませんでした。当て終えるまでにかかった時間のうち、どこまでが配布元の待ちによるものかは、お知らせからは分かりません。 主な配布元の時刻を、さきほどの図の最上段に足すと次のとおりです(自前の段は省きました)。レンタルサーバー各社のバーと見比べられます。 gantt title 配布元の公開時刻とレンタルサーバーの対応(日本時間) dateFormat YYYY-MM-DD HH:mm axisFormat %m/%d %H時 section 予告・公開・配布元 予告(oss-security) :milestone, 2026-10-21 05:22, 0m 修正版の公開 :milestone, 2026-10-21 22:34, 0m Debian(22時45分)・Ubuntu(22時56分) :milestone, 2026-10-21 22:45, 0m Ondřej Surý の PPA :milestone, 2026-10-21 23:45, 0m Docker 公式イメージ :milestone, 2026-10-22 00:16, 0m remi :milestone, 2026-10-22 21:58, 0m section レンタルサーバー エックスサーバー 露出 :crit, 2026-10-21 05:22, 2026-10-21 12:00 エックスサーバー 緩和策のみ・順次適用(全部の完了は 10/26) : 2026-10-21 12:00, 2026-10-23 12:30 コアサーバー V1 ほか 露出 :crit, 2026-10-21 05:22, 2026-10-22 00:50 コアサーバー V1 ほか 適用済み :done, 2026-10-22 00:50, 2026-10-23 12:30 mixhost 露出 :crit, 2026-10-21 05:22, 2026-10-22 10:18 mixhost 適用済み :done, 2026-10-22 10:18, 2026-10-23 12:30 ヘテムル 露出 :crit, 2026-10-21 05:22, 2026-10-22 03:05 ヘテムル サイトを停止 :active, 2026-10-22 03:05, 2026-10-22 11:24 ヘテムル 表示を再開 :done, 2026-10-22 11:24, 2026-10-23 12:30 お名前.com 露出 :crit, 2026-10-21 05:22, 2026-10-22 20:45 お名前.com 適用済み :done, 2026-10-22 20:45, 2026-10-23 12:30 KAGOYA 露出 :crit, 2026-10-21 05:22, 2026-10-22 14:40 KAGOYA 順次適用 : 2026-10-22 14:40, 2026-10-23 00:00 KAGOYA 適用済み :done, 2026-10-23 00:00, 2026-10-23 12:30 危険な時間を減らすには ここからは私の見方です。 いい加減だったのか 測ってみた結果、業者がいい加減だったとは言えません。時間がかかった所も、作業の工程の限界によるものだと私は見ています。 - 主な配布元は修正版の公開から 2 時間以内にパッケージを出し、レンタルサーバーでも公開から 2 時間ほどで当て終えた所(コアサーバー V1 など)があります - 予告から公開までのあいだに、手を打った所があります。エックスサーバーは 12:00 までに緩和策を入れ、ヘテムルは 13:30 ごろから phpMyAdmin などの機能を止め始め(サイトの表示を止めたのは 10/22 3:05)、さくらは 22:30 から会員関連の機能の緊急メンテナンスに入りました - 公式の修正版がない古いバージョンにも手当てをしています。ヘテムルとロリポップ!は自社で修正を作り、mixhost は CloudLinux の修正を使い、エックスサーバーは 5.1 まで対応を広げました もちろん、パッチが当たるまでのあいだ、脆弱性のある状態で利用者のサイトを外部にさらしていた責任は業者にあります。 ただ、完了が翌日以降になった所も、手を抜いていたとは言えません。古いバージョンを抱えていたり、配布元のパッケージを待つ構成だったり、順番に当てる必要があったりすれば、作業の工程上、すぐには当て終えられないからです。 CERT-EU・英国の NCSC・米国の NSA・日本の IPA は、すぐ当てられないなら止めるよう呼びかけていました。サイトを止めてからパッチを当てるやり方は、再開が遅くなっても安全です。呼びかけどおりサイトごと止めたヘテムルとロリポップ!がこれにあたります。 とはいえ、レンタルサーバーが利用者のサイトを止めるのは簡単ではありません。 レンタルサーバーの業者はインフラを持っていますが、その上で動くサイト(サービス)は利用者のものです。止めれば利用者の業務が止まり、動かし続ければ脆弱性にさらされるので、業者だけでは決めにくい立場にあります。 これに対して、自社でサービスとインフラの両方を持っている会社は、サービスを止めるか、配布元を待たずに自分でビルドするか、いつ再開するかを、すべて自社で決められます。さきほどの図で、自前の 5 つが修正版の公開前に止めていたのも、自社で決められる立場にあったから取れた手です。 時刻を分単位で出している所は、測られてもいいと分かって出している所です。書いていない所が悪い、という話ではありません。 どこで露出していたか ここまでの図と表から、露出していた時間は 2 つに分かれます。 !露出していた時間は 2 つに分かれる(レンタルサーバー) 1 つ目の「予告から公開まで」は、この図のレンタルサーバーではエックスサーバーが 12:00 までに緩和策を入れ、さきほどのガントチャートにあった自前の 5 つが公開前にサービスを止めていました。 2 つ目の「公開から当て終える・止めるまで」は、サポート切れの古いバージョンほど長引いています。公式の修正版がないため自社で作るか CloudLinux などから得る必要があり、図に入れていない CPI の PHP 7.4・8.0 は適用が 10/27〜10/29 でした。 減らすためにできること 1 で「予告から公開まで」、2・3 で「公開から当て終えるまで」の 2 つの露出時間を減らし、4・5 で利用者ごとの影響と確認の手間を減らせます。 !2 つの時間を、どこで減らすか 1. 予告の段階(止めるか遮断するか): 止めるか動かし続けるかを利用者が前もって選べる設定(Google Compute Engine のメンテナンス設定のようなもの)を用意し、動かし続ける利用者は WAF で攻撃のリクエストを遮断します。Cloudflare は今回、修正版の公開の 10 時間半ほど前に、PHP2Shell を遮断するルールを既定でオンにしています 2. 配布元を待たない経路: ソースからビルドできるようにしておけば、パッケージを待たずに済みます。pixiv は公式イメージの公開を待たずにビルドする経路を用意していて、修正版に気づいてから 35 分で再開しました。Docker 公式イメージの更新がマージされたのは、その 1 時間後の 10/22 0:16 です 3. サポート切れのバージョンへの備え: 古いバージョンほど修正に時間がかかりました。移行の期限を決めておくか、TuxCare のようなバックポートを使えるようにしておきます 4. アカウントごとの実行環境の分離: 順番に当てると全体が終わるまで時間がかかります。mixhost が採用している CloudLinux には、アカウントごとに PHP を選べる PHP Selector があります 5. 露出していた時間の利用者ごとの通知: 順番に当てた場合、全体の完了時刻だけでは自分のサイトがいつまで露出していたか分かりません。その利用者のサイトに外部からアクセスできた時間を知らせます 処理の流れにすると、次のとおりです。 flowchart TD A[予告が出る] --> B{利用者ごとの設定} B -->|止めて待つ・既定| C[サイトを止める] B -->|動かしたまま| D[WAF で攻撃のリクエストを遮断する] E[上流を待たずにソースからビルド] --> F[修正版を当てる] G[サポート切れのバージョン] -->|期限を決めて移行か、バックポート| F C --> F D --> F F --> H[再開して、外部からアクセスできた時間を利用者ごとに通知] subgraph アカウントごとに分けた PHP の実行環境 B C D F end !危険な時間を減らす処理の流れ 必要な仕組みは、どれも既存の製品や機能にあります。これらをあらかじめ組み合わせておけば、作業の工程でどうしてもかかる時間があっても、外部にさらす時間は減らせます。 もっとも、いちばん強いのは、外部からアクセスできる公開側に PHP の実行環境(ランタイム)を置かないことです。前回の話のとおり、置かなければ、さきほどの対応の図に並ぶこともありません。 おわりに 脆弱性のある状態でサイトを外部にさらしていた責任はレンタルサーバー業者にあります。ただ、時間がかかったのは業者がいい加減だったからではなく、作業の工程や、利用者のサイトを業者だけでは止めにくい立場によるものです。そのうえで、前もって仕組みを組み合わせておけば、危険な時間はまだ減らせます。 一方で、自社でサービスとインフラの両方を持っていれば、止める判断もビルドの手順も自分で決められます。次の記事では、自前でサービスを動かしている会社のお知らせから、止めた時刻と戻した時刻、そこに至る経緯まで詳しく公開していた 3 社(GREE・pixiv・コドモン)を取り上げています。 10/27 時点の情報です。サイトの側から見た Tivo0921 さんの記事もあわせてどうぞ。 興味のある方の参考になれば幸いです。ご指摘は歓迎します。 参考文献 - PHP News Archive 2026 php.net - oss-security(予告) openwall.com - 配布元: Debian DSA 6574-1・Debian DLA 4848-1・Ubuntu USN-8948-1・Ondřej Surý の PPA・Docker official-images #22541・Red Hat・remi - GitHub の security advisory(修正版の公開の時刻 13:34 UTC)github.com/php/php-src/security/advisories/GHSA-w… ・ github.com/php/php-src/security/advisories/GHSA-6… - Tenable(10/17 の悪用の試み) tenable.com - 各社のお知らせ: エックスサーバー・同 停止・コアサーバー V1・mixhost・ヘテムル・ロリポップ!・お名前.com・KAGOYA・さくら・WebARENA・ConoHa WING・CPI - UptimeRobot(配布元の混雑) uptimerobot.com - IPA 注意喚起 ipa.go.jp - Cloudflare の WAF(ppc.land) ppc.land - CloudLinux PHP Selector support.cpanel.net - TuxCare(Plesk) plesk.com - Google Compute Engine cloud.google.com

    1 like
  5. The Compose Race Your Coding Agent Inherits From You !Hero artwork: a robotic arm stamping green checkmarks on a conveyor belt of identical generated compose files, the finished stack toppling into a wall labeled connection refused Ask a coding agent for a Docker Compose file with Postgres, then cold-boot the result on a machine that has never run it. There is a good chance the app container starts, immediately asks for a database that does not exist yet, and fails its first connections. Wait a few seconds, try again, and everything works. The agent will tell you it tested the file. Both things are true, and the bug is older than the agent: it inherited the race from the tutorials it was trained on, and it also inherited the half-fix that looks like the fix. Docker Compose is a tool for defining and running multi-container applications from one YAML file. I tested the generated shape, the half-fixed shape, and the corrected shape on my own host this week: three cold starts on fresh volumes (Docker Engine 29.1.3, Compose v5.1.4), the inherited race, the inherited half-fix that still races, and the three-line correction that actually holds. Everything below is from that run, with the run methodology and the timestamps as captured. > Key Takeaways > - A bare depends_on: list only orders container startup. Compose explicitly does not wait for "ready", only "running" (Docker docs). > - The most-copied Postgres healthcheck, pg_isready -U postgres, probes the Unix socket, and during first-boot initialization a temporary Postgres server answers on that socket before the real one is up (docker-library/postgres issue #146). > - In my run, that socket-probe healthcheck reported the database healthy 4 seconds before Postgres actually accepted a TCP connection. The gate was wired correctly. The probe lied. > - The fix is three edits: condition: service_healthy, restart: true, and -h 127.0.0.1 on the probe. My corrected file connected on attempt one, on a cold volume. In this post: - [What does the agent actually write?](#what-does-the-agent-actually-write) - [Why does the race survive a working demo?](#why-does-the-race-survive-a-working-demo) - [What did I actually run?](#what-did-i-actually-run) - [Why doesn't the copied healthcheck fix it?](#why-doesnt-the-copied-healthcheck-fix-it) - [What does the fixed file look like?](#what-does-the-fixed-file-look-like) - [How do you review agent YAML in 60 seconds?](#how-do-you-review-agent-yaml-in-60-seconds) - [FAQ](#faq) What does the agent actually write? Two lines date a compose file, and agents write both of them together. The first is the bare dependency list. In the long form of depends_on, per the compose-file services reference, this same key takes a condition instead, which is the part the 2021 corpus never learned: services: app: depends_on: - db The second is the socket-probe healthcheck on the database. healthcheck is a container configuration that makes the runtime probe the service at a fixed interval and report a health status: db: image: postgres:16-alpine healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] Neither line is broken on its own. The first one does exactly what it says: start db before app. The second one does exactly what it says: exit 0 when something answers on the Postgres Unix socket. The problem is what they don't say, and they cluster in generated files because they cluster in the training corpus. Both come from the same generation of quick-start guides, the ones that shipped years before the long-form depends_on with conditions was common knowledge. The evidence that this is a corpus problem, not a reasoning problem, is now measured. Work on updating model knowledge after API changes found that models continue generating outdated API calls "even with access to current documentation" (arXiv:2506.20495). A study of deprecated-API recommendations reports that 37.4 percent of GPT-3.5's API predictions were deprecated, and that fixing this by retraining is impractical because library APIs change too fast (arXiv:2511.21022). Compose YAML is not a Python library call, but the mechanism transfers exactly: the spec moved, the tutorials didn't, and the average of the corpus is the 2021 shape. !Diagram of the inheritance pipeline: dated tutorial books flow into a glowing model core that outputs a compose file card, which feeds a container stack where a race condition plays out Why does the race survive a working demo? Because a warm volume hides it. Postgres only does its slow first-boot work when the data directory is empty. The entrypoint script runs initdb, starts a temporary server for the setup phase, runs anything mounted in /docker-entrypoint-initdb.d/, and only then hands over to the real server as PID 1. On a volume that has already been initialized, the container skips all of that, and the gap between "container running" and "accepting connections" shrinks to near zero. Whoever verifies the compose file, human or agent, usually verifies it on a warm volume. The demo passes. The agent's confidence is not fake; the test genuinely passed on the machine it had. The first cold start is the one that pays for it: CI runner, teammate's laptop, fresh VM. That is the boot where the app container starts a beat after the database container and asks a half-initialized Postgres for a connection. Here is the output of that exact cold start from my run, an alpine app probing db:5432 every half second in a loop. Consider it the shape of every "it works on the second try" bug report you have ever closed: app-1 | ATTEMPT 1: CONNECTION-REFUSED app-1 | ATTEMPT 2: CONNECTION-REFUSED app-1 | ATTEMPT 3: CONNECTION-REFUSED app-1 | ATTEMPT 4: CONNECTION-REFUSED app-1 | ATTEMPT 5: CONNECTED Two seconds of refused connections on a fast local disk. The database log tells you why: the "ready to accept connections" line lands at 08:10:28, after the app has already been asking. On a slower disk, or with init scripts in /docker-entrypoint-initdb.d/, the window stretches to seconds more. If your app has restart: unless-stopped, Compose quietly restarts it until it sticks, the stack comes up, and the race is invisible again until the morning it crash-loops. I documented the anatomy of this race in the healthcheck deep-dive earlier in this series. The part that is new here is where the YAML came from, and the fact that the obvious fix, asking the agent to add a healthcheck, often doesn't fix it. <!-- UNIQUE INSIGHT: the inherited race and the inherited half-fix come from the same corpus generation, so the agent reproduces both together. --> What did I actually run? <!-- ORIGINAL DATA: reproduced locally, Docker Engine 29.1.3, Docker Compose v5.1.4, alpine:3.20 + postgres:16-alpine, 2026-10-29, three runs on fresh volumes. --> Three cold starts on my own host: Docker Engine 29.1.3, Docker Compose v5.1.4, with alpine:3.20 as the app and postgres:16-alpine as the database. Every run started from a deleted volume, so Postgres went through its full first-boot initialization each time. The app is deliberately dumb, a shell loop that tries db:5432 every half second and prints the result, so the timing is visible and nothing else can mask it: services: app: image: alpine:3.20 depends_on: - db command: > sh -c 'i=0; while [ $$i -lt 30 ]; do i=$$((i+1)); if nc -z -w 1 db 5432 2>/dev/null; then echo "ATTEMPT $$i: CONNECTED"; exit 0; else echo "ATTEMPT $$i: CONNECTION-REFUSED"; fi; sleep 0.5; done; echo "GAVE UP"' For the third run, to make the initialization window wide enough to observe, I mounted a one-line init script into /docker-entrypoint-initdb.d/ that sleeps four seconds. The sleep does not create the gap, it only stretches the same initialization phase that already existed in the first run, and every timestamp below comes from docker inspect and the Postgres log, not from my watch. The three runs, all from the same host: | Run | File shape | Outcome | |---|---|---| | 1 | bare depends_on list | 4 refused connections, connected on attempt 5 | | 2 | service_healthy + TCP probe | connected on attempt 1, app held until Healthy | | 3 | service_healthy + socket probe | "healthy" 4s early, 7 refused connections after the verdict | Why doesn't the copied healthcheck fix it? Here is where the inherited fix becomes worse than the inherited race. Ask an agent to fix the startup race and it will, correctly, reach for the long-form dependency. service_healthy is the depends_on condition that holds the dependent container back until the dependency's healthcheck reports healthy: app: depends_on: db: condition: service_healthy That is the right gate. Compose will not start the app until the database's healthcheck reports healthy. But the healthcheck it copies, the same one from the same tutorials, is pg_isready -U postgres, and pg_isready without a host flag connects to the Unix socket. pg_isready is the Postgres client's readiness probe; it exits 0 when a server accepts connections. On the official Postgres image, first boot runs a temporary server for initialization that listens on the Unix socket only. The probe passes while the real server, the one your app connects to over TCP, is not up yet. The maintainers have been explaining this in issue #146 for years: "Simply adding -h 127.0.0.1 to the pg_isready command line should be enough to detect readiness correctly." I ran that combination, the correct gate with the copied probe, and watched docker inspect while it happened. The healthcheck went starting at +0.78 seconds and healthy at +3.02 seconds. The real server accepted its first TCP connection at +7.06 seconds. Four seconds of lie. Compose trusted the verdict, started the app, and the app did this: app-1 | ATTEMPT 1: CONNECTION-REFUSED app-1 | ATTEMPT 2: CONNECTION-REFUSED app-1 | ATTEMPT 3: CONNECTION-REFUSED app-1 | ATTEMPT 4: CONNECTION-REFUSED app-1 | ATTEMPT 5: CONNECTION-REFUSED app-1 | ATTEMPT 6: CONNECTION-REFUSED app-1 | ATTEMPT 7: CONNECTION-REFUSED app-1 | ATTEMPT 8: CONNECTED Seven refused connections after the container was declared healthy. The probe log shows the mechanics, and it is worth reading slowly because it is the whole post in three lines: 08:13:52.4 exit 0 /var/run/postgresql:5432 - accepting connections 08:13:54.5 exit 0 /var/run/postgresql:5432 - accepting connections 08:13:56.5 exit 2 /var/run/postgresql:5432 - no response The first two probes passed against the temporary initialization server, on the socket. Then the entrypoint shut that server down to finish init work, and the third probe failed, no response, while the real server was still seconds away. The status even flipped to unhealthy for a moment during the handoff. If the failure had landed one probe later, after the container was already marked healthy, the streak would have needed three consecutive failures to matter, and by then the app had long since started and crashed. This is the failure mode that makes people conclude the healthcheck pattern "doesn't work" and delete it. The gate worked. The probe answered a different question than the one the app asks. What does the fixed file look like? Three edits to the generated file, and my run 2 output after them: Container racefix-db-1 Started Container racefix-db-1 Waiting Container racefix-db-1 Healthy Container racefix-app-1 Starting Container racefix-app-1 Started app-1 | ATTEMPT 1: CONNECTED The app is not created until db reports Healthy, and its very first connection lands. The full file: services: app: image: alpine:3.20 depends_on: db: condition: service_healthy restart: true command: > sh -c 'i=0; while [ $$i -lt 30 ]; do i=$$((i+1)); if nc -z -w 1 db 5432 2>/dev/null; then echo "ATTEMPT $$i: CONNECTED"; exit 0; else echo "ATTEMPT $$i: CONNECTION-REFUSED"; fi; sleep 0.5; done; echo "GAVE UP"' db: image: postgres:16-alpine environment: POSTGRES_PASSWORD: example volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U postgres"] interval: 2s timeout: 3s retries: 10 start_period: 10s volumes: pgdata: The three edits, one line each. condition: service_healthy makes Compose wait for the healthcheck verdict instead of container start, the gate. restart: true re-runs that gate when you explicitly restart the database, so the app is not left pointing at stale connections after a docker compose restart db. And -h 127.0.0.1 forces the probe onto TCP, where the temporary initialization server cannot answer it, the honest probe. The timing knobs around it matter too, start_period especially: they give a slow first boot room to finish before failures start counting. On a cold volume my database needed about seven seconds, so 10 seconds of grace is the floor I would ship, and CI on shared runners gets more. Keep the application-side retry anyway. The gate protects the startup sequence only. If Postgres restarts two hours later, nothing re-runs the gate for you, a limit I pushed on in the exit-0 OOM post from the other angle: a green healthcheck reports its own probe, not your app's reality. How do you review agent YAML in 60 seconds? You do not need to run the stack to catch this class of bug. Read the diff against the current spec, the same way you would review a human's PR. Check the dependency wiring first. A bare depends_on: - db list means no waiting; it needs the long form with condition: service_healthy to gate startup. Check the probe next, because this is the part everyone trusts. pg_isready with no -h flag is a socket probe; on the Postgres image that can pass during initialization. It needs -h 127.0.0.1 (or the service name, if you want the probe to mirror app traffic) to test the path your app actually uses. Check the timing budget after that: no start_period means a slow first boot spends its failure budget before the database had a chance. CI runners are slower than your laptop, so give the budget headroom there. And check what happens after startup: no restart: true on the dependency means a deliberate database restart leaves the app on stale connections. Then fix the input, not just the output. The prompt that produces current YAML is a prompt with current references in it. Paste the startup-order doc and the services reference links into the request, and say "gate the app on service_healthy with a TCP probe" instead of "write me a compose file with Postgres". The model still carries the old corpus, and it will still try the 2021 shape when you let it guess. But it does not argue with a pasted spec. The measured version of this instinct is the ReCode result: outdated API knowledge persists "even with access to current documentation" (arXiv:2506.20495). The documentation you paste is doing real work, and the review pass you run after is doing the rest. One more habit that pays for itself: cold-boot the result before you merge it. On a scratch project, docker compose down -v && docker compose up --wait is a 30-second test. It reproduces exactly the run that fails in CI, and --wait turns the healthcheck into the pass/fail signal instead of a grepped log line. FAQ Doesn't restart: unless-stopped already handle this? It hides it. The app crash-loops until the database happens to be ready, then looks healthy. The race is still there; you just stopped being able to see it. Does the long depends_on form without condition wait for readiness? No. Without condition: service_healthy it is equivalent to the short list: ordering only. Is docker compose up --wait enough for CI? Yes, and it is the right tool: it blocks until healthchecked services report healthy. It does not remove the need for the gate on the app service itself, or for an honest probe. Do I still need app-level retries if the gate works? Yes. The gate runs at startup only. A connection that drops an hour later is the application's problem, and the same retry-with-backoff loop you already write for production networks covers it. Where does this leave the agent? The agent is not wrong that the file works. It worked on the machine it had, with the volume it had, and the corpus it was trained on says that file is what a compose file looks like. The spec moved, the tutorials didn't, and the average of all that 2021 YAML is what comes out when you ask for 2026 infrastructure. The three-line diff between the inherited shape and the current one is small, it is mechanical, and it is exactly the kind of review you were going to do anyway on a file nobody typed by hand. What is the oldest line of config an agent has handed you that you only caught because it failed somewhere strange? I am collecting these for a follow-up, and the comments are where I will read them. Verified against Docker Engine 29.1.3 and Docker Compose v5.1.4 on 2026-10-29, with three cold-start runs on fresh volumes; every timestamp and probe result above is from docker inspect or the Postgres log of those runs. Claims about model behavior are cited to the linked papers, and anything I could not reproduce myself is not in this post. Sources Every claim above traces to these primary sources. Where I could not verify something hands-on, the post says so. - Hero artwork: a robotic arm stamping green checkmarks on a conveyor belt of identical gene - Diagram artwork: dated tutorial books flow into a glowing model core that outputs a compose file card - Docker docs - docker-library/postgres issue #146 - arXiv:2506.20495 - arXiv:2511.21022 - compose-file services reference - docker-library postgres entrypoint - GitHub About the author Kunal Das writes operations engineering notes as he learns them, always against primary sources. Contact: GitHub and dev.to. Follow the Docker tag for the next posts in this series.

    1 reaction
  6. Seems like this situation is about to change. Thank you Ruby core team for your hard work!!! official-images#22616

    31 likes · 2,035 views
  7. 【応急処置】PHP 7.4 のまま CVE-2026-109618 / 109619(RCE)を塞ぐ Docker イメージを公開しました はじめに 2026-10-21、PHP プロジェクトが 認証なしでリモートコード実行(RCE)が可能な脆弱性を 2 件 公表しました(php.net のアナウンス)。 - CVE-2026-109618(GHSA-w8hw-w9fx-j79w): メモリ不足(OOM)の処理が、内部のリクエストデータの初期化途中で PHP を中断させ、壊れたデータがクリーンアップ時に正しいデータとして扱われる - CVE-2026-109619(GHSA-6wmf-4rv5-p3rp): 解放済みのメモリを指すリクエスト用の内部状態が残り、同じワーカーが処理する次のリクエストでそれが使われる 影響を受けるのは、Web リクエストを処理するすべての PHP(PHP-FPM、Apache mod_php、php-cgi など)です。advisory は mod_php のリスクが最も高い としており、アプリケーションのコードが動く前に攻撃が成立する実証コードもあるとしています。 修正版が出たのは PHP 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 だけです。PHP 7.4 は EOL なので修正版は出ません。ただ、深刻度を考慮して、PHP セキュリティチームが 7.4 / 8.0 / 8.1 向けに 最小限のソース backport を公開しています。 一方で、Docker の公式イメージ php:7.4-* が再ビルドされることはありません。FROM php:7.4-apache のまま運用しているシステムは、何もしなければ脆弱なまま です。 そこで、この backport を当てて再ビルドした php:7.4-* 互換のイメージを OSS として公開しました。 GitHub: softjapan/php74-cve-patched softjapan/php74-cve-patched :::note alert このイメージは「応急処置」です。PHP 7.4 を安全なバージョンにするものではありません。 - 直るのは CVE-2026-109618 と CVE-2026-109619 の 2 件だけ です。7.4.33 より後に見つかったほかの問題は直っていません。 - ベース OS の Debian 11 (bullseye) は 2026-08 に LTS が終わっています。OpenSSL や Apache などの OS パッケージは今後更新されません。 - PHP プロジェクトや Docker 社の公式イメージではありません。 恒久的な対策は PHP 8.2 以降への移行 です。このイメージは、移行までの時間を稼ぐために使ってください。 ::: TL;DR - FROM php:7.4-apache + FROM ghcr.io/softjapan/php74-cve-patched:7.4-apache 1. FROM を 1 行差し替える(apache / fpm / cli、amd64 / arm64) 2. イメージを再ビルド・再デプロイし、PHP のワーカーをすべて再起動する 3. check-patched.sh で修正が入ったことを確認する 4. PHP 8.2 以降への移行計画を立てる(ここまでやって対応完了です) まず、自分の環境が脆弱か確認する 困ったことに、backport を当てても php -v は 7.4.33 のまま です。バージョン番号を見ても、対策済みかどうかは分かりません。 そこで、PHP のバイナリの中身を調べて判定するスクリプトを用意しました。 curl -fsSLO https://raw.githubusercontent.com/softjapan/php74-cve-patched/main/scripts/check-patched.sh sh check-patched.sh php:7.4-apache # イメージ sh check-patched.sh --container my-web-1 # 起動中のコンテナ sh check-patched.sh --local # このマシン(VM や物理サーバー) 公式の php:7.4-apache を調べた結果です。 Image: php:7.4-apache PHP version: 7.4.33 VULNERABLE /usr/lib/apache2/modules/libphp7.so VULNERABLE /usr/local/bin/php RESULT: VULNERABLE to CVE-2026-109618 / CVE-2026-109619 パッチを当てたイメージではこうなります。 Image: ghcr.io/softjapan/php74-cve-patched:7.4-apache PHP version: 7.4.33 patched /usr/lib/apache2/modules/libphp7.so patched /usr/local/bin/php RESULT: PATCHED (backport found in every PHP binary) - 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 以降は修正済みと判定します。それより古いバージョンは、backport で追加される関数 zend_new_array_singleton がバイナリにあるかどうかで判定します。 - CLI だけでなく、実際にリクエストを処理する libphp7.so(mod_php)や php-fpm も個別に調べます。CLI だけ直っていて Apache が古いまま、という状態も見つけられます。 - 終了コードは 0 が修正済み、1 が脆弱です。CI に組み込んで、脆弱なイメージをデプロイしないようにもできます。 :::note info この判定は簡易的なものです。ディストリビューションや LTS ベンダーが独自の方法で backport したパッケージは、「脆弱」と判定されることがあります。その場合はベンダーの情報を確認してください。 ::: 使い方 同じイメージを GHCR と Docker Hub の両方で公開しています。タグもダイジェストも同じです。 | 公式イメージ | GHCR | Docker Hub | |---|---|---| | php:7.4-apache | ghcr.io/softjapan/php74-cve-patched:7.4-apache | softjpn/php74-cve-patched:7.4-apache | | php:7.4-fpm | ghcr.io/softjapan/php74-cve-patched:7.4-fpm | softjpn/php74-cve-patched:7.4-fpm | | php:7.4-cli | ghcr.io/softjapan/php74-cve-patched:7.4-cli | softjpn/php74-cve-patched:7.4-cli | 公式イメージと同じ使い方ができることを重視しています。 - docker-php-ext-install / docker-php-ext-configure / pecl / php.ini-production がそのまま使えます - php -m の拡張モジュール一覧が公式イメージと同じであることを CI で確認しています - apt-get install も使えます(apt の参照先を archive.debian.org に変えてあります) Laravel 向けの例です。 FROM ghcr.io/softjapan/php74-cve-patched:7.4-apache RUN apt-get update && apt-get install -y --no-install-recommends \ libzip-dev libpng-dev libjpeg-dev libfreetype6-dev \ && rm -rf /var/lib/apt/lists/* \ && docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j"$(nproc)" gd pdo_mysql zip bcmath opcache \ && a2enmod rewrite :::note alert イメージを差し替えただけでは終わりません。 実行中のコンテナやワーカーは古いバイナリのまま動いています。再ビルド・再デプロイして、PHP のワーカーをすべて再起動してください。最後に check-patched.sh --container <名前> で、動いているコンテナ が修正済みかを確認してください。 ::: 何を変えたのか 変更は最小限にしています。docker-library/php に残っている 最後の PHP 7.4 用 Dockerfile(commit 1361ba95) を元に、変えたのは次の 3 点だけです。 1. apt の参照先を archive.debian.org に変更: bullseye は通常のミラーから消えたため、apt-get update が 404 になります 2. PHP 7.4.33 のソースに backport を適用: patch -p1 --fuzz=0 で当てるので、少しでもずれていればビルドが失敗します 3. 修正が入ったことをビルド中に確認: php、php-fpm、libphp7.so のどれか 1 つでも修正が入っていなければ、ビルドを失敗させます 当てているパッチは、上流の php/php-src@712e60f と バイト単位で同一 です。CI が push のたびと毎週、次の点を確認しています。 - 同梱のパッチが上流の commit と同一であること - 上流の backport ブランチに新しい commit が入っていないこと(入っていれば CI が失敗して知らせます) 当初は社内で php:7.4-apache の上から ./configure をやり直す方法をとっていました。しかしこの方法には、configure のオプションが x86_64 向けに固定される、fpm / cli 版でオプションがずれる、といった問題がありました。公開版では公式の Dockerfile そのものを元にして、マルチアーキテクチャへの対応と公式との互換性を両立しています。 信頼して使うために セキュリティ目的のイメージを、知らない人が配っているものから取ってくるのは不安だと思います。そのため、検証できる材料をそろえました。 - イメージはリポジトリの GitHub Actions だけでビルドし、SBOM と provenance を付けています - cosign(keyless)で署名しています cosign verify ghcr.io/softjapan/php74-cve-patched:7.4-apache \ --certificate-identity-regexp '^https://github.com/softjapan/php74-cve-patched/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com - 本番環境ではダイジェスト(@sha256:...)で固定してください - このイメージを使わず、自分でビルドする手順 も公開しています。Docker を使わずソースからビルドしている環境向けに、patch -p1 で当てる手順も載せています イメージをすぐ差し替えられない場合の緩和策 advisory には、すぐに更新できない場合の緩和策も載っています。どれも攻撃の経路を一部ふさぐだけで、安全になるわけではない と明記されていますが、差し替えまでのつなぎや多層防御として使えます。 CVE-2026-109618(OOM による状態破損) - memory_limit=-1 を、アプリケーションが上書きできない方法(FPM プールや Apache の php_admin_value)で設定する。どこかの設定で有限の値に戻されていないかも確認する - その代わり、コンテナや cgroup のメモリ制限で、影響を受けたワーカーだけが止まるようにしておく - RLIMIT_AS / RLIMIT_DATA での代用はしない(脆弱な経路を逆に通る原因になる) - post_max_size を小さくする。アップロードがないなら 8M、あるなら 32M。upload_max_filesize は post_max_size より小さくする - Web サーバーやプロキシでも、リクエストサイズ、フィールド数、マルチパートの数、ヘッダー数などを制限する。Content-Length がないリクエストも対象に含める CVE-2026-109619(古いグローバル状態) - session.upload_progress.enabled=0 を設定し、ワーカーをすべて再起動する - php.ini、FPM プール、サーバー設定など、リクエストの解析前に効く場所で設定する。ini_set() や .user.ini では効果がない - 初期化前に警告を出すような、無効な設定や非推奨の設定をリクエストごとに適用しているなら取り除く(@ や display_errors で表示を隠しても意味はない) - 攻撃者が起こせる fatal error やタイムアウトを修正する このイメージには、上記の一部(session.upload_progress.enabled = 0 と、小さめの post_max_size / upload_max_filesize)をまとめた設定ファイルを同梱しています。デフォルトでは無効 です。PHP の挙動は公式イメージと変えていないので、使う場合は次の 1 行を追加してください。 RUN cp /usr/local/share/php74-cve-patched/zz-cve-2026-mitigations.ini "$PHP_INI_DIR/conf.d/" 応急処置のあとにやること 最後にもう一度書いておきます。このイメージは止血であって、治療ではありません。 - [ ] すべての PHP 7.4 環境を洗い出し、check-patched.sh で確認した - [ ] イメージを差し替え、再デプロイし、ワーカーを再起動した - [ ] 動いているコンテナ で check-patched.sh --container が PATCHED になった - [ ] 必要に応じて緩和策の設定も入れた - [ ] PHP 8.2 以降への移行計画と期限を決めた(8.2 も 2026-12-31 に EOL になるので、8.3 以降も検討する) - [ ] OS(Debian 11)も更新されないことを、関係者と共有した まとめ - CVE-2026-109618 / 109619 は、認証なしの RCE です。特に mod_php は最優先で対応してください - PHP 7.4 の公式 Docker イメージは今後も直りません。php:7.4-* を使っていれば脆弱なままです - php74-cve-patched は、FROM を 1 行変えるだけで公式の backport を取り込める 応急処置 です - 恒久的な対策は PHP 8.2 以降への移行です PHP 8.0 / 8.1 版(こちらも公式の backport があります)や Alpine 版も検討しています。必要な方は Issue で教えてください。この記事が役に立ったら、Qiita の LGTM や GitHub の ⭐ をもらえるとうれしいです。同じように困っている人に見つけてもらいやすくなります。 softjapan/php74-cve-patched 参考 - PHP: News Archive 2026(2026-10-21 Security advisory) - GHSA-w8hw-w9fx-j79w(CVE-2026-109618) - GHSA-6wmf-4rv5-p3rp(CVE-2026-109619) - php-src: PHP-7.4-CVE-2026-109618-109619 ブランチ

  8. 【応急処置】PHP 7.4 のまま CVE-2026-109618 / 109619(RCE)を塞ぐ Docker イメージを公開しました はじめに 2026-10-21、PHP プロジェクトが 認証なしでリモートコード実行(RCE)が可能な脆弱性を 2 件 公表しました(php.net のアナウンス)。 CVE-2026-109618(GHSA-w8hw-w9fx-j79w): メモリ不足(OOM)の処理が、内部のリクエストデータの初期化途中で PHP を中断させ、壊れたデータがクリーンアップ時に正しいデータとして扱われる CVE-2026-109619(GHSA-6wmf-4rv5-p3rp): 解放済みのメモリを指すリクエスト用の内部状態が残り、同じワーカーが処理する次のリクエストでそれが使われる 影響を受けるのは、Web リクエストを処理するすべての PHP(PHP-FPM、Apache mod_php、php-cgi など)です。advisory は mod_php のリスクが最も高い としており、アプリケーションのコードが動く前に攻撃が成立する実証コードもあるとしています。 修正版が出たのは PHP 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 だけです。PHP 7.4 は EOL なので修正版は出ません。ただ、深刻度を考慮して、PHP セキュリティチームが 7.4 / 8.0 / 8.1 向けに 最小限のソース backport を公開しています。 一方で、Docker の公式イメージ php:7.4-* が再ビルドされることはありません。FROM php:7.4-apache のまま運用しているシステムは、何もしなければ脆弱なまま です。 そこで、この backport を当てて再ビルドした php:7.4-* 互換のイメージを OSS として公開しました。 softjapan/php74-cve-patched ! このイメージは「応急処置」です。PHP 7.4 を安全なバージョンにするものではありません。 直るのは CVE-2026-109618 と CVE-2026-109619 の 2 件だけ です。7.4.33 より後に見つかったほかの問題は直っていません。 ベース OS の Debian 11 (bullseye) は 2026-08 に LTS が終わっています。OpenSSL や Apache などの OS パッケージは今後更新されません。 PHP プロジェクトや Docker 社の公式イメージではありません。 恒久的な対策は PHP 8.2 以降への移行 です。このイメージは、移行までの時間を稼ぐために使ってください。 TL;DR - FROM php:7.4-apache - FROM ghcr.io/softjapan/php74-cve-patched:7.4-apache FROM を 1 行差し替える(apache / fpm / cli、amd64 / arm64) イメージを再ビルド・再デプロイし、PHP のワーカーをすべて再起動する check-patched.sh で修正が入ったことを確認する PHP 8.2 以降への移行計画を立てる(ここまでやって対応完了です) まず、自分の環境が脆弱か確認する 困ったことに、backport を当てても php -v は 7.4.33 のまま です。バージョン番号を見ても、対策済みかどうかは分かりません。 そこで、PHP のバイナリの中身を調べて判定するスクリプトを用意しました。 curl -fsSLO raw.githubusercontent.com sh check-patched.sh php:7.4-apache # イメージ sh check-patched.sh --container my-web-1 # 起動中のコンテナ sh check-patched.sh --local # このマシン(VM や物理サーバー) 公式の php:7.4-apache を調べた結果です。 Image: php:7.4-apache PHP version: 7.4.33 VULNERABLE /usr/lib/apache2/modules/libphp7.so VULNERABLE /usr/local/bin/php RESULT: VULNERABLE to CVE-2026-109618 / CVE-2026-109619 パッチを当てたイメージではこうなります。 Image: ghcr.io/softjapan/php74-cve-patched:7.4-apache PHP version: 7.4.33 patched /usr/lib/apache2/modules/libphp7.so patched /usr/local/bin/php RESULT: PATCHED (backport found in every PHP binary) 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 以降は修正済みと判定します。それより古いバージョンは、backport で追加される関数 zend_new_array_singleton がバイナリにあるかどうかで判定します。 CLI だけでなく、実際にリクエストを処理する libphp7.so(mod_php)や php-fpm も個別に調べます。CLI だけ直っていて Apache が古いまま、という状態も見つけられます。 終了コードは 0 が修正済み、1 が脆弱です。CI に組み込んで、脆弱なイメージをデプロイしないようにもできます。 ! この判定は簡易的なものです。ディストリビューションや LTS ベンダーが独自の方法で backport したパッケージは、「脆弱」と判定されることがあります。その場合はベンダーの情報を確認してください。 使い方 同じイメージを GHCR と Docker Hub の両方で公開しています。タグもダイジェストも同じです。 公式イメージ GHCR Docker Hub php:7.4-apache ghcr.io/softjapan/php74-cve-patched:7.4-apache softjpn/php74-cve-patched:7.4-apache php:7.4-fpm ghcr.io/softjapan/php74-cve-patched:7.4-fpm softjpn/php74-cve-patched:7.4-fpm php:7.4-cli ghcr.io/softjapan/php74-cve-patched:7.4-cli softjpn/php74-cve-patched:7.4-cli 公式イメージと同じ使い方ができることを重視しています。 docker-php-ext-install / docker-php-ext-configure / pecl / php.ini-production がそのまま使えます php -m の拡張モジュール一覧が公式イメージと同じであることを CI で確認しています apt-get install も使えます(apt の参照先を archive.debian.org に変えてあります) Laravel 向けの例です。 FROM ghcr.io/softjapan/php74-cve-patched:7.4-apache RUN apt-get update && apt-get install -y --no-install-recommends \ libzip-dev libpng-dev libjpeg-dev libfreetype6-dev \ && rm -rf /var/lib/apt/lists/* \ && docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j"$(nproc)" gd pdo_mysql zip bcmath opcache \ && a2enmod rewrite ! イメージを差し替えただけでは終わりません。 実行中のコンテナやワーカーは古いバイナリのまま動いています。再ビルド・再デプロイして、PHP のワーカーをすべて再起動してください。最後に check-patched.sh --container <名前> で、動いているコンテナ が修正済みかを確認してください。 何を変えたのか 変更は最小限にしています。docker-library/php に残っている 最後の PHP 7.4 用 Dockerfile(commit 1361ba95) を元に、変えたのは次の 3 点だけです。 apt の参照先を archive.debian.org に変更: bullseye は通常のミラーから消えたため、apt-get update が 404 になります PHP 7.4.33 のソースに backport を適用: patch -p1 --fuzz=0 で当てるので、少しでもずれていればビルドが失敗します 修正が入ったことをビルド中に確認: php、php-fpm、libphp7.so のどれか 1 つでも修正が入っていなければ、ビルドを失敗させます 当てているパッチは、上流の php/php-src@712e60f と バイト単位で同一 です。CI が push のたびと毎週、次の点を確認しています。 同梱のパッチが上流の commit と同一であること 上流の backport ブランチに新しい commit が入っていないこと(入っていれば CI が失敗して知らせます) 当初は社内で php:7.4-apache の上から ./configure をやり直す方法をとっていました。しかしこの方法には、configure のオプションが x86_64 向けに固定される、fpm / cli 版でオプションがずれる、といった問題がありました。公開版では公式の Dockerfile そのものを元にして、マルチアーキテクチャへの対応と公式との互換性を両立しています。 信頼して使うために セキュリティ目的のイメージを、知らない人が配っているものから取ってくるのは不安だと思います。そのため、検証できる材料をそろえました。 イメージはリポジトリの GitHub Actions だけでビルドし、SBOM と provenance を付けています cosign(keyless)で署名しています cosign verify ghcr.io/softjapan/php74-cve-patched:7.4-apache \ --certificate-identity-regexp '^softjapan/php74-cve-patched' \ --certificate-oidc-issuer token.actions.githubusercontent.com 本番環境ではダイジェスト(@sha256:...)で固定してください このイメージを使わず、自分でビルドする手順 も公開しています。Docker を使わずソースからビルドしている環境向けに、patch -p1 で当てる手順も載せています イメージをすぐ差し替えられない場合の緩和策 advisory には、すぐに更新できない場合の緩和策も載っています。どれも攻撃の経路を一部ふさぐだけで、安全になるわけではない と明記されていますが、差し替えまでのつなぎや多層防御として使えます。 CVE-2026-109618(OOM による状態破損) memory_limit=-1 を、アプリケーションが上書きできない方法(FPM プールや Apache の php_admin_value)で設定する。どこかの設定で有限の値に戻されていないかも確認する その代わり、コンテナや cgroup のメモリ制限で、影響を受けたワーカーだけが止まるようにしておく RLIMIT_AS / RLIMIT_DATA での代用はしない(脆弱な経路を逆に通る原因になる) post_max_size を小さくする。アップロードがないなら 8M、あるなら 32M。upload_max_filesize は post_max_size より小さくする Web サーバーやプロキシでも、リクエストサイズ、フィールド数、マルチパートの数、ヘッダー数などを制限する。Content-Length がないリクエストも対象に含める CVE-2026-109619(古いグローバル状態) session.upload_progress.enabled=0 を設定し、ワーカーをすべて再起動する php.ini、FPM プール、サーバー設定など、リクエストの解析前に効く場所で設定する。ini_set() や .user.ini では効果がない 初期化前に警告を出すような、無効な設定や非推奨の設定をリクエストごとに適用しているなら取り除く(@ や display_errors で表示を隠しても意味はない) 攻撃者が起こせる fatal error やタイムアウトを修正する このイメージには、上記の一部(session.upload_progress.enabled = 0 と、小さめの post_max_size / upload_max_filesize)をまとめた設定ファイルを同梱しています。デフォルトでは無効 です。PHP の挙動は公式イメージと変えていないので、使う場合は次の 1 行を追加してください。 RUN cp /usr/local/share/php74-cve-patched/zz-cve-2026-mitigations.ini "$PHP_INI_DIR/conf.d/" 応急処置のあとにやること 最後にもう一度書いておきます。このイメージは止血であって、治療ではありません。 すべての PHP 7.4 環境を洗い出し、check-patched.sh で確認した イメージを差し替え、再デプロイし、ワーカーを再起動した 動いているコンテナ で check-patched.sh --container が PATCHED になった 必要に応じて緩和策の設定も入れた PHP 8.2 以降への移行計画と期限を決めた(8.2 も 2026-12-31 に EOL になるので、8.3 以降も検討する) OS(Debian 11)も更新されないことを、関係者と共有した まとめ CVE-2026-109618 / 109619 は、認証なしの RCE です。特に mod_php は最優先で対応してください PHP 7.4 の公式 Docker イメージは今後も直りません。php:7.4-* を使っていれば脆弱なままです php74-cve-patched は、FROM を 1 行変えるだけで公式の backport を取り込める 応急処置 です 恒久的な対策は PHP 8.2 以降への移行です PHP 8.0 / 8.1 版(こちらも公式の backport があります)や Alpine 版も検討しています。必要な方は Issue で教えてください。役に立ったら GitHub で ⭐ をもらえるとうれしいです。同じように困っている人に見つけてもらいやすくなります。 softjapan/php74-cve-patched 参考 PHP: News Archive 2026(2026-10-21 Security advisory) GHSA-w8hw-w9fx-j79w(CVE-2026-109618) GHSA-6wmf-4rv5-p3rp(CVE-2026-109619) php-src: PHP-7.4-CVE-2026-109618-109619 ブランチ

  9. :obama_phew: ``` # php -v PHP 8.5.12 (cli) (built: Oct 21 2026 15:49:40) (NTS) Copyright (c) The PHP Group Built by docker-library/php Zend Engine v4.5.12, Copyright (c) Zend Technologies with Zend OPcache v8.5.12, Copyright (c), by Zend Technologies ```

    1 like
  10. Understood, thanks for the reply. I would again suggest that maybe CPython should release versions of 3.11 and 3.12 that either do not bundle pip, bundle a newer version of pip, or bundle a fixed minor release of pip created by the CPython team. I think it's only two versions that need to change. Given that uv seems to have fixed this in their builds, Debian has of course fixed it with their patches to use the system trust store, and the Docker image folks seem amenable to potentially copying the debian approach, I can't say this absolutely critical, but it does seem like it would be a better default than shipping a version of pip that is trivially vulnerable to MITM. Just for fun, I vibed up the intercepting server for about $0.30 in spend on a Chinese model. I wrote an example on this post: python#1142

    2 likes · 1 comment

Achievements

Awards earned by the repositories docker-library owns. Select an award to embed its badge.

No awards yet

Repositories earn awards by reaching the top three in a completed trending day or week.

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.