
Source-built nginx 1.25.5 container with backported CVE-2026-42945 fix, OpenSSL bump, full provenance chain, and VEX attestation.
nginx:1.25-bookwormA source-built nginx 1.25.5 container image that remediates two CVEs present in
the official nginx:1.25-bookworm image while preserving upstream runtime behavior.
| CVE | Component | Severity | Technique | Verification model |
|---|---|---|---|---|
| CVE-2024-6119 | OpenSSL / libssl3 | High | Dependency version bump | Scanner-verifiable: libssl3 3.0.20 visible in dpkg database |
| CVE-2026-42945 | nginx ngx_http_rewrite_module | Critical | Backported source patch | Provenance-verifiable: patch derivation + regression test + build attestation + VEX |
These represent two distinct remediation models:
The nginx -V configure arguments of this image match those of
nginx:1.25-bookworm (compared character-by-character after normalizing the
-ffile-prefix-map build path; verified in test/compat.py::test_nginx_version).
The test suite (make test) validates 89 assertions covering image metadata,
dynamic modules, filesystem layout, entrypoint behavior, dpkg packaging, and
HTTP request handling against the live upstream image.
Known differences from upstream (not validated as identical):
built with OpenSSL X.X.X line in nginx -V reflects the builder's
libssl-dev version, which may differ from upstream's compile-time OpenSSL.debian:bookworm-slim base rather than the
official image's inherited package tree.libssl3 version is whatever bookworm currently provides (3.0.20
at time of writing), not the pinned version in the upstream image.# Build everything (builder → .deb → final image)
make image
# Run automated compatibility tests
make test
# Run CVE-2026-42945 regression test
make test-cve
# Verify patch provenance (re-derives from upstream tarballs)
make verify-patch
# Scan and demonstrate VEX
make scan
One command to build, test, and scan:
make all
build/
Dockerfile.build Builder image (debian:bookworm-slim + compilation deps)
build.sh Fetch → verify → patch → compile → package nginx
generate-vex.sh Generate OpenVEX document for backported CVE
verify-patch.sh Re-derive patch from upstream tarballs (audit tool)
patches/
CVE-2026-42945.patch Backported one-line fix from nginx 1.30.1
CVE-2026-42945.provenance.json Machine-readable patch provenance and derivation metadata
test/
compat.py 89-assertion compatibility test suite (runs against live upstream)
test_cve_2026_42945.py CVE-specific regression test (exercises vulnerable code path)
artifacts/
patch-attestation.json Build-time patch attestation (tracked)
nginx_*.deb Compiled package (gitignored - rebuilt via `make build-source`)
nginx Compiled binary (gitignored)
Containerfile Final runtime image definition
Makefile Orchestrates build → test → scan pipeline
vex.json Generated OpenVEX v0.2.0 document
baseline-trivy.txt Point-in-time Trivy scan of nginx:1.25-bookworm
baseline-grype.txt Point-in-time Grype scan of nginx:1.25-bookworm
fixed-trivy.txt Trivy scan of the fixed image
fixed-grype.txt Grype scan of the fixed image (without VEX)
fixed-grype-vex.txt Grype scan of the fixed image (with VEX applied)
debian:bookworm-slim (builder)
└─ build.sh
├─ curl nginx-1.25.5.tar.gz (SHA256-verified)
├─ curl njs-0.8.4 from github.com/nginx/njs
├─ patch -p1 < CVE-2026-42945.patch
├─ ./configure (flags identical to upstream nginx -V)
├─ make: release binary, debug binary, 4 dynamic module families (×2 release/debug)
├─ make: NJS modules (×2 release/debug) + njs CLI binary
└─ dpkg-deb → nginx_1.25.5-1~bookworm+echo1_<arch>.deb
debian:bookworm-slim (runtime)
├─ apt-get install runtime deps (libssl3 ≥ 3.0.14 enforced)
├─ dpkg -i nginx_*.deb
└─ COPY --from=upstream /docker-entrypoint.sh + /docker-entrypoint.d/
The Containerfile does NOT copy /etc/nginx from upstream. All configuration
files are shipped inside the .deb and tracked by dpkg's conffile mechanism.
This is required for the 10-listen-on-ipv6-by-default.sh entrypoint script,
which uses dpkg-query to detect whether default.conf has been user-modified.
./configure && make inside a clean Debian container.
No upstream binaries, no apt install nginx.build.sh only fetches pinned source archives.The build is mostly reproducible but not hermetic:
Dockerfile.build but not version-pinned.To improve reproducibility, pin the base image digest:
docker pull debian:bookworm-slim
docker inspect debian:bookworm-slim --format='{{index .RepoDigests 0}}'
# Then use: FROM debian:bookworm-slim@sha256:<digest>
How the fix works:
The .deb package declares Depends: libssl3 (>= 3.0.14), which forces
apt-get install to pull an OpenSSL version that includes the fix. The current
Debian bookworm repositories provide 3.0.20, which fixes CVE-2024-6119 and
dozens of other OpenSSL CVEs from the baseline (CVE-2024-2511, CVE-2024-5535,
CVE-2024-4741, CVE-2023-5678, CVE-2023-6129, CVE-2023-6237, CVE-2024-9143,
CVE-2025-15467, CVE-2025-69420).
Scanner behavior: Grype and Trivy see libssl3 3.0.20 in the dpkg
database and recognise that 3.0.20 ≥ 3.0.14, so CVE-2024-6119 is no longer
reported. No VEX needed - the version bump is self-evident.
Verification:
grep "CVE-2024-6119" baseline-grype.txt # present
grep "CVE-2024-6119" fixed-grype.txt # absent
Vulnerability:
ngx_http_script_regex_end_code() in src/http/ngx_http_script.c failed to
reset e->is_args when processing a rewrite regex result. If is_args was
set by a preceding script engine operation, subsequent buffer length
calculations in the redirect/rewrite path would be incorrect, causing a heap
buffer overflow of attacker-controlled size via crafted request URIs.
The fix (one line):
// Added before the existing e->quote = 0; at line 1205
e->is_args = 0;
Patch provenance:
How the patch was created:
nginx-1.25.5.tar.gz (SHA256 verified against pinned hash)nginx-1.30.1.tar.gz (SHA256 verified: 99765000d974896b31ca5882d8c279ce3fe7ef6f5c6f9f0a967ed7fd3407f9cc)diff src/http/ngx_http_script.c between the two treese->is_args = 0; additionresearch/ for independent verificationRegression testing:
The test suite test/test_cve_2026_42945.py exercises the vulnerable code
path by sending requests through rewrite rules that manipulate query string
state (is_args). It validates:
? (query strip) produce well-formed responsesis_args state don't crash the workerRun: make test-cve
Scanner behavior:
Because the package version remains 1.25.5, scanners will flag CVE-2026-42945
when the advisory is present in their database. This is expected - scanners
match by (package-name, upstream-version) and cannot inspect whether source
has been patched. The VEX document suppresses this finding with machine-readable
justification. The scanner suppression is a communication mechanism, not the
primary evidence of remediation. The primary evidence is the patch provenance
chain and regression test above.
Scanners identify packages by (name, version) from the dpkg database.
Our package is nginx 1.25.5-1~bookworm+echo1 - scanners parse this as
upstream version 1.25.5 and flag all CVEs affecting nginx ≤ 1.25.5.
For backported fixes, the scanner cannot determine that the vulnerable code has been patched. The version intentionally remains 1.25.5 to preserve package compatibility. VEX provides a machine-readable attestation that the CVE has been addressed, linked to verifiable evidence.
The VEX statement for CVE-2026-42945 is NOT mere paperwork. It is backed by:
build/patches/CVE-2026-42945.provenance.json
documents the exact upstream source, derivation method, and file hashesbuild.sh asserts the fix marker is present
in the compiled source, aborting the build on failureartifacts/patch-attestation.json records
pre/post-patch file hashes generated during the buildtest/test_cve_2026_42945.py exercises the vulnerable
code path without crash or corruptionmake verify-patch downloads both upstream
tarballs and demonstrates the committed patch is the minimal security diff| CVE | Status | Justification | Evidence |
|---|---|---|---|
| CVE-2026-42945 | fixed | vulnerable_code_not_present | Backport patch + provenance + regression test + build gate |
The VEX scope is intentionally narrow: only CVEs that this project actively remediated with verifiable evidence are covered. Findings that are merely "expected to be mitigated" by the shipped version are not suppressed - this avoids using VEX as a blanket suppression tool.
The build/generate-vex.sh script produces an OpenVEX v0.2.0 document.
The scanner command to apply it:
# Without VEX - CVE is reported (version-based match):
grype nginx-fixed > fixed-grype.txt
# With VEX - CVE is suppressed with justification:
grype nginx-fixed --vex vex.json > fixed-grype-vex.txt
The VEX suppression is observable in saved scan artifacts. Note that scanner
databases are rebuilt periodically and CVE presence may vary between runs.
The saved baseline-grype.txt and fixed-grype.txt files are point-in-time
evidence. The VEX document's validity does NOT depend on scanner DB state -
it is independently verifiable via the provenance chain above.
The package is named nginx (not nginx-custom). This is critical because:
nginx allows it to satisfy dependencies
and be managed by standard Debian tooling.Scanner advisory databases are rebuilt periodically. A CVE present one week may be absent the next. This project does NOT rely on scanner output as the primary proof of remediation. The evidence chain is:
Saved scan files are point-in-time evidence only.
| Package | Version | CVE-2024-6119 | CVE-2026-42945 |
|---|---|---|---|
| libssl3 | 3.0.20-1~deb12u1 | Not reported (fixed by version) | n/a |
| nginx | 1.25.5-1~bookworm+echo1 | n/a | Reported when in DB (backport invisible to scanner) |
With --vex vex.json, the CVE-2026-42945 finding (if present in the scanner
DB) is suppressed. The VEX document's validity is established by the provenance
chain (patch derivation, build attestation, regression test), not by
scanner output alone.
The test suite (test/compat.py) boots both nginx:1.25-bookworm and
nginx-fixed as Docker containers and compares behavior. Each assertion
directly tests the live fixed image against the live upstream image.
89 assertions. All passing.
Additionally, test/test_cve_2026_42945.py provides ~20 assertions
specifically targeting the backported CVE fix (see "Regression testing" above).
--with-http_v3_module flag
presence is verified; no UDP/QUIC traffic is sent).so files are present on diskRun:
make test
| Image | Size (arm64, docker images) |
|---|---|
nginx:1.25-bookworm | ~278 MB |
nginx-fixed | ~332 MB |
The fixed image is larger than upstream. This is expected: the official image
uses pre-compiled binaries in optimized layers, while ours installs build
artifacts and full runtime dependencies (including libraries for all 12 dynamic
modules) into a debian:bookworm-slim base. The size difference is a tradeoff
for source-level build control, not a regression in functionality.
The fixed image still contains vulnerabilities in system libraries not
addressed by this project (point-in-time; run make scan for current state):
These are inherited from debian:bookworm-slim and are outside the scope of
this project's nginx-focused CVE remediation.
make sbom target exists, uses syft) and link to VEX via SBOM purl.cosign and attach VEX + SBOM + patch-attestation as OCI attestations.The ./configure flags in build.sh are identical to those reported by
nginx -V in the official nginx:1.25-bookworm image. This is verified
by test/compat.py::test_nginx_version, which normalizes the
-ffile-prefix-map=<path> difference and compares the remaining flags
character-by-character.
Noteworthy flags (all present in both images):
The .deb package metadata:
Package: nginx
Version: 1.25.5-1~bookworm+echo1
Depends: libc6 (>= 2.34), libcrypt1 (>= 1:4.1.0), libpcre2-8-0 (>= 10.22),
libssl3 (>= 3.0.14), zlib1g (>= 1:1.2.11), libgeoip1 (>= 1.6.12),
libgd3 (>= 2.1.0~alpha~), libxml2 (>= 2.7.4), libxslt1.1 (>= 1.1.25),
lsb-base (>= 3.0-6), adduser
Provides: httpd, nginx, nginx-r1.25.5
Conflicts: nginx-common, nginx-core
Replaces: nginx-common, nginx-core
Key packaging decisions:
libssl3 (>= 3.0.14) - explicitly requires patched OpenSSL (upstream
declares >= 3.0.0). This is the mechanism that enforces CVE-2024-6119 fix.Provides: nginx-r1.25.5 - satisfies dynamic module packages that
depend on this virtual package./etc/nginx/nginx.conf and
/etc/nginx/conf.d/default.conf. This is required for the IPv6 entrypoint
script's dpkg-query --showformat check./bin/false
(matching upstream - verified in test suite)..so modules, full
/etc/nginx config tree, init.d/systemd/logrotate files, man page, HTML
defaults, and /etc/nginx/modules → /usr/lib/nginx/modules symlink.Package named nginx, not nginx-custom. If renamed, scanners would
not match it against nginx advisories - all nginx CVEs would silently
disappear from reports, including unfixed ones. Keeping the name nginx
ensures honest scanner reporting and makes VEX meaningful.
VEX requires CVE visibility. The purpose of VEX is to suppress a CVE that is already reported. If the scanner doesn't find the CVE (due to package renaming or DB timing), VEX has nothing to suppress.
libssl3 (>= 3.0.14) as package dependency. Rather than hoping the
runtime base happens to have a patched OpenSSL, the .deb hard-requires
a version that contains the fix for CVE-2024-6119.
Scanner database volatility. Advisory databases rebuild periodically. A CVE present one week may be absent the next. The saved scan files in this repo are point-in-time evidence; current scans may differ.
AI tools used. GitHub Copilot assisted with patch analysis (comparing nginx 1.25.5 and 1.30.1 source trees), test suite structure, identifying the package-rename scanner evasion issue, and VEX document authoring. Debian packaging details and Grype version-matching heuristics required manual verification.
| Field | Value |
|---|
| Component | OpenSSL / libssl3 |
| Severity | High (CVSS 7.5) |
| Type | Denial of Service via X.509 name checks |
| Baseline version | 3.0.11-1~deb12u2 |
| Fixed version | 3.0.14-1~deb12u2 (or later) |
| Our version | 3.0.20-1~deb12u1 |
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2024-6119 |
| Advisory | https://openssl-library.org/news/secadv/20240903.txt |
| Field | Value |
|---|
| Component | nginx ngx_http_rewrite_module |
| Severity | Medium (nginx.org classification) |
| Baseline version | nginx 1.25.5 |
| Fixed in | nginx 1.30.1 (released 2026-05-13) |
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-42945 |
| Advisory | https://my.f5.com/manage/s/article/K000161019 |
| Upstream CHANGES | nginx 1.30.1 CHANGES: "heap memory buffer overflow in rewrite module" |
| Evidence | Location / Value |
|---|
| Upstream commit | 2046b45aa0c6e712c216b9075886f3f26e9b4ca9 |
| Upstream author | Roman Arutyunyan (arut), committed by Sergey Kandaurov (pluknet), 2026-05-13 |
| Upstream PR | nginx/nginx#1350 |
| Upstream tags | release-1.31.0, release-1.30.1 |
| Related prior fix | 74d9399 (2012, similar is_args escaping issue) |
| Patch file | build/patches/CVE-2026-42945.patch |
| Provenance metadata | build/patches/CVE-2026-42945.provenance.json |
| Source tarball (vulnerable) | https://nginx.org/download/nginx-1.25.5.tar.gz SHA256:2fe2294f8af4144e7e842eaea884182a84ee7970e... |
| Source tarball (fixed) | https://nginx.org/download/nginx-1.30.1.tar.gz SHA256:99765000d974896b31ca5882d8c279ce3fe7ef6f... |
| Vulnerable file SHA256 | c4b10c95b00e43d731a61f2380d406155e037f3cfb40a3bf58711ecefddc799a |
| Fixed file SHA256 | a6182175a22d88f66b9a61ae051bdcfece10d1bb2e643f7f175eb07589143403 |
| Derivation method | diff src/http/ngx_http_script.c between the two tarballs (confirmed identical to upstream commit) |
| Lines changed | 1 (addition only) |
| Patch applies cleanly | Verified: patch --dry-run -p1 < build/patches/CVE-2026-42945.patch exits 0 |
| Build-time verification | build.sh verifies e->is_args = 0 is present in source after patching, aborts build if not |
| Build-time attestation | Written to artifacts/patch-attestation.json during build |
| Regression test | test/test_cve_2026_42945.py - exercises the vulnerable code path |
| Patch verification script | make verify-patch - independently re-derives the patch from upstream tarballs |
| Category | What is compared | Assertion count |
|---|
| Image metadata | Entrypoint, Cmd, ExposedPorts, User, StopSignal, Env, Labels | 7 |
| nginx -V arguments | Configure flags identical (normalized for -ffile-prefix-map path) | 1 |
| Dynamic modules | All 12 upstream .so files present + /etc/nginx/modules symlink | 13 |
| nginx-debug binary | --with-debug and --with-http_v3_module present in debug -V | 2 |
| Filesystem layout | ~20 key paths, log symlinks to /dev/stdout and /dev/stderr | ~20 |
| User/group config | getent passwd/group nginx output identical | 2 |
| dpkg conffiles | dpkg-query reports default.conf and nginx.conf as conffiles | 1 |
| Package provides | dpkg -s nginx shows Provides: nginx-r1.25.5 | 1 |
| NJS binary | njs -v returns 0.8.4 | 1 |
| IPv6 entrypoint | 10-listen-on-ipv6-by-default.sh mutates default.conf identically | 1 |
| Envsubst templates | NGINX_ENVSUBST template substitution produces same output | 1 |
| Startup logs | All entrypoint log lines match upstream format | 1 |
| HTTP GET/HEAD/POST | Response body, status, headers match for multiple endpoints | ~10 |
| Malformed request | Raw TCP error response identical | 1 |
| Static file serving | Mounted file served with correct content | 1 |
| TLS termination | HTTPS with self-signed cert works identically | 1 |
| Graceful shutdown | SIGQUIT produces clean exit (code 0) | 1 |
| Config reload | SIGHUP applies new config without restart | 1 |
| Concurrent requests | 50 parallel requests all return 200 | 1 |
| Risk Level | Package | CVE(s) | Notes |
|---|
| High | nginx | CVE-2023-44487 | Mitigated in nginx ≥ 1.25.3 per upstream; scanner still flags because Debian tracker has not closed the advisory |
| Critical | libgnutls30 | CVE-2026-42010, CVE-2026-33845 | No fix available in bookworm |
| Critical | libc6/libc-bin | CVE-2026-5450 | Won't fix in this Debian release |
| High | libc6/libc-bin | CVE-2026-5928, CVE-2026-5435, CVE-2026-4437 | glibc won't-fix for bookworm |
| High | dpkg | CVE-2025-6297, CVE-2026-2219 | Fixable (upgrade to 1.21.23) |
| High | libldap-2.5-0 | CVE-2023-2953 | Won't fix |
| High | curl/libcurl4 | CVE-2026-5773, CVE-2026-6276 | Won't fix |
| High | ncurses | CVE-2025-69720 | Won't fix |
| Flag / feature | Purpose |
|---|
--prefix=/etc/nginx | Docker image convention (not Debian's /usr/share/nginx) |
--with-http_v3_module | Built-in QUIC stack (independent of OpenSSL QUIC API) |
--with-pcre (PCRE2) | Links against libpcre2-8-0 (not legacy PCRE1) |
--with-stream | TCP/UDP proxy module (dynamic) |
| Security hardening | -fstack-protector-strong, FORTIFY_SOURCE=2, RELRO, PIE |
| Dynamic modules (×12) | geoip, stream_geoip, image_filter, xslt, http_js, stream_js (release + debug) |
--with-debug (debug) | nginx-debug binary built separately with identical flags + debug |