Skip to content
Closed
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
17 changes: 17 additions & 0 deletions Dockerfile.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ RUN cat /kaggle_requirements.txt >> /requirements.txt
# GHSA-hq85-5cpj-mr62: uv < 0.13.4 vendors a CA bundle that trusts emSign intermediates with compromised keys.
# Upgrade it first so the patched uv performs all subsequent installs.
# The base image's uv may not be the pip-managed one, so point every uv on PATH at the new binary.
# Remove once the base image ships uv >= 0.13.4 itself.
RUN uv pip install --system --no-cache "uv>=0.13.4" && \
NEW_UV="$(python -c 'import uv; print(uv.find_uv_bin())')" && \
for old in $(which -a uv); do [ "$old" -ef "$NEW_UV" ] || ln -sf "$NEW_UV" "$old"; done
Expand Down Expand Up @@ -63,6 +64,22 @@ RUN sed -i "s/httpredir.debian.org/debian.uchicago.edu/" /etc/apt/sources.list &
apt-get install -y graphviz && pip install graphviz && \
/tmp/clean-layer.sh

# GHSA-6q48-6c7w-9gw2: emSign ECC Root CA - G3 issued intermediates with compromised keys.
# Ubuntu's noble-security ca-certificates 20260816ubuntu0.24.04.1 blocklists it in the system store.
# The Java keystore only drops certificates when rebuilt from scratch.
# Remove once the base image ships that ca-certificates and a cacerts keystore without the G3 root.
RUN apt-get install -y --only-upgrade ca-certificates && \
rm -f /etc/ssl/certs/java/cacerts && \
update-ca-certificates --fresh && \
/tmp/clean-layer.sh

# gcloud (including the gsutil and bq it wraps), gRPC, and Node vendor unpatched bundles, so point them at the system store.
# No upstream fix exists for these yet. Remove each one once the base image ships a release whose bundle drops the G3 root.
# gcloud ignores the base image's installation properties file (it can't locate its SDK root), so use the env var form.
ENV CLOUDSDK_CORE_CUSTOM_CA_CERTS_FILE=/etc/ssl/certs/ca-certificates.crt \
GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/etc/ssl/certs/ca-certificates.crt \
NODE_OPTIONS=--use-openssl-ca

# patches/keras_internal.py is not installed right now. tensorflow_decision_forests
# is no longer in the base image and its latest release (1.12.0) ships no cp313
# wheel, so there is no tensorflow_decision_forests/keras/ to patch.
Expand Down
5 changes: 5 additions & 0 deletions kaggle_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ boto3
catboost
category-encoders
# GHSA-6q48-6c7w-9gw2: older bundles trust emSign intermediates with compromised keys.
# Remove once the base image ships >=2026.10.19 itself.
certifi>=2026.10.19
cesium
comm
Expand Down Expand Up @@ -111,6 +112,7 @@ pdf2image
# Remove once the base image ships >=12.2 itself.
pillow>=12.2
# pip < 26.2.2 always loads its vendored certifi, so it needs the GHSA-6q48-6c7w-9gw2 fix separately.
# Remove once the base image ships >=26.2.2 itself.
pip>=26.2.2
plotly-express
pudb
Expand Down Expand Up @@ -151,6 +153,9 @@ transformers>=5.0.0
urllib3>=2.7
universal-pathlib
vtk
# wandb < 0.31.0 embeds a CA bundle with the GHSA-6q48-6c7w-9gw2 emSign root in its Rust binaries.
# Remove once the base image ships >=0.31.0 itself.
wandb>=0.31.0
wavio
xvfbwrapper
ydata-profiling
52 changes: 52 additions & 0 deletions tests/test_certifi.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import glob
import os
import ssl
import subprocess
import unittest

import certifi
import pip
import wandb
from packaging.version import Version

SYSTEM_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"


class TestCertifi(unittest.TestCase):
# GHSA-6q48-6c7w-9gw2: emSign ECC Root CA - G3 intermediates have compromised keys.
Expand All @@ -20,3 +26,49 @@ def test_pip_vendored_certifi_patched(self):
def test_uv_vendored_certs_patched(self):
version = subprocess.check_output(["uv", "--version"], text=True).split()[1]
self.assertGreaterEqual(Version(version), Version("0.13.4"))

# Covers both the merged bundle and the per-certificate files OpenSSL looks up by hash.
def test_system_store_patched(self):
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
for path in [SYSTEM_BUNDLE, *glob.glob("/etc/ssl/certs/*.pem")]:
context.load_verify_locations(cafile=path)
common_names = {
value
for cert in context.get_ca_certs()
for rdn in cert["subject"]
for key, value in rdn
if key == "commonName"
}
# The other emSign roots are unaffected and should still be trusted.
self.assertIn("emSign Root CA - G1", common_names)
self.assertNotIn("emSign ECC Root CA - G3", common_names)

def test_java_keystore_patched(self):
listing = subprocess.check_output(
["keytool", "-list", "-v", "-cacerts", "-storepass", "changeit"], text=True
)
self.assertIn("emSign Root CA - G1", listing)
self.assertNotIn("emSign ECC Root CA - G3", listing)

# gcloud, gRPC, and Node vendor unpatched bundles, so they must use the system store.
# Remove each of these alongside its workaround in Dockerfile.tmpl.
def test_gcloud_uses_system_store(self):
path = subprocess.check_output(
["gcloud", "config", "get", "core/custom_ca_certs_file"], text=True
).strip()
self.assertEqual(SYSTEM_BUNDLE, path)

def test_grpc_uses_system_store(self):
self.assertEqual(SYSTEM_BUNDLE, os.environ.get("GRPC_DEFAULT_SSL_ROOTS_FILE_PATH"))

def test_node_uses_system_store(self):
self.assertIn("--use-openssl-ca", os.environ.get("NODE_OPTIONS", ""))
# Node refuses to start if NODE_OPTIONS contains a flag it doesn't accept.
subprocess.check_call(["node", "-e", ""])

def test_wandb_binaries_patched(self):
bin_dir = os.path.join(os.path.dirname(wandb.__file__), "bin")
for name in os.listdir(bin_dir):
with open(os.path.join(bin_dir, name), "rb") as f:
# Not assertNotIn: its failure message would dump the whole binary.
self.assertFalse(b"emSign ECC Root CA - G3" in f.read(), name)
Loading