From 08ac83dd2054867eb3f812e5948f5f0306044fe1 Mon Sep 17 00:00:00 2001 From: rtemperini <29879569+rtemperini@users.noreply.github.com> Date: Thu, 22 Oct 2026 14:23:43 +0200 Subject: [PATCH 1/5] fix(security): update runtime certificate trust in v0.10.x Signed-off-by: rtemperini <29879569+rtemperini@users.noreply.github.com> --- .devcontainer/devcontainer.json | 4 +- .github/workflows/image-scan.yaml | 16 ++++ .github/workflows/tag.yaml | 2 +- Makefile | 4 +- docker/skills-init/Dockerfile | 2 +- go/Dockerfile | 2 +- go/Dockerfile.full | 8 +- python/packages/kagent-adk/pyproject.toml | 2 +- python/packages/kagent-core/pyproject.toml | 1 + python/pyproject.toml | 1 + python/samples/crewai/poem_flow/Dockerfile | 5 +- .../samples/crewai/research-crew/Dockerfile | 5 +- python/samples/langgraph/currency/Dockerfile | 5 +- .../samples/langgraph/hitl-tools/Dockerfile | 5 +- python/samples/langgraph/kebab/Dockerfile | 5 +- python/samples/openai/basic_agent/Dockerfile | 4 +- python/uv.lock | 61 ++++++------ scripts/check_python_image_trust.py | 96 +++++++++++++++++++ ui/Dockerfile | 10 +- 19 files changed, 186 insertions(+), 52 deletions(-) create mode 100644 scripts/check_python_image_trust.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3bbb647d6b..2b74027dcb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,8 +4,8 @@ "dockerfile": "Dockerfile", "args": { "TOOLS_GO_VERSION": "1.26.10", - "TOOLS_NODE_VERSION": "24.13.0", - "TOOLS_UV_VERSION": "0.10.4", + "TOOLS_NODE_VERSION": "24.22.1", + "TOOLS_UV_VERSION": "0.13.5", "TOOLS_K9S_VERSION": "0.50.4", "TOOLS_KIND_VERSION": "0.27.0", "TOOLS_ISTIO_VERSION": "1.26.0", diff --git a/.github/workflows/image-scan.yaml b/.github/workflows/image-scan.yaml index 097b02c917..15357a258c 100644 --- a/.github/workflows/image-scan.yaml +++ b/.github/workflows/image-scan.yaml @@ -30,6 +30,12 @@ jobs: fail-fast: false matrix: include: + - build_target: kagent-adk + image_name: kagent-adk + tag_suffix: "" + - build_target: kagent-adk-full + image_name: kagent-adk + tag_suffix: "-full" - build_target: controller image_name: controller tag_suffix: "" @@ -71,6 +77,8 @@ jobs: env: DOCKER_BUILDER: "docker buildx build" DOCKER_BUILD_ARGS: >- + --pull + --no-cache --cache-from=type=gha,scope=${{ env.CACHE_KEY_PREFIX }}-${{ matrix.build_target }} --cache-from=type=gha --cache-to=type=gha,scope=${{ env.CACHE_KEY_PREFIX }}-${{ matrix.build_target }},mode=max @@ -80,6 +88,14 @@ jobs: export VERSION=${{ steps.vars.outputs.version }} make build-${{ matrix.build_target }} working-directory: ./ + - name: Check Python certificate trust + if: startsWith(matrix.build_target, 'kagent-adk') + env: + IMAGE: localhost:5001/kagent-dev/kagent/${{ matrix.image_name }}:${{ steps.vars.outputs.version }}${{ matrix.tag_suffix }} + run: | + docker run --rm --entrypoint /.kagent/.venv/bin/python \ + -v "$PWD/scripts/check_python_image_trust.py:/tmp/check_python_image_trust.py:ro" \ + "$IMAGE" /tmp/check_python_image_trust.py - name: Image vulnerability scanner uses: aquasecurity/trivy-action@v0.36.0 with: diff --git a/.github/workflows/tag.yaml b/.github/workflows/tag.yaml index a7b0727eb2..d2400546e3 100644 --- a/.github/workflows/tag.yaml +++ b/.github/workflows/tag.yaml @@ -52,7 +52,7 @@ jobs: use: 'true' - name: 'Build Images' env: - DOCKER_BUILD_ARGS: "--push --platform linux/amd64,linux/arm64" + DOCKER_BUILD_ARGS: "--pull --no-cache --push --platform linux/amd64,linux/arm64" DOCKER_BUILDER: "docker buildx build" DOCKER_REGISTRY: "ghcr.io" # prod for releases, local for dev run: | diff --git a/Makefile b/Makefile index 977be33fff..19fc1c9eca 100644 --- a/Makefile +++ b/Makefile @@ -96,8 +96,8 @@ LDFLAGS := -X github.com/$(DOCKER_REPO)/go/core/internal/version.Version=$(VERSI -X github.com/$(DOCKER_REPO)/go/core/internal/version.BuildDate=$(BUILD_DATE) #tools versions -TOOLS_UV_VERSION ?= 0.10.4 -TOOLS_NODE_VERSION ?= 24 +TOOLS_UV_VERSION ?= 0.13.5 +TOOLS_NODE_VERSION ?= 24.22.1 TOOLS_PYTHON_VERSION ?= 3.13 # build args diff --git a/docker/skills-init/Dockerfile b/docker/skills-init/Dockerfile index 31feebac3a..2962b9ec0c 100644 --- a/docker/skills-init/Dockerfile +++ b/docker/skills-init/Dockerfile @@ -31,7 +31,7 @@ ARG PYTHON_GID=1001 # git is invoked by skills-init via exec.Command with an argv vector — never # through a shell — so the only attack surface here is git itself. OCI fetch # uses the in-process go-containerregistry library, so krane and jq are gone. -RUN apk upgrade --no-cache && apk add --no-cache git openssh-client ca-certificates +RUN apk upgrade --no-cache && apk add --no-cache git openssh-client "ca-certificates>=20261020-r0" COPY --from=builder /skills-init /usr/local/bin/skills-init diff --git a/go/Dockerfile b/go/Dockerfile index 5042c40728..772a544819 100644 --- a/go/Dockerfile +++ b/go/Dockerfile @@ -32,7 +32,7 @@ RUN --mount=type=cache,target=/root/go/pkg/mod,rw \ ### STAGE 2: final image # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details -FROM gcr.io/distroless/static:nonroot +FROM gcr.io/distroless/static-debian13:nonroot@sha256:d62067879de890a1def9da0e7766b7d20810a5e5555cef8506b752437d7a3e88 ARG TARGETPLATFORM WORKDIR / diff --git a/go/Dockerfile.full b/go/Dockerfile.full index 3c63876674..83273e67ba 100644 --- a/go/Dockerfile.full +++ b/go/Dockerfile.full @@ -23,12 +23,12 @@ RUN --mount=type=cache,target=/root/go/pkg/mod,rw \ CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -ldflags "$LDFLAGS" -o /app "$BUILD_PACKAGE" # sandbox-runtime produces architecture-independent JavaScript; build it natively to avoid QEMU. -FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest AS srt-builder +FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:bc76fa39ddd1482dd56a761b3331e03ba8f0e093ebb525618a56d014b480da88 AS srt-builder ARG TOOLS_PYTHON_VERSION=3.13 RUN --mount=type=cache,target=/var/cache/apk,rw \ apk add --no-cache \ - bash git ca-certificates nodejs npm node-gyp bubblewrap python-${TOOLS_PYTHON_VERSION} libstdc++ + bash git "ca-certificates>=20260909-r3" nodejs npm node-gyp bubblewrap python-${TOOLS_PYTHON_VERSION} libstdc++ # Keep the pinned sandbox-runtime revision, but replace its vulnerable locked package versions. # Both lodash-es and shell-quote are direct dependencies of sandbox-runtime, so pinning them @@ -42,12 +42,12 @@ RUN --mount=type=cache,target=/root/.npm \ npm run build && \ npm prune --omit=dev -FROM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest +FROM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:bc76fa39ddd1482dd56a761b3331e03ba8f0e093ebb525618a56d014b480da88 ARG TOOLS_PYTHON_VERSION=3.13 RUN --mount=type=cache,target=/var/cache/apk,rw \ apk add --no-cache \ - bash ca-certificates curl nodejs bubblewrap socat python-${TOOLS_PYTHON_VERSION} ripgrep libstdc++ + bash "ca-certificates>=20260909-r3" curl nodejs bubblewrap socat python-${TOOLS_PYTHON_VERSION} ripgrep libstdc++ RUN addgroup -g 1001 goagent && \ adduser -u 1001 -G goagent -s /bin/bash -D goagent diff --git a/python/packages/kagent-adk/pyproject.toml b/python/packages/kagent-adk/pyproject.toml index 5e38e6f57d..058f038bca 100644 --- a/python/packages/kagent-adk/pyproject.toml +++ b/python/packages/kagent-adk/pyproject.toml @@ -35,7 +35,7 @@ dependencies = [ "filelock>=3.20.3", # CVE-2025-68146, CVE-2026-22701: TOCTOU symlink race condition "python-multipart>=0.0.22", # CVE-2026-24486: Arbitrary file write via path traversal "httpx-sse>=0.4.3", # U+2028/U+2029 SSE line terminator fix (0.4.2) + performance regression fix (0.4.3) - "boto3>=1.28.57", + "boto3>=1.43.117", "ollama >=0.3.6", # Ollama SDK "numpy>=2.2.6", ] diff --git a/python/packages/kagent-core/pyproject.toml b/python/packages/kagent-core/pyproject.toml index 004a4769cc..6b49400461 100644 --- a/python/packages/kagent-core/pyproject.toml +++ b/python/packages/kagent-core/pyproject.toml @@ -9,6 +9,7 @@ description = "kagent common library for kagent python packages" readme = "README.md" requires-python = ">=3.11" dependencies = [ + "certifi>=2026.10.19", "a2a-sdk[http-server]>=0.3.26", "opentelemetry-api>=1.38.0,<1.39.0", "opentelemetry-sdk>=1.38.0,<1.39.0", diff --git a/python/pyproject.toml b/python/pyproject.toml index 4bdb894f75..3bfbd19402 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -11,6 +11,7 @@ dev = [ [tool.uv] constraint-dependencies = [ + "uv>=0.13.5", "aiohttp>=3.14.3", "cryptography>=50.0.0", "httplib2>=0.32.0", diff --git a/python/samples/crewai/poem_flow/Dockerfile b/python/samples/crewai/poem_flow/Dockerfile index 64406faeac..042976a017 100644 --- a/python/samples/crewai/poem_flow/Dockerfile +++ b/python/samples/crewai/poem_flow/Dockerfile @@ -3,7 +3,10 @@ ARG DOCKER_REGISTRY=ghcr.io ARG VERSION=latest # substrate's gVisor (ateom) checkpoint/restore segfaults processes linked against trixie's newer glibc # use bookworm to align with the kagent-adk runtime image -FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim +FROM python:3.13.17-slim-bookworm@sha256:e32be0e55814a08977501ca863271d9bd02443710db8f2afaf8372e2c4508530 +COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /usr/local/bin/ + +ENV GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/etc/ssl/certs/ca-certificates.crt WORKDIR /app diff --git a/python/samples/crewai/research-crew/Dockerfile b/python/samples/crewai/research-crew/Dockerfile index 271eead58f..fd5855a22f 100644 --- a/python/samples/crewai/research-crew/Dockerfile +++ b/python/samples/crewai/research-crew/Dockerfile @@ -3,7 +3,10 @@ ARG DOCKER_REGISTRY=ghcr.io ARG VERSION=latest # substrate's gVisor (ateom) checkpoint/restore segfaults processes linked against trixie's newer glibc # use bookworm to align with the kagent-adk runtime image -FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim +FROM python:3.13.17-slim-bookworm@sha256:e32be0e55814a08977501ca863271d9bd02443710db8f2afaf8372e2c4508530 +COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /usr/local/bin/ + +ENV GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/etc/ssl/certs/ca-certificates.crt WORKDIR /app diff --git a/python/samples/langgraph/currency/Dockerfile b/python/samples/langgraph/currency/Dockerfile index 27b98160ec..2f91e9d895 100644 --- a/python/samples/langgraph/currency/Dockerfile +++ b/python/samples/langgraph/currency/Dockerfile @@ -3,7 +3,10 @@ ARG DOCKER_REGISTRY=ghcr.io ARG VERSION=latest # substrate's gVisor (ateom) checkpoint/restore segfaults processes linked against trixie's newer glibc # use bookworm to align with the kagent-adk runtime image -FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim +FROM python:3.13.17-slim-bookworm@sha256:e32be0e55814a08977501ca863271d9bd02443710db8f2afaf8372e2c4508530 +COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /usr/local/bin/ + +ENV GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/etc/ssl/certs/ca-certificates.crt WORKDIR /app diff --git a/python/samples/langgraph/hitl-tools/Dockerfile b/python/samples/langgraph/hitl-tools/Dockerfile index d783b11f46..92fe0bee34 100644 --- a/python/samples/langgraph/hitl-tools/Dockerfile +++ b/python/samples/langgraph/hitl-tools/Dockerfile @@ -3,7 +3,10 @@ ARG DOCKER_REGISTRY=ghcr.io ARG VERSION=latest # substrate's gVisor (ateom) checkpoint/restore segfaults processes linked against trixie's newer glibc # use bookworm to align with the kagent-adk runtime image -FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim +FROM python:3.13.17-slim-bookworm@sha256:e32be0e55814a08977501ca863271d9bd02443710db8f2afaf8372e2c4508530 +COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /usr/local/bin/ + +ENV GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/etc/ssl/certs/ca-certificates.crt WORKDIR /app diff --git a/python/samples/langgraph/kebab/Dockerfile b/python/samples/langgraph/kebab/Dockerfile index 5eecc34f62..725ac57363 100644 --- a/python/samples/langgraph/kebab/Dockerfile +++ b/python/samples/langgraph/kebab/Dockerfile @@ -3,7 +3,10 @@ ARG DOCKER_REGISTRY=ghcr.io ARG VERSION=latest # substrate's gVisor (ateom) checkpoint/restore segfaults processes linked against trixie's newer glibc # use bookworm to align with the kagent-adk runtime image -FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim +FROM python:3.13.17-slim-bookworm@sha256:e32be0e55814a08977501ca863271d9bd02443710db8f2afaf8372e2c4508530 +COPY --from=ghcr.io/astral-sh/uv:0.13.5 /uv /uvx /usr/local/bin/ + +ENV GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/etc/ssl/certs/ca-certificates.crt WORKDIR /app diff --git a/python/samples/openai/basic_agent/Dockerfile b/python/samples/openai/basic_agent/Dockerfile index 4f9216593b..af81b7e60f 100644 --- a/python/samples/openai/basic_agent/Dockerfile +++ b/python/samples/openai/basic_agent/Dockerfile @@ -1,5 +1,7 @@ ### STAGE 1: base image -FROM ghcr.io/astral-sh/uv:python3.13-trixie-slim +FROM ghcr.io/astral-sh/uv:0.13.5-python3.13-trixie-slim + +ENV GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/etc/ssl/certs/ca-certificates.crt WORKDIR /app diff --git a/python/uv.lock b/python/uv.lock index 9f74ea4b50..ae9456aa7c 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -39,6 +39,7 @@ constraints = [ { name = "python-dotenv", specifier = ">=1.2.2" }, { name = "requests", specifier = ">=2.34.2" }, { name = "sqlparse", specifier = ">=0.6.0" }, + { name = "uv", specifier = ">=0.13.5" }, { name = "wheel", specifier = ">=0.46.2" }, ] @@ -519,30 +520,30 @@ wheels = [ [[package]] name = "boto3" -version = "1.43.36" +version = "1.43.117" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/9f/897287e955db0f50b12fd69ef45956e4fd2c7ddb48c736872f7ea2314443/boto3-1.43.36.tar.gz", hash = "sha256:587d7ee92a12e440ad12b0e7f11f3358f0c4d65b19f64726efc94aaf194aff28", size = 112690, upload-time = "2026-06-23T02:47:14.561Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/a7/61f0295f79dfdcebb5aa01f86c614fac7e1294faabbd66259d3609c50539/boto3-1.43.117.tar.gz", hash = "sha256:f3d314908d866b577ee6b74ed432c21e565c4c4dbd640ef3fd5e0da30fa13a98", size = 112677, upload-time = "2026-10-21T19:30:03.849Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/f1/274303f52483ecf199eae6f8d9b6f5951670397ee4d72c06cfd4eb644612/boto3-1.43.36-py3-none-any.whl", hash = "sha256:42942dde254673abcbc9e6e60017c88341a4f49d99d24e1f2e290fb38138c26f", size = 140031, upload-time = "2026-06-23T02:47:13.178Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3b/22b05a545fb95983733d84681364c6b0a36afb0710d832f77732d1fc4c0b/boto3-1.43.117-py3-none-any.whl", hash = "sha256:a5b6cd7e67653987fcf0e212187c16f7b58af2ae192e368f7212ddab7c05b25d", size = 140043, upload-time = "2026-10-21T19:30:02.114Z" }, ] [[package]] name = "botocore" -version = "1.43.36" +version = "1.43.117" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/37/da9e7f6ca73ac73afd7f0bb7f238aa5daba35c081e98d7f48a7c399599c0/botocore-1.43.36.tar.gz", hash = "sha256:4cae47d1b2d426316b85a0087d9e69e048f13bc003b5177d74639fe9dfd28205", size = 15625488, upload-time = "2026-06-23T02:47:03.192Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/a0/d0a5c44ad6a01f195681ddcb4ce203f06c712811daf1d15f8f8785e8a9f8/botocore-1.43.117.tar.gz", hash = "sha256:71993702832cc37c2495048b9945f2929d306fccb3fb767e9964e437e637fa6c", size = 16366888, upload-time = "2026-10-21T19:29:59.274Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/19/934f81592527a3f7f9b943c893e334c721a4644948642bc33885d584e9ec/botocore-1.43.36-py3-none-any.whl", hash = "sha256:3c65fdc39ed01d8dfde1e961b34038aed03c459f8ddf80717a12ac006475e49d", size = 15313630, upload-time = "2026-06-23T02:46:59.327Z" }, + { url = "https://files.pythonhosted.org/packages/10/94/050e35c9febf6dd2fce4a76be587ae4a323964865700467d9d7b082f7133/botocore-1.43.117-py3-none-any.whl", hash = "sha256:5920ef9b3002e1361846549078549d9154a6e0004c22853c01ce7b279d05d495", size = 16061765, upload-time = "2026-10-21T19:29:56.001Z" }, ] [[package]] @@ -2250,7 +2251,7 @@ requires-dist = [ { name = "aiofiles", specifier = ">=24.1.0" }, { name = "anthropic", extras = ["vertex"], specifier = ">=0.120.0" }, { name = "anyio", specifier = ">=4.14.2" }, - { name = "boto3", specifier = ">=1.28.57" }, + { name = "boto3", specifier = ">=1.43.117" }, { name = "fastapi", specifier = ">=0.115.1" }, { name = "filelock", specifier = ">=3.20.3" }, { name = "google-adk", specifier = ">=1.38.0,<2" }, @@ -2284,6 +2285,7 @@ version = "0.1.0" source = { editable = "packages/kagent-core" } dependencies = [ { name = "a2a-sdk", extra = ["http-server"] }, + { name = "certifi" }, { name = "opentelemetry-api" }, { name = "opentelemetry-exporter-otlp-proto-grpc" }, { name = "opentelemetry-exporter-otlp-proto-http" }, @@ -2299,6 +2301,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "a2a-sdk", extras = ["http-server"], specifier = ">=0.3.26" }, + { name = "certifi", specifier = ">=2026.10.19" }, { name = "opentelemetry-api", specifier = ">=1.38.0,<1.39.0" }, { name = "opentelemetry-exporter-otlp-proto-grpc", specifier = ">=1.38.0,<1.39.0" }, { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.38.0,<1.39.0" }, @@ -5151,28 +5154,28 @@ wheels = [ [[package]] name = "uv" -version = "0.9.26" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/6a/ef4ea19097ecdfd7df6e608f93874536af045c68fd70aa628c667815c458/uv-0.9.26.tar.gz", hash = "sha256:8b7017a01cc48847a7ae26733383a2456dd060fc50d21d58de5ee14f6b6984d7", size = 3790483, upload-time = "2026-01-15T20:51:33.582Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/e1/5c0b17833d5e3b51a897957348ff8d937a3cdfc5eea5c4a7075d8d7b9870/uv-0.9.26-py3-none-linux_armv6l.whl", hash = "sha256:7dba609e32b7bd13ef81788d580970c6ff3a8874d942755b442cffa8f25dba57", size = 22638031, upload-time = "2026-01-15T20:51:44.187Z" }, - { url = "https://files.pythonhosted.org/packages/a5/8b/68ac5825a615a8697e324f52ac0b92feb47a0ec36a63759c5f2931f0c3a0/uv-0.9.26-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b815e3b26eeed00e00f831343daba7a9d99c1506883c189453bb4d215f54faac", size = 21507805, upload-time = "2026-01-15T20:50:42.574Z" }, - { url = "https://files.pythonhosted.org/packages/0d/a2/664a338aefe009f6e38e47455ee2f64a21da7ad431dbcaf8b45d8b1a2b7a/uv-0.9.26-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1b012e6c4dfe767f818cbb6f47d02c207c9b0c82fee69a5de6d26ffb26a3ef3c", size = 20249791, upload-time = "2026-01-15T20:50:49.835Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3d/b8186a7dec1346ca4630c674b760517d28bffa813a01965f4b57596bacf3/uv-0.9.26-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:ea296b700d7c4c27acdfd23ffaef2b0ecdd0aa1b58d942c62ee87df3b30f06ac", size = 22039108, upload-time = "2026-01-15T20:51:00.675Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a9/687fd587e7a3c2c826afe72214fb24b7f07b0d8b0b0300e6a53b554180ea/uv-0.9.26-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:1ba860d2988efc27e9c19f8537a2f9fa499a8b7ebe4afbe2d3d323d72f9aee61", size = 22174763, upload-time = "2026-01-15T20:50:46.471Z" }, - { url = "https://files.pythonhosted.org/packages/38/69/7fa03ee7d59e562fca1426436f15a8c107447d41b34e0899e25ee69abfad/uv-0.9.26-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8610bdfc282a681a0a40b90495a478599aa3484c12503ef79ef42cd271fd80fe", size = 22189861, upload-time = "2026-01-15T20:51:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/10/2d/4be446a2ec09f3c428632b00a138750af47c76b0b9f987e9a5b52fef0405/uv-0.9.26-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4bf700bd071bd595084b9ee0a8d77c6a0a10ca3773d3771346a2599f306bd9c", size = 23005589, upload-time = "2026-01-15T20:50:57.185Z" }, - { url = "https://files.pythonhosted.org/packages/c3/16/860990b812136695a63a8da9fb5f819c3cf18ea37dcf5852e0e1b795ca0d/uv-0.9.26-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:89a7beea1c692f76a6f8da13beff3cbb43f7123609e48e03517cc0db5c5de87c", size = 24713505, upload-time = "2026-01-15T20:51:04.366Z" }, - { url = "https://files.pythonhosted.org/packages/01/43/5d7f360d551e62d8f8bf6624b8fca9895cea49ebe5fce8891232d7ed2321/uv-0.9.26-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:182f5c086c7d03ad447e522b70fa29a0302a70bcfefad4b8cd08496828a0e179", size = 24342500, upload-time = "2026-01-15T20:51:47.863Z" }, - { url = "https://files.pythonhosted.org/packages/9b/9c/2bae010a189e7d8e5dc555edcfd053b11ce96fad2301b919ba0d9dd23659/uv-0.9.26-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d8c62a501f13425b4b0ce1dd4c6b82f3ce5a5179e2549c55f4bb27cc0eb8ef8", size = 23222578, upload-time = "2026-01-15T20:51:36.85Z" }, - { url = "https://files.pythonhosted.org/packages/38/16/a07593a040fe6403c36f3b0a99b309f295cbfe19a1074dbadb671d5d4ef7/uv-0.9.26-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7e89798bd3df7dcc4b2b4ac4e2fc11d6b3ff4fe7d764aa3012d664c635e2922", size = 23250201, upload-time = "2026-01-15T20:51:19.117Z" }, - { url = "https://files.pythonhosted.org/packages/23/a0/45893e15ad3ab842db27c1eb3b8605b9b4023baa5d414e67cfa559a0bff0/uv-0.9.26-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:60a66f1783ec4efc87b7e1f9bd66e8fd2de3e3b30d122b31cb1487f63a3ea8b7", size = 22229160, upload-time = "2026-01-15T20:51:22.931Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c0/20a597a5c253702a223b5e745cf8c16cd5dd053080f896bb10717b3bedec/uv-0.9.26-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:63c6a1f1187facba1fb45a2fa45396980631a3427ac11b0e3d9aa3ebcf2c73cf", size = 23090730, upload-time = "2026-01-15T20:51:26.611Z" }, - { url = "https://files.pythonhosted.org/packages/40/c9/744537867d9ab593fea108638b57cca1165a0889cfd989981c942b6de9a5/uv-0.9.26-py3-none-musllinux_1_1_i686.whl", hash = "sha256:c6d8650fbc980ccb348b168266143a9bd4deebc86437537caaf8ff2a39b6ea50", size = 22436632, upload-time = "2026-01-15T20:51:12.045Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e2/be683e30262f2cf02dcb41b6c32910a6939517d50ec45f502614d239feb7/uv-0.9.26-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:25278f9298aa4dade38241a93d036739b0c87278dcfad1ec1f57e803536bfc49", size = 23480064, upload-time = "2026-01-15T20:50:53.333Z" }, - { url = "https://files.pythonhosted.org/packages/50/3e/4a7e6bc5db2beac9c4966f212805f1903d37d233f2e160737f0b24780ada/uv-0.9.26-py3-none-win32.whl", hash = "sha256:10d075e0193e3a0e6c54f830731c4cb965d6f4e11956e84a7bed7ed61d42aa27", size = 21000052, upload-time = "2026-01-15T20:51:40.753Z" }, - { url = "https://files.pythonhosted.org/packages/07/5d/eb80c6eff2a9f7d5cf35ec84fda323b74aa0054145db28baf72d35a7a301/uv-0.9.26-py3-none-win_amd64.whl", hash = "sha256:0315fc321f5644b12118f9928086513363ed9b29d74d99f1539fda1b6b5478ab", size = 23684930, upload-time = "2026-01-15T20:51:08.448Z" }, - { url = "https://files.pythonhosted.org/packages/ed/9d/3b2631931649b1783f5024796ca8ad2b42a01a829b9ce1202d973cc7bce5/uv-0.9.26-py3-none-win_arm64.whl", hash = "sha256:344ff38749b6cd7b7dfdfb382536f168cafe917ae3a5aa78b7a63746ba2a905b", size = 22158123, upload-time = "2026-01-15T20:51:30.939Z" }, +version = "0.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/f0/7565e80415f841c3500ed69d86516c7676e3b24470a218ab61b1f43071a2/uv-0.13.5.tar.gz", hash = "sha256:979409b8c9a78df140ffb19c53f511b7a73526da8d1d5e36558833055e371c6c", size = 7513309, upload-time = "2026-10-22T01:32:33.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/ff/e30478710f53cf388c892e27e90662aa2acac7a7998bdb18023c595213c1/uv-0.13.5-py3-none-linux_armv6l.whl", hash = "sha256:8d092d07d2389909027c2ed4965ade1bd46bbcecceb26686ff40927684f8c457", size = 18325046, upload-time = "2026-10-22T01:31:37.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2a/dcae794203261e49564bb9ff7c4b64a093f69ac73476cc8ce54c1deed3a1/uv-0.13.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:337a14829bb499e114989df4f908f134ff37f1fb6c33497541cfb05b28080e39", size = 17335724, upload-time = "2026-10-22T01:31:40.889Z" }, + { url = "https://files.pythonhosted.org/packages/d2/26/9ac45211123244ea68be852775093af487a64debc13621a2de239f8ebe73/uv-0.13.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a42c85fa8156e1e5e2cecdbc77bb818ea1dcf5f78b8e2a8f84c7fe4f16f0702e", size = 14470685, upload-time = "2026-10-22T01:31:43.897Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2e/5bf9acf9d11fb790420e258a875e13bba36bc890858350642aca185efe0e/uv-0.13.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:2386c2dc9b59fc1d39b1c500e4319d71d3268232eb3b38e29cd6775d6269e8d4", size = 17784583, upload-time = "2026-10-22T01:31:46.95Z" }, + { url = "https://files.pythonhosted.org/packages/f3/70/b02fdcbae77346e3ec1310e7de9dde541252e709f6641db3fbd32cd4da79/uv-0.13.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:4f62680fe58c5a83fd539e38f1956c2b577b1f5939c9020aa4c7f0addda2f4ad", size = 17866408, upload-time = "2026-10-22T01:31:50.352Z" }, + { url = "https://files.pythonhosted.org/packages/fd/b9/6663095851e0943e05e47318d7600e567bc172a1e7a26b21b8e6608e8491/uv-0.13.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e03f5717bdaaf5ab4e2df361d2395e36b515946f56ef4bed88801aeb9959fb0", size = 17753574, upload-time = "2026-10-22T01:31:53.502Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/1bb89a94486b813bbeaa1b8b491e69a25fa864626711944127b186f903de/uv-0.13.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f867c5ec24c563afaae730a13e46cdf74a1a0b64c134286ff99762a96e2abf40", size = 18502087, upload-time = "2026-10-22T01:31:56.748Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a1/b0b0c7247f3bb0f307eae8a28679ecd0d4b0e2757fcf6c87dea6f679a099/uv-0.13.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3fd5db55c20feb672ea457361aba29ab68b9ae69a14d98ad6c138b5f86dcca4", size = 19927049, upload-time = "2026-10-22T01:31:59.831Z" }, + { url = "https://files.pythonhosted.org/packages/b4/44/6bf11de78ce18869849a14178534b900dbe4e310270b2f60df6503c03137/uv-0.13.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e01481ae5212e45ee7c85fad5fc67f06b0ee0329a7c3c289dc529da884b03881", size = 19662743, upload-time = "2026-10-22T01:32:03.258Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ad/3c2a81d66d1839063662e5b6c0002ec31f8ee987dbc5295747627ca6160a/uv-0.13.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa8d26e08e34c29c0c4e820cf3181757086d547240b4f4d31ff1d8bd810ca946", size = 17211825, upload-time = "2026-10-22T01:32:06.393Z" }, + { url = "https://files.pythonhosted.org/packages/4c/92/d83d019c63c1ccc6ff17df2bfe76270412f9766e7e71aa4d4ccbf018683f/uv-0.13.5-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:91e58e304b63b6c91bac5561459ab0c3ea24f17bc01ea9359d69fed72142978f", size = 16654136, upload-time = "2026-10-22T01:32:10.086Z" }, + { url = "https://files.pythonhosted.org/packages/b8/5a/d42f9d93b322ad36647eb3bf516cc528847c240f630f6df213b6221c5262/uv-0.13.5-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:309a74d3c488bfaa3ba7bff759da73193d8781729c0a01274f4940fd32bb5712", size = 17921105, upload-time = "2026-10-22T01:32:13.174Z" }, + { url = "https://files.pythonhosted.org/packages/97/79/c5c2c39c78cf7a1dd6e041b3f7aa2cc4258d351d42dea06f6fee0bde5daa/uv-0.13.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:469cca66de1b17c75b9b9de806a64cffb57cba5e42b1e30d0bba6fa72a146f58", size = 18103266, upload-time = "2026-10-22T01:32:16.302Z" }, + { url = "https://files.pythonhosted.org/packages/01/2f/e5c70a05013077e5ecadb804418ddbab33f7279f96d052f79f565369b2a7/uv-0.13.5-py3-none-musllinux_1_1_i686.whl", hash = "sha256:2386e8dbfe110ff2f8ec8bd344dd18bf39eb9eb54e1edeeb87c3b85c34229cd3", size = 17842061, upload-time = "2026-10-22T01:32:19.208Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b0/7dba2cb636d7f58e0d74e948ad1afc6b7923e84e82de025e9951a68055db/uv-0.13.5-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d162669e3a7811c584b03690eaa11e796ad1d309aa8229d5dfe3cbcd68a90400", size = 18963145, upload-time = "2026-10-22T01:32:22.362Z" }, + { url = "https://files.pythonhosted.org/packages/00/7c/e38b312abaae1bd59ff5886126f48e9e223fbb9a8edf9e236ede976f67cb/uv-0.13.5-py3-none-win32.whl", hash = "sha256:f4e55a6e2a1089fe74ed50fbdf2df0f6f336ee148ad232b05932db5deee8007e", size = 16240919, upload-time = "2026-10-22T01:32:25.329Z" }, + { url = "https://files.pythonhosted.org/packages/32/ea/771744a784b16aa21d195cb5d5d5a2878d72fa00fc26f11ea3f9646994fe/uv-0.13.5-py3-none-win_amd64.whl", hash = "sha256:1fb29fc46ba12033e2ef95a95ac79d649e71bbb1358419bbbcbd19d86026ef2f", size = 15358650, upload-time = "2026-10-22T01:32:28.22Z" }, + { url = "https://files.pythonhosted.org/packages/52/67/1f31243f75ece410f2db1afdfc9a9f9722fdee2385e4a347c3ca1dee6d13/uv-0.13.5-py3-none-win_arm64.whl", hash = "sha256:0548460d46c8667488b1f089e936f29f0edfb1cc753f9152ffbd8bc84a46aa20", size = 15533668, upload-time = "2026-10-22T01:32:31.335Z" }, ] [[package]] diff --git a/scripts/check_python_image_trust.py b/scripts/check_python_image_trust.py new file mode 100644 index 0000000000..8cadd79a59 --- /dev/null +++ b/scripts/check_python_image_trust.py @@ -0,0 +1,96 @@ +"""Check the active Python image trust stores against the eMudhra compromise.""" + +import hashlib +import os +import re +import ssl +import sys +import sysconfig +import unittest +import zipfile +from importlib import import_module +from importlib.util import find_spec +from pathlib import Path + +import botocore.httpsession +import certifi +import grpc + +# These roots authenticate the compromised intermediates, including cross-signs. +# https://emudhra.we-have-a-problem.com/roots.json +BLOCKED_ROOTS = { + "7dd78d5f4f13459a83dff9abbba62edbaf6f2d102bf257fd712f4d9f2746ed8d", + "f2edaaff3d9b701fb4d14c9f285f8c0bad90fc3bcc1fa07d47f4718edaba34a5", + "d7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef4", + "86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b", +} + + +def bundle_fingerprints(bundle): + return { + hashlib.sha256(ssl.PEM_cert_to_DER_cert(pem)).hexdigest() + for pem in re.findall( + r"-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----", + bundle, + re.DOTALL, + ) + } + + +class ImageTrustTest(unittest.TestCase): + def assert_safe_bundle(self, path): + roots = bundle_fingerprints(Path(path).read_text()) + self.assertTrue(roots, f"Empty CA bundle: {path}") + self.assertFalse(roots & BLOCKED_ROOTS, f"Compromised CA trust in {path}") + + def test_system_roots(self): + paths = ssl.get_default_verify_paths() + roots = { + hashlib.sha256(root).hexdigest() + for root in ssl.create_default_context().get_ca_certs(binary_form=True) + } + # OpenSSL loads hashed-directory certificates lazily during verification. + if paths.capath: + for path in Path(paths.capath).iterdir(): + if path.is_file(): + roots.update(bundle_fingerprints(path.read_text())) + self.assertTrue(roots, "The Python default trust store is empty") + self.assertFalse(roots & BLOCKED_ROOTS) + + def test_library_bundles(self): + paths = [ + certifi.where(), + botocore.httpsession.get_cert_path(True), + Path(botocore.httpsession.__file__).with_name("cacert.pem"), + ] + if find_spec("httplib2") is not None: + paths.append(import_module("httplib2.certs").where()) + for path in paths: + with self.subTest(bundle=path): + self.assert_safe_bundle(path) + + def test_grpc_roots(self): + # Python gRPC's roots callback precedes its system-store fallback. + path = os.environ.get("GRPC_DEFAULT_SSL_ROOTS_FILE_PATH") + self.assertTrue(path, "gRPC must use the updated system CA bundle") + self.assert_safe_bundle(path) + self.assertIsNotNone(grpc.ssl_channel_credentials()) + + def test_managed_python(self): + self.assertGreaterEqual(sys.version_info[:3], (3, 13, 17)) + + def test_ensurepip_bundles(self): + bundled = Path(sysconfig.get_path("stdlib")) / "ensurepip" / "_bundled" + for wheel in bundled.glob("pip-*.whl"): + with self.subTest(wheel=wheel), zipfile.ZipFile(wheel) as archive: + roots = bundle_fingerprints( + archive.read("pip/_vendor/certifi/cacert.pem").decode() + ) + self.assertTrue(roots) + self.assertFalse( + roots & BLOCKED_ROOTS, f"Compromised CA trust in {wheel}" + ) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/ui/Dockerfile b/ui/Dockerfile index 256b2e86d4..3fb2d05fdc 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -1,8 +1,8 @@ ### STAGE 1: Dependencies and Build ARG BASE_IMAGE_REGISTRY=cgr.dev -ARG TOOLS_NODE_VERSION=24 +ARG TOOLS_NODE_VERSION=24.22.1 ARG BUILDPLATFORM -FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest AS deps +FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:bc76fa39ddd1482dd56a761b3331e03ba8f0e093ebb525618a56d014b480da88 AS deps ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 # This is used to print the build platform in the logs @@ -10,7 +10,7 @@ ARG BUILDPLATFORM ARG TOOLS_NODE_VERSION RUN echo "Installing on $BUILDPLATFORM" \ - && apk add --no-cache curl bash openssl unzip ca-certificates nginx supervisor "nodejs~${TOOLS_NODE_VERSION}" node-gyp \ + && apk add --no-cache curl bash openssl unzip "ca-certificates>=20260909-r3" nginx supervisor "nodejs~${TOOLS_NODE_VERSION}" node-gyp \ && update-ca-certificates ENV DO_NOT_TRACK=1 @@ -40,7 +40,7 @@ RUN --mount=type=cache,target=/root/.npm,rw \ && mkdir -p /app/ui/public ### STAGE 3: Runtime -FROM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest AS final +FROM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:bc76fa39ddd1482dd56a761b3331e03ba8f0e093ebb525618a56d014b480da88 AS final ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 ENV NODE_ENV=production @@ -49,7 +49,7 @@ ARG BUILDPLATFORM ARG TOOLS_NODE_VERSION RUN echo "Installing on $BUILDPLATFORM" \ - && apk add --no-cache curl bash openssl unzip ca-certificates nginx supervisor "nodejs~${TOOLS_NODE_VERSION}" \ + && apk add --no-cache curl bash openssl unzip "ca-certificates>=20260909-r3" nginx supervisor "nodejs~${TOOLS_NODE_VERSION}" \ && update-ca-certificates RUN mkdir -p /app/ui/public /tmp/nginx/client_temp /tmp/nginx/proxy_temp /tmp/nginx/fastcgi_temp /tmp/nginx/uwsgi_temp /tmp/nginx/scgi_temp \ From 4289ae9912037c986aa52805fce95f9daaab9c03 Mon Sep 17 00:00:00 2001 From: rtemperini <29879569+rtemperini@users.noreply.github.com> Date: Thu, 22 Oct 2026 17:32:28 +0200 Subject: [PATCH 2/5] chore: keep certificate trust fix focused Signed-off-by: rtemperini <29879569+rtemperini@users.noreply.github.com> --- .github/workflows/image-scan.yaml | 16 ----- .github/workflows/tag.yaml | 2 +- scripts/check_python_image_trust.py | 96 ----------------------------- 3 files changed, 1 insertion(+), 113 deletions(-) delete mode 100644 scripts/check_python_image_trust.py diff --git a/.github/workflows/image-scan.yaml b/.github/workflows/image-scan.yaml index 15357a258c..097b02c917 100644 --- a/.github/workflows/image-scan.yaml +++ b/.github/workflows/image-scan.yaml @@ -30,12 +30,6 @@ jobs: fail-fast: false matrix: include: - - build_target: kagent-adk - image_name: kagent-adk - tag_suffix: "" - - build_target: kagent-adk-full - image_name: kagent-adk - tag_suffix: "-full" - build_target: controller image_name: controller tag_suffix: "" @@ -77,8 +71,6 @@ jobs: env: DOCKER_BUILDER: "docker buildx build" DOCKER_BUILD_ARGS: >- - --pull - --no-cache --cache-from=type=gha,scope=${{ env.CACHE_KEY_PREFIX }}-${{ matrix.build_target }} --cache-from=type=gha --cache-to=type=gha,scope=${{ env.CACHE_KEY_PREFIX }}-${{ matrix.build_target }},mode=max @@ -88,14 +80,6 @@ jobs: export VERSION=${{ steps.vars.outputs.version }} make build-${{ matrix.build_target }} working-directory: ./ - - name: Check Python certificate trust - if: startsWith(matrix.build_target, 'kagent-adk') - env: - IMAGE: localhost:5001/kagent-dev/kagent/${{ matrix.image_name }}:${{ steps.vars.outputs.version }}${{ matrix.tag_suffix }} - run: | - docker run --rm --entrypoint /.kagent/.venv/bin/python \ - -v "$PWD/scripts/check_python_image_trust.py:/tmp/check_python_image_trust.py:ro" \ - "$IMAGE" /tmp/check_python_image_trust.py - name: Image vulnerability scanner uses: aquasecurity/trivy-action@v0.36.0 with: diff --git a/.github/workflows/tag.yaml b/.github/workflows/tag.yaml index d2400546e3..a7b0727eb2 100644 --- a/.github/workflows/tag.yaml +++ b/.github/workflows/tag.yaml @@ -52,7 +52,7 @@ jobs: use: 'true' - name: 'Build Images' env: - DOCKER_BUILD_ARGS: "--pull --no-cache --push --platform linux/amd64,linux/arm64" + DOCKER_BUILD_ARGS: "--push --platform linux/amd64,linux/arm64" DOCKER_BUILDER: "docker buildx build" DOCKER_REGISTRY: "ghcr.io" # prod for releases, local for dev run: | diff --git a/scripts/check_python_image_trust.py b/scripts/check_python_image_trust.py deleted file mode 100644 index 8cadd79a59..0000000000 --- a/scripts/check_python_image_trust.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Check the active Python image trust stores against the eMudhra compromise.""" - -import hashlib -import os -import re -import ssl -import sys -import sysconfig -import unittest -import zipfile -from importlib import import_module -from importlib.util import find_spec -from pathlib import Path - -import botocore.httpsession -import certifi -import grpc - -# These roots authenticate the compromised intermediates, including cross-signs. -# https://emudhra.we-have-a-problem.com/roots.json -BLOCKED_ROOTS = { - "7dd78d5f4f13459a83dff9abbba62edbaf6f2d102bf257fd712f4d9f2746ed8d", - "f2edaaff3d9b701fb4d14c9f285f8c0bad90fc3bcc1fa07d47f4718edaba34a5", - "d7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef4", - "86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b", -} - - -def bundle_fingerprints(bundle): - return { - hashlib.sha256(ssl.PEM_cert_to_DER_cert(pem)).hexdigest() - for pem in re.findall( - r"-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----", - bundle, - re.DOTALL, - ) - } - - -class ImageTrustTest(unittest.TestCase): - def assert_safe_bundle(self, path): - roots = bundle_fingerprints(Path(path).read_text()) - self.assertTrue(roots, f"Empty CA bundle: {path}") - self.assertFalse(roots & BLOCKED_ROOTS, f"Compromised CA trust in {path}") - - def test_system_roots(self): - paths = ssl.get_default_verify_paths() - roots = { - hashlib.sha256(root).hexdigest() - for root in ssl.create_default_context().get_ca_certs(binary_form=True) - } - # OpenSSL loads hashed-directory certificates lazily during verification. - if paths.capath: - for path in Path(paths.capath).iterdir(): - if path.is_file(): - roots.update(bundle_fingerprints(path.read_text())) - self.assertTrue(roots, "The Python default trust store is empty") - self.assertFalse(roots & BLOCKED_ROOTS) - - def test_library_bundles(self): - paths = [ - certifi.where(), - botocore.httpsession.get_cert_path(True), - Path(botocore.httpsession.__file__).with_name("cacert.pem"), - ] - if find_spec("httplib2") is not None: - paths.append(import_module("httplib2.certs").where()) - for path in paths: - with self.subTest(bundle=path): - self.assert_safe_bundle(path) - - def test_grpc_roots(self): - # Python gRPC's roots callback precedes its system-store fallback. - path = os.environ.get("GRPC_DEFAULT_SSL_ROOTS_FILE_PATH") - self.assertTrue(path, "gRPC must use the updated system CA bundle") - self.assert_safe_bundle(path) - self.assertIsNotNone(grpc.ssl_channel_credentials()) - - def test_managed_python(self): - self.assertGreaterEqual(sys.version_info[:3], (3, 13, 17)) - - def test_ensurepip_bundles(self): - bundled = Path(sysconfig.get_path("stdlib")) / "ensurepip" / "_bundled" - for wheel in bundled.glob("pip-*.whl"): - with self.subTest(wheel=wheel), zipfile.ZipFile(wheel) as archive: - roots = bundle_fingerprints( - archive.read("pip/_vendor/certifi/cacert.pem").decode() - ) - self.assertTrue(roots) - self.assertFalse( - roots & BLOCKED_ROOTS, f"Compromised CA trust in {wheel}" - ) - - -if __name__ == "__main__": - unittest.main(verbosity=2) From ec43e6a313ab43f80567958a5ca4aa11ccb3f490 Mon Sep 17 00:00:00 2001 From: rtemperini <29879569+rtemperini@users.noreply.github.com> Date: Mon, 26 Oct 2026 09:59:39 +0100 Subject: [PATCH 3/5] fix(security): enforce patched runtime version floors Signed-off-by: rtemperini <29879569+rtemperini@users.noreply.github.com> --- Makefile | 2 +- ui/Dockerfile | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 19fc1c9eca..57a92aa14a 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ LDFLAGS := -X github.com/$(DOCKER_REPO)/go/core/internal/version.Version=$(VERSI #tools versions TOOLS_UV_VERSION ?= 0.13.5 -TOOLS_NODE_VERSION ?= 24.22.1 +TOOLS_NODE_VERSION ?= 24 TOOLS_PYTHON_VERSION ?= 3.13 # build args diff --git a/ui/Dockerfile b/ui/Dockerfile index 3fb2d05fdc..49e5dfab9c 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -1,6 +1,6 @@ ### STAGE 1: Dependencies and Build ARG BASE_IMAGE_REGISTRY=cgr.dev -ARG TOOLS_NODE_VERSION=24.22.1 +ARG TOOLS_NODE_VERSION=24 ARG BUILDPLATFORM FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:bc76fa39ddd1482dd56a761b3331e03ba8f0e093ebb525618a56d014b480da88 AS deps ENV LANG=C.UTF-8 @@ -10,7 +10,7 @@ ARG BUILDPLATFORM ARG TOOLS_NODE_VERSION RUN echo "Installing on $BUILDPLATFORM" \ - && apk add --no-cache curl bash openssl unzip "ca-certificates>=20260909-r3" nginx supervisor "nodejs~${TOOLS_NODE_VERSION}" node-gyp \ + && apk add --no-cache curl bash openssl unzip "ca-certificates>=20260909-r3" nginx supervisor "nodejs-${TOOLS_NODE_VERSION}>=24.22.1" node-gyp \ && update-ca-certificates ENV DO_NOT_TRACK=1 @@ -49,7 +49,7 @@ ARG BUILDPLATFORM ARG TOOLS_NODE_VERSION RUN echo "Installing on $BUILDPLATFORM" \ - && apk add --no-cache curl bash openssl unzip "ca-certificates>=20260909-r3" nginx supervisor "nodejs~${TOOLS_NODE_VERSION}" \ + && apk add --no-cache curl bash openssl unzip "ca-certificates>=20260909-r3" nginx supervisor "nodejs-${TOOLS_NODE_VERSION}>=24.22.1" \ && update-ca-certificates RUN mkdir -p /app/ui/public /tmp/nginx/client_temp /tmp/nginx/proxy_temp /tmp/nginx/fastcgi_temp /tmp/nginx/uwsgi_temp /tmp/nginx/scgi_temp \ From dc89326e84936ce755475571139d2a375fd724bb Mon Sep 17 00:00:00 2001 From: rtemperini <29879569+rtemperini@users.noreply.github.com> Date: Fri, 6 Nov 2026 10:53:16 +0100 Subject: [PATCH 4/5] fix: use updated Wolfi trust store Signed-off-by: rtemperini <29879569+rtemperini@users.noreply.github.com> --- python/Dockerfile | 10 ++++++---- python/Dockerfile.full | 5 +++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/python/Dockerfile b/python/Dockerfile index 674008e0f3..dd7e10fe64 100644 --- a/python/Dockerfile +++ b/python/Dockerfile @@ -6,13 +6,14 @@ FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv-bin ### STAGE 2: builder # Build the uv-managed standalone Python interpreter and the project venv. Nothing # from this stage ships except /python and the venv. -FROM ${BASE_IMAGE_REGISTRY}/chainguard/wolfi-base:latest AS builder +FROM ${BASE_IMAGE_REGISTRY}/chainguard/wolfi-base:latest@sha256:8509ee4351a2e501b7f98201f2e2642396e82020c61a589cacdcfe74b9de46c1 AS builder ARG TOOLS_PYTHON_VERSION=3.13 ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 ENV PYTHONOPTIMIZE=2 ENV PYTHONUNBUFFERED=1 +ENV GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/etc/ssl/certs/ca-certificates.crt # uv configuration: install a managed standalone Python under /python and a copy-mode venv # (no editable installs) so the final image is self-contained with no source tree dependency. @@ -26,7 +27,7 @@ ENV UV_PROJECT_ENVIRONMENT=/.kagent/.venv ENV UV_PYTHON_INSTALL_DIR=/python ENV UV_PYTHON_PREFERENCE=only-managed -RUN apk add --no-cache ca-certificates +RUN apk add --no-cache "ca-certificates>=20260909-r3" COPY --from=uv-bin /uv /uvx /usr/local/bin/ @@ -75,17 +76,18 @@ RUN mkdir -p /staging/config ### STAGE 3: final # Agents that need in-container code execution / bash tools use the "full" image # (python/Dockerfile.full) instead. -FROM ${BASE_IMAGE_REGISTRY}/chainguard/wolfi-base:latest +FROM ${BASE_IMAGE_REGISTRY}/chainguard/wolfi-base:latest@sha256:8509ee4351a2e501b7f98201f2e2642396e82020c61a589cacdcfe74b9de46c1 ARG VERSION # Shared libraries the venv's C extensions link against (libstdc++ for pyarrow and friends, # zlib for numpy). The standalone CPython build bundles its own sqlite, bz2, lzma and ffi. # tzdata backs zoneinfo lookups. -RUN apk add --no-cache ca-certificates libstdc++ zlib tzdata +RUN apk add --no-cache "ca-certificates>=20260909-r3" libstdc++ zlib tzdata ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 ENV PYTHONUNBUFFERED=1 +ENV GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/etc/ssl/certs/ca-certificates.crt ENV PATH="/.kagent/.venv/bin:$PATH" ENV VIRTUAL_ENV=/.kagent/.venv diff --git a/python/Dockerfile.full b/python/Dockerfile.full index 0718737959..e2c6eff70c 100644 --- a/python/Dockerfile.full +++ b/python/Dockerfile.full @@ -12,13 +12,14 @@ FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv-bin ### STAGE 2: base os + sandbox runtime # The apk package name encodes the Node major, so the sandbox-runtime's node >= 20 # requirement is pinned by the package rather than by the base image tag. -FROM ${BASE_IMAGE_REGISTRY}/chainguard/wolfi-base:latest AS python-os +FROM ${BASE_IMAGE_REGISTRY}/chainguard/wolfi-base:latest@sha256:8509ee4351a2e501b7f98201f2e2642396e82020c61a589cacdcfe74b9de46c1 AS python-os ARG TOOLS_PYTHON_VERSION=3.13 ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 ENV PYTHONOPTIMIZE=2 ENV PYTHONUNBUFFERED=1 +ENV GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/etc/ssl/certs/ca-certificates.crt # Optimize malloc for containerized Python workloads ENV MALLOC_TRIM_THRESHOLD_=262144 @@ -37,7 +38,7 @@ ENV UV_PYTHON_INSTALL_DIR=/python ENV UV_PYTHON_PREFERENCE=only-managed RUN apk add --no-cache \ - curl openssl bash git ca-certificates tzdata \ + curl openssl bash git "ca-certificates>=20260909-r3" tzdata \ bubblewrap socat ripgrep \ nodejs-26 npm From 40854a1b51fc3e3aecfe213c8fe764d85814101f Mon Sep 17 00:00:00 2001 From: rtemperini <29879569+rtemperini@users.noreply.github.com> Date: Fri, 6 Nov 2026 10:54:12 +0100 Subject: [PATCH 5/5] fix: refresh Wolfi base digest Signed-off-by: rtemperini <29879569+rtemperini@users.noreply.github.com> --- go/Dockerfile.full | 4 ++-- ui/Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go/Dockerfile.full b/go/Dockerfile.full index 83273e67ba..ccadd436de 100644 --- a/go/Dockerfile.full +++ b/go/Dockerfile.full @@ -23,7 +23,7 @@ RUN --mount=type=cache,target=/root/go/pkg/mod,rw \ CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -ldflags "$LDFLAGS" -o /app "$BUILD_PACKAGE" # sandbox-runtime produces architecture-independent JavaScript; build it natively to avoid QEMU. -FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:bc76fa39ddd1482dd56a761b3331e03ba8f0e093ebb525618a56d014b480da88 AS srt-builder +FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:8509ee4351a2e501b7f98201f2e2642396e82020c61a589cacdcfe74b9de46c1 AS srt-builder ARG TOOLS_PYTHON_VERSION=3.13 RUN --mount=type=cache,target=/var/cache/apk,rw \ @@ -42,7 +42,7 @@ RUN --mount=type=cache,target=/root/.npm \ npm run build && \ npm prune --omit=dev -FROM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:bc76fa39ddd1482dd56a761b3331e03ba8f0e093ebb525618a56d014b480da88 +FROM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:8509ee4351a2e501b7f98201f2e2642396e82020c61a589cacdcfe74b9de46c1 ARG TOOLS_PYTHON_VERSION=3.13 RUN --mount=type=cache,target=/var/cache/apk,rw \ diff --git a/ui/Dockerfile b/ui/Dockerfile index 49e5dfab9c..fe1e313878 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -2,7 +2,7 @@ ARG BASE_IMAGE_REGISTRY=cgr.dev ARG TOOLS_NODE_VERSION=24 ARG BUILDPLATFORM -FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:bc76fa39ddd1482dd56a761b3331e03ba8f0e093ebb525618a56d014b480da88 AS deps +FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:8509ee4351a2e501b7f98201f2e2642396e82020c61a589cacdcfe74b9de46c1 AS deps ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 # This is used to print the build platform in the logs @@ -40,7 +40,7 @@ RUN --mount=type=cache,target=/root/.npm,rw \ && mkdir -p /app/ui/public ### STAGE 3: Runtime -FROM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:bc76fa39ddd1482dd56a761b3331e03ba8f0e093ebb525618a56d014b480da88 AS final +FROM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@sha256:8509ee4351a2e501b7f98201f2e2642396e82020c61a589cacdcfe74b9de46c1 AS final ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 ENV NODE_ENV=production