Skip to content

fix(security): clear util-linux CVEs and cut the Go CVE surface in the ingestion images - #31612

Merged
Khairajani merged 5 commits into
mainfrom
security/ingestion-go-utillinux-cves
Aug 17, 2026
Merged

Khairajani merged 5 commits into
mainfrom
security/ingestion-go-utillinux-cves

Conversation

@Khairajani

@Khairajani Khairajani commented Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

Fixes the util-linux CVEs in the ingestion images and cuts the go/stdlib surface. Collate side: open-metadata/openmetadata-collate#5825 — the two must land together, since the Collate image inherits a pinned ingestion-base tag.

CVE Sev Package Status
CVE-2025-14104, CVE-2026-13595, CVE-2026-27456 Medium util-linux ✅ Fixed
CVE-2026-39821 Critical go/stdlib ⚠️ Not fixable — surface cut
CVE-2026-33818, -46600, -56853, -56859, -56862 High go/stdlib ⚠️ Not fixable — surface cut
CVE-2026-56858 Medium go/stdlib ⚠️ Not fixable — surface cut

util-linux — fixed

The trixie base ships 2.41-5; trixie-security has 2.41.5-0+deb13u1, which fixes all three. Added to the late root layer of both ingestion-base Dockerfiles, not the top-of-file apt RUN — that layer's cache key never changes, so an upgrade there freezes its Debian index with it.

The package set is computed from dpkg, not hand-listed:

$(dpkg-query -W -f='${source:Package} ${Package}\n' | awk '$1=="util-linux"{print $2}')

One source package produces many binaries — util-linux, bsdutils, login, mount, liblastlog2-2, libblkid1, libmount1, libsmartcols1, libuuid1 — and scanners report each separately, so a hand-written list silently leaves behind whichever binary it forgot. liblastlog2-2 is exactly that binary: new in Debian 13, installed in the base at 2.41-5, and absent from any list written from memory. Asking dpkg which installed packages came from the util-linux source cannot miss one.

The query is guarded: apt-get install --only-upgrade with no package arguments exits 0, so a query that silently returned nothing would give a green build that shipped the vulnerable packages anyway. An empty result fails the build instead.

Verified in a real build layer — all nine land on the fix (2.41.5-0+deb13u1, login as 1:4.16.0-2+really2.41.5-0+deb13u1).

go/stdlib — not fixable, surface reduced

Every Go artifact in a published ingestion image, all built with go1.26.5. Inventory taken from openmetadata/ingestion:2.0.0-rc2 — picked simply because it is a public tag that ships the teradata extra, so this is a real build rather than a synthetic one:

Artifact Count Source
teradatasql/teradatasql.*.{so,dll,dylib} 10 teradatasqlalchemy → teradatasql
adbc_driver_flightsql/libadbc_driver_flightsql.so 1 iomete extra
/usr/bin/docker 1 docker-ce-cli, from the airflow base

No version bump fixes these. teradatasql 20.0.0.65 and adbc-driver-flightsql 1.12.0 are the newest releases on PyPI and both ship go1.26.5 builds. The fix is go1.26.6 — they clear only when Teradata and Apache Arrow rebuild.

What this PR does is cut the surface. The teradatasql wheel ships all ten platform builds it supports — 337 MB, of which exactly one file is ever dlopen()ed. ingestion/scripts/strip_teradatasql_arch_libs.sh removes the ones this image cannot load: 10 artifacts → 2, and 337 MB → 48 MB on arm64 / 70 MB on amd64 (the x86 libraries are larger).

Two files are kept, not one: the driver picks the fips variant when the host kernel reports /proc/sys/crypto/fips_enabled == 1 — a property of the node, not the build. The script mirrors the driver's selection branch-for-branch, derives the arch at build time (multi-arch images), and deletes nothing until every keeper has been found on disk and dlopen()ed. Every path exits 0.

adbc_driver_flightsql stays — real runtime dependency, single-arch, nothing to strip. Both extras are in the slim build too (filter_requirements takes an exclusion set), so these counts apply to ingestion-base-slim, the image the Snyk target builds.

Commits

  1. util-linux upgrade + teradatasql strip.
  2. Drop the unused docker CLI — separate because it changes the shipped image surface, not just package versions. 42 MB Go executable with no upgrade path here (Docker's apt repo isn't configured in the image), so removal is the only remediation. Nothing invokes it — Airflow's DockerOperator and the test helpers both use the docker-py SDK over the socket, and apt-get -s purge shows no dependents. Anyone who execs in and runs docker by hand loses that; revert this commit alone if that matters.

Verification

Layer-level builds, not scripts in isolation (linux/arm64):

  • apt layer on python:3.12-slim-trixie → all nine util-linux binaries on the fixed versions above.
  • strip layer on openmetadata/ingestion:2.0.0-rc2 via COPY --chown + RUN → 337 MB → 48 MB (arm64), 10 artifacts → 2, import teradatasql and dlopen still succeed.
  • The trixie ingestion-base chain is covered separately: same strip under its conditions (useradd -u 1000 openmetadata, HOME=/home/openmetadata, user-site install) — rules out a silent no-op from the interpreter not seeing the install.
  • Verified on both architectures: linux/arm64 337 MB → 48 MB and linux/amd64 337 MB → 70 MB, 10 artifacts → 2, import teradatasql OK on each.
  • Idempotent on re-run. Gate paths: primary keeper missing or corrupt → deletes nothing; FIPS keeper missing or corrupt → strips normally (the gate tests only the non-FIPS library); package absent → clean no-op. Every path exits 0.
  • docker build --check clean (the one WORKDIR ingestion/ warning is pre-existing).

A full multi-hour image build was not run; each changed layer was built and inspected directly.

Docker/Python only — no Java, no UI. Cherry-picks cleanly to 1.13 and 2.0 (identical Dockerfiles apart from RI_VERSION).

🤖 Generated with Claude Code

Greptile Summary

This PR reduces the CVE surface of ingestion images while preserving the runtime libraries selected for each supported platform.

  • Dynamically upgrades installed util-linux binary packages from the Debian security repository.
  • Removes the unused Docker CLI from the published Airflow-based ingestion image.
  • Adds a guarded post-install step that strips platform-inapplicable Teradata shared libraries.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
ingestion/Dockerfile Removes docker-ce-cli and invokes the guarded Teradata library cleanup after dependency installation.
ingestion/operators/docker/Dockerfile Adds a fail-closed util-linux security upgrade and strips platform-inapplicable Teradata artifacts.
ingestion/operators/docker/Dockerfile.ci Mirrors the production operator image’s util-linux upgrade and Teradata cleanup in CI.
ingestion/scripts/strip_teradatasql_arch_libs.sh Selects architecture and FIPS keepers, verifies the primary library is loadable, and removes only known non-keeper variants.

Reviews (6): Last reviewed commit: "Merge branch 'main' into security/ingest..." | Re-trigger Greptile

@Khairajani
Khairajani requested a review from a team as a code owner August 17, 2026 08:22
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@github-actions

github-actions Bot commented Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit e1110a0f9676691bd9c92d7e26365e979c932ebc in Playwright run 32048722897, attempt 2.

✅ 110 passed · ❌ 0 failed · 🟡 0 flaky · ⏭️ 0 skipped · 🧰 0 lifecycle flaky

Performance

Blocking targets: ✅ met · Optimization targets: 🟡 in progress

Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting.

🕒 Full workflow signal wall (to summary) 1h 19m 2s

⏱️ Max setup 3m 5s · max shard execution 12m 21s · max shard-job elapsed before upload 18m 34s · reporting 4s

🌐 212.78 requests/attempt · 1.79 app boots/UI scenario · 0.00% common-shard skew

Optimization targets still in progress:

  • Browser traffic was 212.78 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 1.79 per UI scenario (216 boots / 121 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard chromium-01 46 0 0 0 0 0
✅ Shard ingestion-01 28 0 0 0 0 0
✅ Shard ingestion-02 36 0 0 0 0 0

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

Comment thread ingestion/scripts/strip_teradatasql_arch_libs.sh
@Khairajani
Khairajani force-pushed the security/ingestion-go-utillinux-cves branch from 7c5dcd9 to cb8e32f Compare August 17, 2026 09:17
@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

…e ingestion images

Two unrelated root causes behind the current Inspector/Snyk findings on the
ingestion images.

util-linux: the trixie base ships 2.41-5, which carries CVE-2025-14104,
CVE-2026-13595 and CVE-2026-27456. trixie-security has 2.41.5-0+deb13u1, so this
is a straight upgrade -- added to the late root layer in both ingestion-base
Dockerfiles rather than the top-of-file apt RUN, whose cache key never changes
and would freeze the Debian index with it.

The package set is computed from dpkg rather than hand-listed. One source
package produces many binaries -- util-linux, bsdutils, login, mount,
liblastlog2-2, libblkid1, libmount1, libsmartcols1, libuuid1 -- and scanners
report each separately, so a hand-written list silently leaves behind whichever
binary it forgot. liblastlog2-2 is that binary: new in Debian 13, installed in
the base at 2.41-5, and absent from every list you would write from memory.
Asking dpkg which installed packages came from the util-linux source cannot miss
one.

The query is guarded because the failure mode is silent: `apt-get install
--only-upgrade` with no package arguments exits 0, so a query that returned
nothing would produce a green build that shipped the vulnerable packages anyway.
An empty result now fails the build instead. Verified in a real build layer on
python:3.12-slim-trixie: all nine land on the fixed versions, and the guard
fires with a clear message when the query matches nothing.

teradatasql: the wheel ships all ten platform builds it supports -- seven
Linux/AIX .so variants, a Windows .dll pair, a macOS .dylib -- 337 MB of Go
shared objects, of which exactly one is ever dlopen()ed. Every one of them
reports the full go/stdlib set (CVE-2026-39821 Critical, CVE-2026-33818,
CVE-2026-46600, CVE-2026-56853, CVE-2026-56859, CVE-2026-56862,
CVE-2026-56858). This does not fix those CVEs: 20.0.0.65 is the newest release
on PyPI and is built with go1.26.5, and the fix is go1.26.6 -- there is nothing
to upgrade to until Teradata rebuilds. It does cut the flagged artifacts from
ten to two and the package from 337 MB to ~48 MB, leaving only the code the
image can actually load.

The strip keeps two files, not one: the driver picks the `fips` variant when the
host kernel reports /proc/sys/crypto/fips_enabled == 1, which is a property of
the node, not the build. It also refuses to delete anything until every keeper
for the platform has been found on disk and dlopen()ed, so a surprise leaves a
working driver rather than a broken one.

Verified against openmetadata/ingestion 2.0.0-rc2 (linux/arm64): 337 MB -> 48 MB,
ten Go artifacts -> two, `import teradatasql` and dlopen of the kept library both
still succeed, the script is idempotent, and all three failure paths (keeper
missing, keeper corrupt, package absent) delete nothing and exit 0.
/usr/bin/docker is a 42 MB Go executable inherited from the apache/airflow base.
It reports the same go/stdlib set as the teradatasql libraries (CVE-2026-39821
Critical, plus five Highs and a Medium), and unlike those it has no upgrade path
here: Docker's apt repo is not configured in the image, so --only-upgrade cannot
reach a rebuilt package. Removing it is the only remediation available, and it
is the one Go artifact in these images that can be eliminated outright rather
than merely reduced.

Nothing in the image invokes the binary. Airflow's DockerOperator and the
ingestion test helpers both drive the daemon through the docker-py SDK over the
socket. apt-get -s purge confirms no installed package depends on it.

Kept as its own commit because this changes the shipped image surface rather
than just its package versions -- anyone who execs into the container and runs
`docker` by hand loses that. Revert this commit alone if that matters.
@Khairajani
Khairajani force-pushed the security/ingestion-go-utillinux-cves branch from cb8e32f to 643390b Compare August 17, 2026 09:48
@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@Khairajani Khairajani added the safe to test Add this label to run secure Github workflows on PRs label Aug 17, 2026
Review catch: keepers_are_loadable() required *both* keepers to dlopen before
anything was deleted, so a FIPS variant that failed to load took the whole strip
with it -- 337 MB and ten flagged artifacts left in place behind nothing but a
stderr warning, on a green build.

Testing the FIPS variant was never worth that risk. It is only ever selected on a
host whose kernel reports fips_enabled=1, which a build machine is not, so loading
it here proves nothing about the environment that will actually use it. It is also
a second Go c-shared object: dlopening it alongside the non-FIPS runtime spins up
a second Go runtime in the same process, which can fail -- or abort the
interpreter -- for reasons unrelated to whether the file is good.

The gate is now the non-FIPS keeper alone. Safety is unchanged: the FIPS variant
is in `keepers`, and the delete loop skips those, so it is never removed either
way.

Verified on both architectures this time, not just arm64:
  linux/arm64  337 MB -> 48 MB, 10 files -> 2, import teradatasql OK
  linux/amd64  337 MB -> 70 MB, 10 files -> 2, import teradatasql OK
Gate paths re-tested: primary corrupt and primary missing still delete nothing;
FIPS corrupt and FIPS missing now strip normally (both previously blocked);
package absent is still a clean no-op. Every path still exits 0.
…measured values

Review catch: the script header claimed the package drops to "~72 MB". That was an
estimate written from the amd64 file sizes before anything was built, and it did
not match the 48 MB the PR reported. Both numbers were right for different
architectures and neither said so -- amd64 measures 70 MB, arm64 48 MB, because
the x86 libraries are larger. Now stated as both, marked as measured.

The same audit caught a second drift the review did not flag: the three Dockerfile
comments said "nine of them dead weight" while the strip keeps two and deletes
eight. Exactly one library is loaded on any given host, so nine are idle, but the
FIPS variant cannot be deleted at build time because the choice is made from the
host kernel -- "nine dead weight" read as though nine were removable. Reworded to
say only the two this architecture can load are kept.

Comments only; no behaviour change. Re-ran the strip against
openmetadata/ingestion:2.0.0-rc2 to confirm: strips 8, import teradatasql OK.
@Khairajani
Khairajani enabled auto-merge August 17, 2026 17:05
@Khairajani
Khairajani requested a deployment to test August 17, 2026 17:36 — with GitHub Actions Abandoned
@Khairajani
Khairajani requested a deployment to test August 17, 2026 17:36 — with GitHub Actions Abandoned
@Khairajani
Khairajani added this pull request to the merge queue Aug 17, 2026
Merged via the queue into main with commit 45c96d3 Aug 17, 2026
109 of 113 checks passed
@Khairajani
Khairajani deleted the security/ingestion-go-utillinux-cves branch August 17, 2026 20:53
@gitar-bot

gitar-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 1 resolved / 1 findings

Upgrades util-linux to clear critical CVEs and strips unused Go architectures from the teradatasql wheel alongside dropping the docker CLI, addressing the missing FIPS variant dlopen handling. No issues found.

✅ 1 resolved
✅ Edge Case: Strip silently no-ops if FIPS variant can't dlopen at build

📄 ingestion/scripts/strip_teradatasql_arch_libs.sh:97-103 📄 ingestion/scripts/strip_teradatasql_arch_libs.sh:130-144 📄 ingestion/scripts/strip_teradatasql_arch_libs.sh:160-163
On x86_64/arm the keeper set is ["so","fips.so"] and keepers_are_loadable() requires both to dlopen before anything is deleted. The FIPS variant is a separate Go c-shared object; loading it in the same process as the non-FIPS runtime, or on a non-FIPS build host, may fail (or abort the interpreter), which leaves the whole 337MB teradatasql tree in place with only a stderr WARNING and no build signal — silently defeating the size/CVE-surface reduction this PR is for. The PR verified only linux/arm64; amd64 is unverified. Consider requiring dlopen success only for the non-FIPS keeper and merely existence for the FIPS keeper (which is never loaded on this build host anyway), so a FIPS-load failure doesn't block the strip.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

Khairajani added a commit that referenced this pull request Aug 17, 2026
…e ingestion images (#31612)

* fix(security): clear util-linux CVEs and cut the Go CVE surface in the ingestion images

Two unrelated root causes behind the current Inspector/Snyk findings on the
ingestion images.

util-linux: the trixie base ships 2.41-5, which carries CVE-2025-14104,
CVE-2026-13595 and CVE-2026-27456. trixie-security has 2.41.5-0+deb13u1, so this
is a straight upgrade -- added to the late root layer in both ingestion-base
Dockerfiles rather than the top-of-file apt RUN, whose cache key never changes
and would freeze the Debian index with it.

The package set is computed from dpkg rather than hand-listed. One source
package produces many binaries -- util-linux, bsdutils, login, mount,
liblastlog2-2, libblkid1, libmount1, libsmartcols1, libuuid1 -- and scanners
report each separately, so a hand-written list silently leaves behind whichever
binary it forgot. liblastlog2-2 is that binary: new in Debian 13, installed in
the base at 2.41-5, and absent from every list you would write from memory.
Asking dpkg which installed packages came from the util-linux source cannot miss
one.

The query is guarded because the failure mode is silent: `apt-get install
--only-upgrade` with no package arguments exits 0, so a query that returned
nothing would produce a green build that shipped the vulnerable packages anyway.
An empty result now fails the build instead. Verified in a real build layer on
python:3.12-slim-trixie: all nine land on the fixed versions, and the guard
fires with a clear message when the query matches nothing.

teradatasql: the wheel ships all ten platform builds it supports -- seven
Linux/AIX .so variants, a Windows .dll pair, a macOS .dylib -- 337 MB of Go
shared objects, of which exactly one is ever dlopen()ed. Every one of them
reports the full go/stdlib set (CVE-2026-39821 Critical, CVE-2026-33818,
CVE-2026-46600, CVE-2026-56853, CVE-2026-56859, CVE-2026-56862,
CVE-2026-56858). This does not fix those CVEs: 20.0.0.65 is the newest release
on PyPI and is built with go1.26.5, and the fix is go1.26.6 -- there is nothing
to upgrade to until Teradata rebuilds. It does cut the flagged artifacts from
ten to two and the package from 337 MB to ~48 MB, leaving only the code the
image can actually load.

The strip keeps two files, not one: the driver picks the `fips` variant when the
host kernel reports /proc/sys/crypto/fips_enabled == 1, which is a property of
the node, not the build. It also refuses to delete anything until every keeper
for the platform has been found on disk and dlopen()ed, so a surprise leaves a
working driver rather than a broken one.

Verified against openmetadata/ingestion 2.0.0-rc2 (linux/arm64): 337 MB -> 48 MB,
ten Go artifacts -> two, `import teradatasql` and dlopen of the kept library both
still succeed, the script is idempotent, and all three failure paths (keeper
missing, keeper corrupt, package absent) delete nothing and exit 0.

* fix(security): drop the unused docker CLI from the ingestion image

/usr/bin/docker is a 42 MB Go executable inherited from the apache/airflow base.
It reports the same go/stdlib set as the teradatasql libraries (CVE-2026-39821
Critical, plus five Highs and a Medium), and unlike those it has no upgrade path
here: Docker's apt repo is not configured in the image, so --only-upgrade cannot
reach a rebuilt package. Removing it is the only remediation available, and it
is the one Go artifact in these images that can be eliminated outright rather
than merely reduced.

Nothing in the image invokes the binary. Airflow's DockerOperator and the
ingestion test helpers both drive the daemon through the docker-py SDK over the
socket. apt-get -s purge confirms no installed package depends on it.

Kept as its own commit because this changes the shipped image surface rather
than just its package versions -- anyone who execs into the container and runs
`docker` by hand loses that. Revert this commit alone if that matters.

* fix(security): gate the teradatasql strip on the non-FIPS library only

Review catch: keepers_are_loadable() required *both* keepers to dlopen before
anything was deleted, so a FIPS variant that failed to load took the whole strip
with it -- 337 MB and ten flagged artifacts left in place behind nothing but a
stderr warning, on a green build.

Testing the FIPS variant was never worth that risk. It is only ever selected on a
host whose kernel reports fips_enabled=1, which a build machine is not, so loading
it here proves nothing about the environment that will actually use it. It is also
a second Go c-shared object: dlopening it alongside the non-FIPS runtime spins up
a second Go runtime in the same process, which can fail -- or abort the
interpreter -- for reasons unrelated to whether the file is good.

The gate is now the non-FIPS keeper alone. Safety is unchanged: the FIPS variant
is in `keepers`, and the delete loop skips those, so it is never removed either
way.

Verified on both architectures this time, not just arm64:
  linux/arm64  337 MB -> 48 MB, 10 files -> 2, import teradatasql OK
  linux/amd64  337 MB -> 70 MB, 10 files -> 2, import teradatasql OK
Gate paths re-tested: primary corrupt and primary missing still delete nothing;
FIPS corrupt and FIPS missing now strip normally (both previously blocked);
package absent is still a clean no-op. Every path still exits 0.

* docs(security): correct the teradatasql size and count claims to the measured values

Review catch: the script header claimed the package drops to "~72 MB". That was an
estimate written from the amd64 file sizes before anything was built, and it did
not match the 48 MB the PR reported. Both numbers were right for different
architectures and neither said so -- amd64 measures 70 MB, arm64 48 MB, because
the x86 libraries are larger. Now stated as both, marked as measured.

The same audit caught a second drift the review did not flag: the three Dockerfile
comments said "nine of them dead weight" while the strip keeps two and deletes
eight. Exactly one library is loaded on any given host, so nine are idle, but the
FIPS variant cannot be deleted at build time because the choice is made from the
host kernel -- "nine dead weight" read as though nine were removable. Reworded to
say only the two this architecture can load are kept.

Comments only; no behaviour change. Re-ran the strip against
openmetadata/ingestion:2.0.0-rc2 to confirm: strips 8, import teradatasql OK.
Khairajani added a commit that referenced this pull request Aug 17, 2026
…e ingestion images (#31612)

* fix(security): clear util-linux CVEs and cut the Go CVE surface in the ingestion images

Two unrelated root causes behind the current Inspector/Snyk findings on the
ingestion images.

util-linux: the trixie base ships 2.41-5, which carries CVE-2025-14104,
CVE-2026-13595 and CVE-2026-27456. trixie-security has 2.41.5-0+deb13u1, so this
is a straight upgrade -- added to the late root layer in both ingestion-base
Dockerfiles rather than the top-of-file apt RUN, whose cache key never changes
and would freeze the Debian index with it.

The package set is computed from dpkg rather than hand-listed. One source
package produces many binaries -- util-linux, bsdutils, login, mount,
liblastlog2-2, libblkid1, libmount1, libsmartcols1, libuuid1 -- and scanners
report each separately, so a hand-written list silently leaves behind whichever
binary it forgot. liblastlog2-2 is that binary: new in Debian 13, installed in
the base at 2.41-5, and absent from every list you would write from memory.
Asking dpkg which installed packages came from the util-linux source cannot miss
one.

The query is guarded because the failure mode is silent: `apt-get install
--only-upgrade` with no package arguments exits 0, so a query that returned
nothing would produce a green build that shipped the vulnerable packages anyway.
An empty result now fails the build instead. Verified in a real build layer on
python:3.12-slim-trixie: all nine land on the fixed versions, and the guard
fires with a clear message when the query matches nothing.

teradatasql: the wheel ships all ten platform builds it supports -- seven
Linux/AIX .so variants, a Windows .dll pair, a macOS .dylib -- 337 MB of Go
shared objects, of which exactly one is ever dlopen()ed. Every one of them
reports the full go/stdlib set (CVE-2026-39821 Critical, CVE-2026-33818,
CVE-2026-46600, CVE-2026-56853, CVE-2026-56859, CVE-2026-56862,
CVE-2026-56858). This does not fix those CVEs: 20.0.0.65 is the newest release
on PyPI and is built with go1.26.5, and the fix is go1.26.6 -- there is nothing
to upgrade to until Teradata rebuilds. It does cut the flagged artifacts from
ten to two and the package from 337 MB to ~48 MB, leaving only the code the
image can actually load.

The strip keeps two files, not one: the driver picks the `fips` variant when the
host kernel reports /proc/sys/crypto/fips_enabled == 1, which is a property of
the node, not the build. It also refuses to delete anything until every keeper
for the platform has been found on disk and dlopen()ed, so a surprise leaves a
working driver rather than a broken one.

Verified against openmetadata/ingestion 2.0.0-rc2 (linux/arm64): 337 MB -> 48 MB,
ten Go artifacts -> two, `import teradatasql` and dlopen of the kept library both
still succeed, the script is idempotent, and all three failure paths (keeper
missing, keeper corrupt, package absent) delete nothing and exit 0.

* fix(security): drop the unused docker CLI from the ingestion image

/usr/bin/docker is a 42 MB Go executable inherited from the apache/airflow base.
It reports the same go/stdlib set as the teradatasql libraries (CVE-2026-39821
Critical, plus five Highs and a Medium), and unlike those it has no upgrade path
here: Docker's apt repo is not configured in the image, so --only-upgrade cannot
reach a rebuilt package. Removing it is the only remediation available, and it
is the one Go artifact in these images that can be eliminated outright rather
than merely reduced.

Nothing in the image invokes the binary. Airflow's DockerOperator and the
ingestion test helpers both drive the daemon through the docker-py SDK over the
socket. apt-get -s purge confirms no installed package depends on it.

Kept as its own commit because this changes the shipped image surface rather
than just its package versions -- anyone who execs into the container and runs
`docker` by hand loses that. Revert this commit alone if that matters.

* fix(security): gate the teradatasql strip on the non-FIPS library only

Review catch: keepers_are_loadable() required *both* keepers to dlopen before
anything was deleted, so a FIPS variant that failed to load took the whole strip
with it -- 337 MB and ten flagged artifacts left in place behind nothing but a
stderr warning, on a green build.

Testing the FIPS variant was never worth that risk. It is only ever selected on a
host whose kernel reports fips_enabled=1, which a build machine is not, so loading
it here proves nothing about the environment that will actually use it. It is also
a second Go c-shared object: dlopening it alongside the non-FIPS runtime spins up
a second Go runtime in the same process, which can fail -- or abort the
interpreter -- for reasons unrelated to whether the file is good.

The gate is now the non-FIPS keeper alone. Safety is unchanged: the FIPS variant
is in `keepers`, and the delete loop skips those, so it is never removed either
way.

Verified on both architectures this time, not just arm64:
  linux/arm64  337 MB -> 48 MB, 10 files -> 2, import teradatasql OK
  linux/amd64  337 MB -> 70 MB, 10 files -> 2, import teradatasql OK
Gate paths re-tested: primary corrupt and primary missing still delete nothing;
FIPS corrupt and FIPS missing now strip normally (both previously blocked);
package absent is still a clean no-op. Every path still exits 0.

* docs(security): correct the teradatasql size and count claims to the measured values

Review catch: the script header claimed the package drops to "~72 MB". That was an
estimate written from the amd64 file sizes before anything was built, and it did
not match the 48 MB the PR reported. Both numbers were right for different
architectures and neither said so -- amd64 measures 70 MB, arm64 48 MB, because
the x86 libraries are larger. Now stated as both, marked as measured.

The same audit caught a second drift the review did not flag: the three Dockerfile
comments said "nine of them dead weight" while the strip keeps two and deletes
eight. Exactly one library is loaded on any given host, so nine are idle, but the
FIPS variant cannot be deleted at build time because the choice is made from the
host kernel -- "nine dead weight" read as though nine were removable. Reworded to
say only the two this architecture can load are kept.

Comments only; no behaviour change. Re-ran the strip against
openmetadata/ingestion:2.0.0-rc2 to confirm: strips 8, import teradatasql OK.

This branch was previously deployed

1 inactive deployment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs skip-pr-checks Bypass PR metadata validation check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants