Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .devcontainer/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

FROM composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee AS composer

FROM php:8.4.27-cli-bookworm@sha256:496b5e02fe9fe44fcfb715023b931048111dc4e0bc719789d83c906be9f04b05 AS brotli-build

COPY --from=composer /usr/bin/composer /usr/local/bin/composer
COPY .devcontainer/docker/verify-php-trust.php /usr/local/share/sample-app/verify-php-trust.php
COPY docker/brotli /usr/src/brotli-inputs
RUN php /usr/local/share/sample-app/verify-php-trust.php /usr/local/bin/composer \
&& sh /usr/src/brotli-inputs/build.sh

FROM docker:27.5.1-cli@sha256:851f91d241214e7c6db86513b270d58776379aacc5eb9c4a87e5b47115e3065c AS docker-cli

FROM rust:1.86.0-slim-bookworm@sha256:57d415bbd61ce11e2d5f73de068103c7bd9f3188dc132c97cef4a8f62989e944 AS rust
Expand All @@ -16,7 +24,7 @@

FROM mariadb:11.4@sha256:250e1609e93c0e6aecf64bfbab2d84d5e89d2481540fe77447ff6c855c3b1873 AS mysql-seed

ENV MYSQL_ROOT_PASSWORD=password \

Check warning on line 27 in .devcontainer/docker/Dockerfile

View workflow job for this annotation

GitHub Actions / Validate candidate image

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "MYSQL_PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 27 in .devcontainer/docker/Dockerfile

View workflow job for this annotation

GitHub Actions / Validate candidate image

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "MYSQL_ROOT_PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
MYSQL_ROOT_HOST=% \
MYSQL_DATABASE=sample \
MYSQL_USER=laravel \
Expand Down Expand Up @@ -86,6 +94,12 @@

FROM php:8.4.27-cli-bookworm@sha256:496b5e02fe9fe44fcfb715023b931048111dc4e0bc719789d83c906be9f04b05

COPY --from=brotli-build /out /usr/local/share/sample-app/brotli
RUN dpkg -i /usr/local/share/sample-app/brotli/libbrotli1_*.deb \
/usr/local/share/sample-app/brotli/libbrotli-dev_*.deb \
&& php /usr/local/share/sample-app/brotli/verify-runtime.php \
&& rm /usr/local/share/sample-app/brotli/*.deb

ARG VCS_REF=unknown
ARG DURABLE_WORKFLOW_CLI_VERSION

Expand Down Expand Up @@ -205,6 +219,7 @@
fi; \
done; \
apt-get install -y --no-install-recommends build-essential pkg-config; \
php /usr/local/share/sample-app/brotli/verify-runtime.php; \
groupadd --gid 1000 laravel; \
useradd --create-home --gid laravel --shell /bin/bash --uid 1000 laravel; \
passwd --delete laravel; \
Expand Down Expand Up @@ -368,7 +383,8 @@
/usr/local/bin/wait-for-devcontainer-identity \
&& DEVCONTAINER_DEPENDENCY_SCOPE=baked \
DEVCONTAINER_SSH_HOST_KEY_STATE=absent \
gosu laravel verify-devcontainer-image
gosu laravel verify-devcontainer-image \
&& php /usr/local/share/sample-app/brotli/verify-runtime.php

EXPOSE 22 80 5173

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
docker compose exec -T app node docker/verify-node-trust.mjs
docker compose exec -T app node --test tests/Node/ProductionNodeTrustTest.mjs
docker compose exec -T app php /usr/local/share/sample-app/verify-runtime-trust.php /usr/bin/composer
docker compose exec -T app php /usr/local/share/sample-app/brotli/verify-runtime.php
docker compose exec -T app node docker/playwright-smoke.js
- name: Run embedded workflow samples
run: scripts/compose-smoke.sh
Expand Down
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
FROM composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee AS composer

FROM php:8.4.27-cli-bookworm@sha256:496b5e02fe9fe44fcfb715023b931048111dc4e0bc719789d83c906be9f04b05 AS brotli-build

COPY --from=composer /usr/bin/composer /usr/local/bin/composer
COPY docker/verify-runtime-trust.php /usr/local/share/sample-app/verify-runtime-trust.php
COPY docker/brotli /usr/src/brotli-inputs
RUN php /usr/local/share/sample-app/verify-runtime-trust.php /usr/local/bin/composer \
&& sh /usr/src/brotli-inputs/build.sh

FROM php:8.4.27-cli-bookworm@sha256:496b5e02fe9fe44fcfb715023b931048111dc4e0bc719789d83c906be9f04b05 AS base

COPY --from=brotli-build /out /usr/local/share/sample-app/brotli
RUN dpkg -i /usr/local/share/sample-app/brotli/libbrotli1_*.deb \
/usr/local/share/sample-app/brotli/libbrotli-dev_*.deb \
&& php /usr/local/share/sample-app/brotli/verify-runtime.php \
&& rm /usr/local/share/sample-app/brotli/*.deb

COPY --from=composer /usr/bin/composer /usr/bin/composer
COPY docker/install-phpredis.sh /usr/local/bin/install-phpredis
COPY docker/verify-runtime-trust.php docker/verify-node-trust.mjs /usr/local/share/sample-app/
Expand All @@ -11,6 +25,7 @@ RUN php /usr/local/share/sample-app/verify-runtime-trust.php "$(command -v compo
curl ffmpeg git libnspr4 libnss3 libpq-dev libzip-dev unzip \
&& docker-php-ext-install pdo pdo_mysql pcntl zip bcmath \
&& sh /usr/local/bin/install-phpredis \
&& php /usr/local/share/sample-app/brotli/verify-runtime.php \
&& php /usr/local/share/sample-app/verify-runtime-trust.php "$(command -v composer)" \
&& rm -rf /var/lib/apt/lists/*

Expand Down Expand Up @@ -67,6 +82,7 @@ RUN node /usr/local/share/sample-app/verify-node-trust.mjs \
&& npx playwright install --with-deps chromium \
&& node docker/playwright-smoke.js \
&& node /usr/local/share/sample-app/verify-node-trust.mjs \
&& php /usr/local/share/sample-app/brotli/verify-runtime.php \
&& php /usr/local/share/sample-app/verify-runtime-trust.php "$(command -v composer)" \
&& (cp .env.example .env 2>/dev/null || echo "APP_KEY=" > .env) \
&& php artisan key:generate --force \
Expand Down
233 changes: 233 additions & 0 deletions docker/brotli/CVE-2026-111497.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
From 50a9b70a4d5e1443fc40927952276b68fffa83a0 Mon Sep 17 00:00:00 2001
From: Evgenii Kliuchnikov <eustas.ru@gmail.com>
Date: Wed, 21 Oct 2026 11:25:30 +0000
Subject: [PATCH] Detect and report invalid stream earlier.

PiperOrigin-RevId: 876198329
(cherry picked from commit 0ed4268a4401abcb4472096340477cf4af7d1a2b
---
c/dec/decode.c | 87 +++++++++++++++++++++++++--------------
c/include/brotli/decode.h | 3 +-
2 files changed, 59 insertions(+), 31 deletions(-)

diff --git a/c/dec/decode.c b/c/dec/decode.c
index ae5a3d3fa..b23d9fb22 100644
--- a/c/dec/decode.c
+++ b/c/dec/decode.c
@@ -1117,7 +1117,7 @@ static BrotliDecoderErrorCode DecodeContextMap(uint32_t context_map_size,

/* Decodes a command or literal and updates block type ring-buffer.
Reads 3..54 bits. */
-static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength(
+static BROTLI_INLINE BrotliDecoderErrorCode DecodeBlockTypeAndLength(
int safe, BrotliDecoderState* s, int tree_type) {
uint32_t max_block_type = s->num_block_types[tree_type];
const HuffmanCode* type_tree = &s->block_type_trees[
@@ -1128,7 +1128,7 @@ static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength(
uint32_t* ringbuffer = &s->block_type_rb[tree_type * 2];
uint32_t block_type;
if (max_block_type <= 1) {
- return BROTLI_FALSE;
+ return BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH;
}

/* Read 0..15 + 3..39 bits. */
@@ -1138,11 +1138,13 @@ static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength(
} else {
BrotliBitReaderState memento;
BrotliBitReaderSaveState(br, &memento);
- if (!SafeReadSymbol(type_tree, br, &block_type)) return BROTLI_FALSE;
+ if (!SafeReadSymbol(type_tree, br, &block_type)) {
+ return BROTLI_DECODER_NEEDS_MORE_INPUT;
+ }
if (!SafeReadBlockLength(s, &s->block_length[tree_type], len_tree, br)) {
s->substate_read_block_length = BROTLI_STATE_READ_BLOCK_LENGTH_NONE;
BrotliBitReaderRestoreState(br, &memento);
- return BROTLI_FALSE;
+ return BROTLI_DECODER_NEEDS_MORE_INPUT;
}
}

@@ -1158,7 +1160,7 @@ static BROTLI_INLINE BROTLI_BOOL DecodeBlockTypeAndLength(
}
ringbuffer[0] = ringbuffer[1];
ringbuffer[1] = block_type;
- return BROTLI_TRUE;
+ return BROTLI_DECODER_SUCCESS;
}

static BROTLI_INLINE void DetectTrivialLiteralBlockTypes(
@@ -1194,62 +1196,68 @@ static BROTLI_INLINE void PrepareLiteralDecoding(BrotliDecoderState* s) {

/* Decodes the block type and updates the state for literal context.
Reads 3..54 bits. */
-static BROTLI_INLINE BROTLI_BOOL DecodeLiteralBlockSwitchInternal(
+static BROTLI_INLINE BrotliDecoderErrorCode DecodeLiteralBlockSwitchInternal(
int safe, BrotliDecoderState* s) {
- if (!DecodeBlockTypeAndLength(safe, s, 0)) {
- return BROTLI_FALSE;
+ BrotliDecoderErrorCode result = DecodeBlockTypeAndLength(safe, s, 0);
+ if (result != BROTLI_DECODER_SUCCESS) {
+ return result;
}
PrepareLiteralDecoding(s);
- return BROTLI_TRUE;
+ return BROTLI_DECODER_SUCCESS;
}

-static void BROTLI_NOINLINE DecodeLiteralBlockSwitch(BrotliDecoderState* s) {
- DecodeLiteralBlockSwitchInternal(0, s);
+static BROTLI_NOINLINE BrotliDecoderErrorCode
+DecodeLiteralBlockSwitch(BrotliDecoderState* s) {
+ return DecodeLiteralBlockSwitchInternal(0, s);
}

-static BROTLI_BOOL BROTLI_NOINLINE SafeDecodeLiteralBlockSwitch(
+static BROTLI_NOINLINE BrotliDecoderErrorCode SafeDecodeLiteralBlockSwitch(
BrotliDecoderState* s) {
return DecodeLiteralBlockSwitchInternal(1, s);
}

/* Block switch for insert/copy length.
Reads 3..54 bits. */
-static BROTLI_INLINE BROTLI_BOOL DecodeCommandBlockSwitchInternal(
+static BROTLI_INLINE BrotliDecoderErrorCode DecodeCommandBlockSwitchInternal(
int safe, BrotliDecoderState* s) {
- if (!DecodeBlockTypeAndLength(safe, s, 1)) {
- return BROTLI_FALSE;
+ BrotliDecoderErrorCode result = DecodeBlockTypeAndLength(safe, s, 1);
+ if (result != BROTLI_DECODER_SUCCESS) {
+ return result;
}
s->htree_command = s->insert_copy_hgroup.htrees[s->block_type_rb[3]];
- return BROTLI_TRUE;
+ return BROTLI_DECODER_SUCCESS;
}

-static void BROTLI_NOINLINE DecodeCommandBlockSwitch(BrotliDecoderState* s) {
- DecodeCommandBlockSwitchInternal(0, s);
+static BROTLI_NOINLINE BrotliDecoderErrorCode
+DecodeCommandBlockSwitch(BrotliDecoderState* s) {
+ return DecodeCommandBlockSwitchInternal(0, s);
}

-static BROTLI_BOOL BROTLI_NOINLINE SafeDecodeCommandBlockSwitch(
+static BROTLI_NOINLINE BrotliDecoderErrorCode SafeDecodeCommandBlockSwitch(
BrotliDecoderState* s) {
return DecodeCommandBlockSwitchInternal(1, s);
}

/* Block switch for distance codes.
Reads 3..54 bits. */
-static BROTLI_INLINE BROTLI_BOOL DecodeDistanceBlockSwitchInternal(
+static BROTLI_INLINE BrotliDecoderErrorCode DecodeDistanceBlockSwitchInternal(
int safe, BrotliDecoderState* s) {
- if (!DecodeBlockTypeAndLength(safe, s, 2)) {
- return BROTLI_FALSE;
+ BrotliDecoderErrorCode result = DecodeBlockTypeAndLength(safe, s, 2);
+ if (result != BROTLI_DECODER_SUCCESS) {
+ return result;
}
s->dist_context_map_slice = s->dist_context_map +
(s->block_type_rb[5] << BROTLI_DISTANCE_CONTEXT_BITS);
s->dist_htree_index = s->dist_context_map_slice[s->distance_context];
- return BROTLI_TRUE;
+ return BROTLI_DECODER_SUCCESS;
}

-static void BROTLI_NOINLINE DecodeDistanceBlockSwitch(BrotliDecoderState* s) {
- DecodeDistanceBlockSwitchInternal(0, s);
+static BROTLI_NOINLINE BrotliDecoderErrorCode
+DecodeDistanceBlockSwitch(BrotliDecoderState* s) {
+ return DecodeDistanceBlockSwitchInternal(0, s);
}

-static BROTLI_BOOL BROTLI_NOINLINE SafeDecodeDistanceBlockSwitch(
+static BROTLI_NOINLINE BrotliDecoderErrorCode SafeDecodeDistanceBlockSwitch(
BrotliDecoderState* s) {
return DecodeDistanceBlockSwitchInternal(1, s);
}
@@ -1719,6 +1727,9 @@ static BROTLI_INLINE BROTLI_BOOL CheckInputAmount(
return BrotliCheckInputAmount(br, num);
}

+/* NB: METHOD should return BROTLI_FALSE only in case there is not enough input;
+ in case of "unsafe" execution, when input is guaranteed to be sufficient,
+ result is ignored. */
#define BROTLI_SAFE(METHOD) \
{ \
if (safe) { \
@@ -1731,6 +1742,22 @@ static BROTLI_INLINE BROTLI_BOOL CheckInputAmount(
} \
}

+/* NB: METHOD should return BROTLI_DECODER_SUCCESS, BROTLI_DECODER_ERROR_*, or
+ BROTLI_DECODER_NEEDS_MORE_INPUT; the later two break the processing. */
+#define BROTLI_SAFE_WITH_STATUS(METHOD) \
+ { \
+ BrotliDecoderErrorCode status; \
+ if (safe) { \
+ status = Safe##METHOD; \
+ } else { \
+ status = METHOD; \
+ } \
+ if (status != BROTLI_DECODER_SUCCESS) { \
+ result = status; \
+ goto saveStateAndReturn; \
+ } \
+ }
+
static BROTLI_INLINE BrotliDecoderErrorCode ProcessCommandsInternal(
int safe, BrotliDecoderState* s) {
int pos = s->pos;
@@ -1769,7 +1796,7 @@ static BROTLI_INLINE BrotliDecoderErrorCode ProcessCommandsInternal(
goto saveStateAndReturn;
}
if (BROTLI_PREDICT_FALSE(s->block_length[1] == 0)) {
- BROTLI_SAFE(DecodeCommandBlockSwitch(s));
+ BROTLI_SAFE_WITH_STATUS(DecodeCommandBlockSwitch(s));
goto CommandBegin;
}
/* Read the insert/copy length in the command. */
@@ -1797,7 +1824,7 @@ static BROTLI_INLINE BrotliDecoderErrorCode ProcessCommandsInternal(
goto saveStateAndReturn;
}
if (BROTLI_PREDICT_FALSE(s->block_length[0] == 0)) {
- BROTLI_SAFE(DecodeLiteralBlockSwitch(s));
+ BROTLI_SAFE_WITH_STATUS(DecodeLiteralBlockSwitch(s));
PreloadSymbol(safe, s->literal_htree, br, &bits, &value);
if (!s->trivial_literal_context) goto CommandInner;
}
@@ -1833,7 +1860,7 @@ static BROTLI_INLINE BrotliDecoderErrorCode ProcessCommandsInternal(
goto saveStateAndReturn;
}
if (BROTLI_PREDICT_FALSE(s->block_length[0] == 0)) {
- BROTLI_SAFE(DecodeLiteralBlockSwitch(s));
+ BROTLI_SAFE_WITH_STATUS(DecodeLiteralBlockSwitch(s));
if (s->trivial_literal_context) goto CommandInner;
}
context = BROTLI_CONTEXT(p1, p2, s->context_lookup);
@@ -1880,7 +1907,7 @@ static BROTLI_INLINE BrotliDecoderErrorCode ProcessCommandsInternal(
} else {
/* Read distance code in the command, unless it was implicitly zero. */
if (BROTLI_PREDICT_FALSE(s->block_length[2] == 0)) {
- BROTLI_SAFE(DecodeDistanceBlockSwitch(s));
+ BROTLI_SAFE_WITH_STATUS(DecodeDistanceBlockSwitch(s));
}
BROTLI_SAFE(ReadDistance(s, br));
}
diff --git a/c/include/brotli/decode.h b/c/include/brotli/decode.h
index 0f5c8f9d1..00e20c407 100644
--- a/c/include/brotli/decode.h
+++ b/c/include/brotli/decode.h
@@ -84,8 +84,9 @@ typedef enum {
BROTLI_ERROR_CODE(_ERROR_FORMAT_, PADDING_1, -14) SEPARATOR \
BROTLI_ERROR_CODE(_ERROR_FORMAT_, PADDING_2, -15) SEPARATOR \
BROTLI_ERROR_CODE(_ERROR_FORMAT_, DISTANCE, -16) SEPARATOR \
+ BROTLI_ERROR_CODE(_ERROR_FORMAT_, BLOCK_SWITCH, -17) SEPARATOR \
\
- /* -17..-18 codes are reserved */ \
+ /* Code -18 is reserved */ \
\
BROTLI_ERROR_CODE(_ERROR_, DICTIONARY_NOT_SET, -19) SEPARATOR \
BROTLI_ERROR_CODE(_ERROR_, INVALID_ARGUMENTS, -20) SEPARATOR \
25 changes: 25 additions & 0 deletions docker/brotli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Bookworm Brotli correction

The pinned PHP base uses Debian's Brotli 1.0.9 package. This directory builds a
same-base Debian backport of Google's
[CVE-2026-111497 fix](https://github.com/google/brotli/commit/50a9b70a4d5e1443fc40927952276b68fffa83a0).
The patched decoder and header must match upstream
[1.0.10](https://github.com/google/brotli/releases/tag/v1.0.10) exactly.

APT authenticates the source index. `source.sha256` binds its three source
artifacts, and the script verifies the vendored patch and both resulting files.
The runtime and development packages keep the honest upstream version 1.0.9
and use revision `1.0.9-2+b6+dw1`. It sorts below the next Debian binary rebuild
and security revision. When Debian publishes its correction, review and replace
this backport with that vendor package.

The build deliberately does not execute upstream test corpora. Verification
checks the added error-name marker and encodes/decodes ordinary valid data.
The final PHP guard checks the exact package, refuses stray decoder copies, and
inspects dynamic consumers in the image's tool, application and browser roots.
Existing Node checks separately verify its embedded decoder's fixed release.
Both final image recipes and their native qualification run these guards.

A scanner that checks only the upstream `1.0.9` string may still report the CVE.
Keep that result alongside the patch hashes, package revision and actual runtime
checks. Do not claim an upstream version upgrade or suppress it without review.
Loading
Loading