From d9bb91ba70abef05f3e4867a87a2706c6223011f Mon Sep 17 00:00:00 2001 From: Oleksandr Akhtyrskiy Date: Sun, 5 Jul 2026 19:51:43 -0600 Subject: [PATCH] Migrate ci-tools lua toolchain to 5.4 via multi-stage build Compile luacheck/busted for Lua 5.4 in a throwaway builder stage and copy only the runtime plus rock trees into the final image. This drops luarocks, gcc, and the liblua-dev -> libc6-dev -> linux-libc-dev header chain from the final image, eliminating 79 linux-libc-dev CVEs (incl. one critical) at the source rather than suppressing them. busted now runs on Lua 5.4 to match Pandoc's embedded HsLua runtime. Suppress jq CVE-2026-32316 and CVE-2026-40164 (deb12u2 fix not yet in the Debian mirror) and advertise luassert plus the Lua 5.4 interpreter. --- cspell.json | 3 ++ images/ci-tools/.trivyignore.yaml | 18 ++++++++ images/ci-tools/Dockerfile | 68 ++++++++++++++++++++++++------- images/ci-tools/README.md | 4 +- images/ci-tools/versions.lock | 4 +- scripts/ci-tools/verify.sh | 4 ++ 6 files changed, 83 insertions(+), 18 deletions(-) diff --git a/cspell.json b/cspell.json index e5fb548..291a518 100644 --- a/cspell.json +++ b/cspell.json @@ -18,10 +18,13 @@ "ignorefile", "imagetools", "importlib", + "libc", "libgnutls", + "liblua", "ltrimstr", "luacheck", "luarocks", + "luassert", "markdownlint", "mediatype", "mikefarah", diff --git a/images/ci-tools/.trivyignore.yaml b/images/ci-tools/.trivyignore.yaml index 398ea4f..9293b7b 100644 --- a/images/ci-tools/.trivyignore.yaml +++ b/images/ci-tools/.trivyignore.yaml @@ -137,3 +137,21 @@ vulnerabilities: so practical risk is negligible. Tracking: #96. Remove once both ship builds on Go >= 1.25.11 or 1.26.4. expired_at: 2026-07-21 + + - id: CVE-2026-32316 + statement: >- + jq: DoS or potential arbitrary code execution via crafted input, fixed + in 1.6-2.1+deb12u2. Affects jq 1.6-2.1+deb12u1 — the deb12u2 build is + not yet in the Debian mirror, so a rebuild still installs deb12u1. jq + runs only on trusted CI inputs here, so practical risk is negligible. + Tracking: #96. Remove once deb12u2 reaches the bookworm mirror. + expired_at: 2026-08-19 + + - id: CVE-2026-40164 + statement: >- + jq: DoS via a crafted JSON object, fixed in 1.6-2.1+deb12u2. Affects jq + 1.6-2.1+deb12u1 — the deb12u2 build is not yet in the Debian mirror, so + a rebuild still installs deb12u1. jq runs only on trusted CI inputs + here, so practical risk is negligible. Tracking: #96. Remove once + deb12u2 reaches the bookworm mirror. + expired_at: 2026-08-19 diff --git a/images/ci-tools/Dockerfile b/images/ci-tools/Dockerfile index 8808d39..40bc025 100644 --- a/images/ci-tools/Dockerfile +++ b/images/ci-tools/Dockerfile @@ -1,4 +1,45 @@ # ci-tools — shared linting image for Knight Owl CI pipelines + +# ---------- lua builder ---------- +# Compiles the Lua 5.4 rocks in a throwaway stage. luarocks, gcc, and the +# liblua5.4-dev → libc6-dev → linux-libc-dev header chain live only here and are +# never copied forward, so the final image ships none of their CVEs. +# +# The base digest is repeated verbatim in the final stage rather than hoisted +# into an ARG: Dependabot's Docker parser only bumps digests written directly in +# FROM, and an `ARG BASE_IMAGE` reference would silence its base-image updates. +# The two FROM lines must stay identical — the native .so modules built here +# load under the final stage's interpreter and need the same glibc ABI. +FROM node:26-bookworm-slim@sha256:b16ca7b4dcfb20184e1c70f9ee30c6a75ed1da669cfafd6d2add4761b123d79f AS lua-builder + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# DL3008: apt packages are intentionally unpinned +# hadolint ignore=DL3008 +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + lua5.4 \ + liblua5.4-dev \ + luarocks \ + gcc \ + make \ + unzip \ + ca-certificates \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# ---------- luacheck (Lua 5.4) ---------- +ARG LUACHECK_VERSION +RUN luarocks --lua-version=5.4 install luacheck "${LUACHECK_VERSION}" \ + && rm -rf /root/.cache/luarocks + +# ---------- busted (Lua 5.4; pulls luassert, say, mediator, term, system) ---------- +ARG BUSTED_VERSION +RUN luarocks --lua-version=5.4 install busted "${BUSTED_VERSION}" \ + && rm -rf /root/.cache/luarocks + +# ---------- final image ---------- +# Base digest duplicated from lua-builder for Dependabot — see the note there. FROM node:26-bookworm-slim@sha256:b16ca7b4dcfb20184e1c70f9ee30c6a75ed1da669cfafd6d2add4761b123d79f SHELL ["/bin/bash", "-o", "pipefail", "-c"] @@ -17,8 +58,7 @@ RUN apt-get update \ make \ curl \ ca-certificates \ - luarocks \ - gcc \ + lua5.4 \ rsync \ git \ gnupg \ @@ -111,19 +151,17 @@ ARG STYLELINT_VERSION RUN npm install -g "stylelint@${STYLELINT_VERSION}" \ && npm cache clean --force -# ---------- luacheck ---------- -ARG LUACHECK_VERSION -RUN luarocks install luacheck "${LUACHECK_VERSION}" \ - && rm -rf /root/.cache/luarocks - -# ---------- busted ---------- -ARG BUSTED_VERSION -# gcc is purged here because busted is the last luarocks tool that needs -# compilation. If another luarocks tool is added after this, move the purge. -RUN luarocks install busted "${BUSTED_VERSION}" \ - && rm -rf /root/.cache/luarocks \ - && apt-get purge -y --auto-remove gcc \ - && rm -rf /var/lib/apt/lists/* +# ---------- lua toolchain (luacheck + busted on Lua 5.4) ---------- +# Rocks are compiled against Lua 5.4 in the builder and copied as artifacts, so +# this image carries no -dev headers, no gcc, and no luarocks. The generated +# wrappers hardcode /usr/bin/lua5.4 and these absolute rock paths, so the copy +# targets must match the builder exactly. luassert (and busted's other deps) +# live under the copied rocks/share/lib trees. +COPY --from=lua-builder /usr/local/lib/luarocks/rocks-5.4 /usr/local/lib/luarocks/rocks-5.4 +COPY --from=lua-builder /usr/local/share/lua/5.4 /usr/local/share/lua/5.4 +COPY --from=lua-builder /usr/local/lib/lua/5.4 /usr/local/lib/lua/5.4 +COPY --from=lua-builder /usr/local/bin/luacheck /usr/local/bin/luacheck +COPY --from=lua-builder /usr/local/bin/busted /usr/local/bin/busted # ---------- bats-core ---------- ARG BATS_VERSION diff --git a/images/ci-tools/README.md b/images/ci-tools/README.md index 599eb11..3429333 100644 --- a/images/ci-tools/README.md +++ b/images/ci-tools/README.md @@ -9,13 +9,15 @@ CI pipelines. Published to GHCR at `ghcr.io/knight-owl-dev/ci-tools`. | --- | --- | | [actionlint](https://github.com/rhysd/actionlint) | GitHub Actions workflow linting | | [bats](https://github.com/bats-core/bats-core) | Shell script test framework with bats-support, bats-assert, bats-file helper libraries | -| [busted](https://github.com/lunarmodules/busted) | Lua testing framework | +| [busted](https://github.com/lunarmodules/busted) | Lua testing framework (runs on Lua 5.4 to match Pandoc's embedded runtime) | | [biome](https://github.com/biomejs/biome) | JavaScript/TypeScript linting | | [chktex](https://www.nongnu.org/chktex/) | LaTeX document linting | | [git](https://git-scm.com) | Version control (build-time cloning and runtime use) | | [gpg](https://gnupg.org) | GPG signature verification | | [hadolint](https://github.com/hadolint/hadolint) | Dockerfile linting | +| [lua](https://www.lua.org) | Lua 5.4 interpreter (runtime for busted; matches Pandoc's HsLua) | | [luacheck](https://github.com/lunarmodules/luacheck) | Lua script linting | +| [luassert](https://github.com/lunarmodules/luassert) | Lua assertion library (busted dependency) | | [make](https://www.gnu.org/software/make/) | Build automation | | [parallel](https://www.gnu.org/software/parallel/) | Parallel execution backend for bats --jobs | | [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) | Markdown linting | diff --git a/images/ci-tools/versions.lock b/images/ci-tools/versions.lock index 8d05e87..23b48a2 100644 --- a/images/ci-tools/versions.lock +++ b/images/ci-tools/versions.lock @@ -11,8 +11,8 @@ HADOLINT_SHA256_ARM64=331f1d3511b84a4f1e3d18d52fec284723e4019552f4f47b19322a53ce YQ_VERSION=v4.53.3 YQ_SHA256_AMD64=fa52a4e758c63d38299163fbdd1edfb4c4963247918bf9c1c5d31d84789eded4 YQ_SHA256_ARM64=578648e463a11c1b6db6010cbf41eafed6bee79466fcffa1bb446672cf7945ea -MARKDOWNLINT_CLI2_VERSION=0.22.1 -BIOME_VERSION=2.5.1 +MARKDOWNLINT_CLI2_VERSION=0.23.0 +BIOME_VERSION=2.5.2 STYLELINT_VERSION=17.14.0 LUACHECK_VERSION=1.2.0-1 BUSTED_VERSION=2.3.0-1 diff --git a/scripts/ci-tools/verify.sh b/scripts/ci-tools/verify.sh index d7634d8..bf5198f 100755 --- a/scripts/ci-tools/verify.sh +++ b/scripts/ci-tools/verify.sh @@ -35,8 +35,12 @@ check "hadolint" "${HADOLINT_VERSION}" hadolint --version check "yq" "${YQ_VERSION}" yq --version check "markdownlint-cli2" "${MARKDOWNLINT_CLI2_VERSION}" markdownlint-cli2 --version check "biome" "${BIOME_VERSION}" biome --version +check "lua" "5.4" lua5.4 -v check "luacheck" "${LUACHECK_VERSION}" luacheck --version check "busted" "${BUSTED_VERSION}" busted --version +# luassert ships as a busted dependency (no CLI); confirm the 5.4 interpreter +# can load it — this also exercises the copied rock tree. +check "luassert" "" lua5.4 -e "require('luassert')" check "chktex" "" chktex --version check "mandoc" "" command -v mandoc check "stylelint" "${STYLELINT_VERSION}" stylelint --version