From e2c6d821100717fc2f22855e7c0c6b2b9a470b6c Mon Sep 17 00:00:00 2001 From: rtemperini <29879569+rtemperini@users.noreply.github.com> Date: Thu, 22 Oct 2026 14:24:06 +0200 Subject: [PATCH 1/2] fix(security): refresh Python and native runtime trust Signed-off-by: rtemperini <29879569+rtemperini@users.noreply.github.com> --- .devcontainer/devcontainer.json | 4 +- .github/actions/publish-image/action.yaml | 2 +- .github/workflows/image-scan.yaml | 13 +++ Makefile | 2 +- go/Dockerfile | 2 +- go/harness/claude/Dockerfile | 8 +- go/harness/codex/Dockerfile | 2 +- go/sandbox/guest/Dockerfile | 2 +- python/Dockerfile | 5 +- 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 | 83 ++++++++-------- scripts/check_python_image_trust.py | 96 +++++++++++++++++++ ui/Dockerfile | 6 +- 21 files changed, 195 insertions(+), 63 deletions(-) create mode 100644 scripts/check_python_image_trust.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c92c1c3b89..3ea5dcfb9e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,8 +4,8 @@ "dockerfile": "Dockerfile", "args": { "TOOLS_GO_VERSION": "1.27.3", - "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/actions/publish-image/action.yaml b/.github/actions/publish-image/action.yaml index f64fa85637..612aae14e5 100644 --- a/.github/actions/publish-image/action.yaml +++ b/.github/actions/publish-image/action.yaml @@ -33,7 +33,7 @@ runs: DOCKER_BUILDER: docker buildx build DOCKER_REGISTRY: ghcr.io run: | - export DOCKER_BUILD_ARGS="--push --platform linux/amd64,linux/arm64" + export DOCKER_BUILD_ARGS="--pull --no-cache --push --platform linux/amd64,linux/arm64" export RUNTIME_IMAGE_METADATA_DIR="$RUNNER_TEMP" make "build-$IMAGE" - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 diff --git a/.github/workflows/image-scan.yaml b/.github/workflows/image-scan.yaml index b88d7df807..95bcc1800d 100644 --- a/.github/workflows/image-scan.yaml +++ b/.github/workflows/image-scan.yaml @@ -30,6 +30,9 @@ jobs: fail-fast: false matrix: include: + - build_target: kagent-adk + image_name: kagent-adk + tag_suffix: "" - build_target: controller image_name: controller tag_suffix: "" @@ -71,6 +74,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 +85,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@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 env: diff --git a/Makefile b/Makefile index f006cd64c5..9d4daa7ee7 100644 --- a/Makefile +++ b/Makefile @@ -107,7 +107,7 @@ LDFLAGS := -X github.com/$(DOCKER_REPO)/go/core/internal/version.Version=$(VERSI -X github.com/$(DOCKER_REPO)/go/core/internal/version.SubstrateVersion=$(SUBSTRATE_VERSION) #tools versions -TOOLS_UV_VERSION ?= 0.10.4 +TOOLS_UV_VERSION ?= 0.13.5 TOOLS_NODE_VERSION ?= 24 TOOLS_PYTHON_VERSION ?= 3.13 BUF_VERSION ?= v1.72.0 diff --git a/go/Dockerfile b/go/Dockerfile index c37111e678..1ae039b16b 100644 --- a/go/Dockerfile +++ b/go/Dockerfile @@ -38,7 +38,7 @@ ARG TARGETPLATFORM # The runtime user gets a passwd entry and a home it owns: commands an agent # runs (git config --global, CLIs that keep a config under $HOME/.config) # write there. -RUN apk upgrade --no-cache && apk add --no-cache bash ca-certificates git && \ +RUN apk upgrade --no-cache && apk add --no-cache bash "ca-certificates>=20261020-r0" git && \ addgroup -S -g 65532 kagent && \ adduser -S -D -u 65532 -G kagent -h /home/kagent kagent && \ mkdir -p /skills /plugins /data/plugins && \ diff --git a/go/harness/claude/Dockerfile b/go/harness/claude/Dockerfile index 03e17a5d73..3834b4025b 100644 --- a/go/harness/claude/Dockerfile +++ b/go/harness/claude/Dockerfile @@ -20,14 +20,14 @@ RUN --mount=type=cache,target=/root/go/pkg/mod,rw \ go build -trimpath -o /kagent-claude ./harness/claude/cmd FROM alpine:3.22 -ARG CLAUDE_CODE_VERSION=2.1.285 -ARG CLAUDE_CODE_AMD64_SHA256=7b4414af1bc06eb6d91730759bd01c4c02a4976d0b8526237e9a6d8c33eaf102 -ARG CLAUDE_CODE_ARM64_SHA256=31efc4136bc678575f4c6730e248d34f89dbfea0468be1c5d012af199cd62ee8 +ARG CLAUDE_CODE_VERSION=2.1.308 +ARG CLAUDE_CODE_AMD64_SHA256=45cc10091e10ca658daa691e879058ba1d4752218c63577d1c7d54b0c7dc9a68 +ARG CLAUDE_CODE_ARM64_SHA256=b820bec20177aa09875515cf5ffadb445ecf814ec7c8ed51ba2f86c3090a692d ARG TARGETARCH ARG VERSION ENV USE_BUILTIN_RIPGREP=0 -RUN apk upgrade --no-cache && apk add --no-cache bash ca-certificates git libgcc libstdc++ ripgrep \ +RUN apk upgrade --no-cache && apk add --no-cache bash "ca-certificates>=20261020-r0" git libgcc libstdc++ ripgrep \ && case "${TARGETARCH}" in \ amd64) platform="linux-x64-musl"; checksum="${CLAUDE_CODE_AMD64_SHA256}" ;; \ arm64) platform="linux-arm64-musl"; checksum="${CLAUDE_CODE_ARM64_SHA256}" ;; \ diff --git a/go/harness/codex/Dockerfile b/go/harness/codex/Dockerfile index e28d97aee4..045e3a51f8 100644 --- a/go/harness/codex/Dockerfile +++ b/go/harness/codex/Dockerfile @@ -26,7 +26,7 @@ ARG CODEX_CLI_ARM64_SHA256=3c02e2ae34be0d06e62557e98fc5c0a783bec5a2fed406fe00e56 ARG TARGETARCH ARG VERSION -RUN apk upgrade --no-cache && apk add --no-cache bash ca-certificates git libgcc libstdc++ \ +RUN apk upgrade --no-cache && apk add --no-cache bash "ca-certificates>=20261020-r0" git libgcc libstdc++ \ && case "${TARGETARCH}" in \ amd64) platform="x86_64-unknown-linux-musl"; checksum="${CODEX_CLI_AMD64_SHA256}" ;; \ arm64) platform="aarch64-unknown-linux-musl"; checksum="${CODEX_CLI_ARM64_SHA256}" ;; \ diff --git a/go/sandbox/guest/Dockerfile b/go/sandbox/guest/Dockerfile index ad8d6e2fe3..25715704f8 100644 --- a/go/sandbox/guest/Dockerfile +++ b/go/sandbox/guest/Dockerfile @@ -17,7 +17,7 @@ RUN --mount=type=cache,target=/root/go/pkg/mod,rw \ FROM alpine:3.22 -RUN apk add --no-cache bash ca-certificates git && \ +RUN apk add --no-cache bash "ca-certificates>=20261020-r0" git && \ mkdir -p /data/workspace /data/guest-logs && \ chown -R 65532:65532 /data diff --git a/python/Dockerfile b/python/Dockerfile index cc6b72fc83..95bea9e3e8 100644 --- a/python/Dockerfile +++ b/python/Dockerfile @@ -1,8 +1,8 @@ -ARG UV_VERSION=0.12.3 +ARG UV_VERSION=0.13.5 FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv-bin # Match the BYO images' glibc version for Substrate checkpoint/restore. -FROM debian:bookworm-slim AS base +FROM debian:bookworm-slim@sha256:7ff6ba31527b9e336162fa061746c366c4b2032f4d2cff2078bea43f78efe3f8 AS base ENV UV_PYTHON_INSTALL_DIR=/python COPY --from=uv-bin /uv /usr/local/bin/uv RUN apt-get update \ @@ -40,6 +40,7 @@ COPY --from=builder /.kagent/.venv /.kagent/.venv ENV LANG=C.UTF-8 \ LC_ALL=C.UTF-8 \ PYTHONUNBUFFERED=1 \ + GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=/etc/ssl/certs/ca-certificates.crt \ UV_PYTHON_DOWNLOADS=never \ UV_CACHE_DIR=/config/.cache/uv \ PATH="/.kagent/.venv/bin:$PATH" \ diff --git a/python/packages/kagent-adk/pyproject.toml b/python/packages/kagent-adk/pyproject.toml index 601bb19c65..ae4295cc8a 100644 --- a/python/packages/kagent-adk/pyproject.toml +++ b/python/packages/kagent-adk/pyproject.toml @@ -40,7 +40,7 @@ dependencies = [ "filelock>=4.0.9", # 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", "azure-identity>=1.19.0", # Azure Workload Identity for Azure OpenAI / Foundry diff --git a/python/packages/kagent-core/pyproject.toml b/python/packages/kagent-core/pyproject.toml index 3d2421da42..36bcd359ef 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]>=1.1.5,<2", "kagent-proto>=0.1.0", "opentelemetry-api>=1.42.1,<2", diff --git a/python/pyproject.toml b/python/pyproject.toml index 22920841cc..ace54a2cc5 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -15,6 +15,7 @@ override-dependencies = [ "opentelemetry-sdk>=1.45.1,<1.46", ] 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 853801a361..f3aff0e258 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.11, <3.14" resolution-markers = [ "python_full_version >= '3.13'", @@ -42,6 +42,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" }, ] overrides = [ @@ -256,9 +257,9 @@ name = "aiologic" version = "0.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sniffio", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, - { name = "wrapt", marker = "python_full_version < '3.13'" }, + { name = "sniffio" }, + { name = "typing-extensions" }, + { name = "wrapt" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/7a/d51f2fde1e8ae8a83431f8e97b7a71e9358cdb1d4d2ce6be387fa44d68de/aiologic-0.17.1.tar.gz", hash = "sha256:2e1b93b9e88ced318c2a63ad7b382688f40cbfe40e3d42258d49dc9c5aea179d", size = 252354, upload-time = "2026-06-27T20:41:33.25Z" } wheels = [ @@ -569,30 +570,30 @@ wheels = [ [[package]] name = "boto3" -version = "1.43.114" +version = "1.43.117" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/8c/f6f884dc947789317e73ed6fce85e18580d22e9f90e48d67c2367b02667e/boto3-1.43.114.tar.gz", hash = "sha256:be704857751564a5cf69c5bbaadbfa01c22806409815c73563db42fbffe583a2", size = 112653, upload-time = "2026-10-14T19:24:22.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/c8/f8/0799a101e6f65c8b687f50c218654cef1e44658e946c7d33d362e2572621/boto3-1.43.114-py3-none-any.whl", hash = "sha256:d9cac2eb921ce674970cef1c9ad750f85ee3a846aedcf188d18368fb9eb6da23", size = 140043, upload-time = "2026-10-14T19:24:21.038Z" }, + { 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.114" +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/ce/c8/b508359d1f3846a918c06807a9ae27eee063f904559269e42ccde9de09ea/botocore-1.43.114.tar.gz", hash = "sha256:f366fa4db518775632ad1eb128cd8203ca46396cecf37209d904f0bbc049ce90", size = 16369844, upload-time = "2026-10-14T19:24:17.683Z" } +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/9a/41/7c6fa7ac5fcfd5ea3c6f32aab001942da32b184a210f39042778cb1ad8ed/botocore-1.43.114-py3-none-any.whl", hash = "sha256:d1c441a22e93e158de5b1e026205f5d6d67a4545d10540c5090c62dccb3a9eca", size = 16067885, upload-time = "2026-10-14T19:24:14.629Z" }, + { 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]] @@ -611,11 +612,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.8.3" +version = "2026.10.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, + { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" }, ] [[package]] @@ -908,8 +909,8 @@ name = "culsans" version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiologic", marker = "python_full_version < '3.13'" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "aiologic" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/e3/49afa1bc180e0d28008ec6bcdf82a4072d1c7a41032b5b759b60814ca4b0/culsans-0.11.0.tar.gz", hash = "sha256:0b43d0d05dce6106293d114c86e3fb4bfc63088cfe8ff08ed3fe36891447fe33", size = 107546, upload-time = "2025-12-31T23:15:38.196Z" } wheels = [ @@ -1501,8 +1502,8 @@ name = "httpcore2" version = "2.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "h11", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, - { name = "truststore", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" }, + { name = "h11" }, + { name = "truststore" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz", hash = "sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103", size = 68071, upload-time = "2026-09-23T07:47:22.372Z" } wheels = [ @@ -1895,7 +1896,7 @@ requires-dist = [ { name = "anthropic", extras = ["vertex"], specifier = ">=1.15.0" }, { name = "anyio", specifier = ">=4.14.2" }, { name = "azure-identity", specifier = ">=1.19.0" }, - { name = "boto3", specifier = ">=1.28.57" }, + { name = "boto3", specifier = ">=1.43.117" }, { name = "fastapi", specifier = ">=0.115.1" }, { name = "filelock", specifier = ">=4.0.9" }, { name = "google-adk", extras = ["a2a", "db"], specifier = ">=2.12.0,<3" }, @@ -1934,6 +1935,7 @@ version = "0.2.0" source = { editable = "packages/kagent-core" } dependencies = [ { name = "a2a-sdk", extra = ["http-server"] }, + { name = "certifi" }, { name = "kagent-proto" }, { name = "opentelemetry-api" }, { name = "opentelemetry-distro" }, @@ -1963,6 +1965,7 @@ openai-agents = [ [package.metadata] requires-dist = [ { name = "a2a-sdk", extras = ["http-server"], specifier = ">=1.1.5,<2" }, + { name = "certifi", specifier = ">=2026.10.19" }, { name = "kagent-proto", editable = "packages/kagent-proto" }, { name = "opentelemetry-api", specifier = ">=1.42.1,<2" }, { name = "opentelemetry-distro", specifier = ">=0.63b0,<1" }, @@ -4815,28 +4818,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 534642386f..7565cc9303 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -1,6 +1,6 @@ ### STAGE 1: Dependencies ARG BASE_IMAGE_REGISTRY=cgr.dev -ARG WOLFI_BASE_DIGEST=sha256:256c88bb066424045021090932d50be93c8e3f2ce3586707702a401b146f3e43 +ARG WOLFI_BASE_DIGEST=sha256:bc76fa39ddd1482dd56a761b3331e03ba8f0e093ebb525618a56d014b480da88 ARG TOOLS_NODE_VERSION=24 ARG BUILDPLATFORM FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest@${WOLFI_BASE_DIGEST} AS deps @@ -17,7 +17,7 @@ RUN echo "Installing on $BUILDPLATFORM" \ "bash~5" \ "openssl~3" \ "unzip~6" \ - ca-certificates \ + "ca-certificates>=20260909-r3" \ "nodejs-${TOOLS_NODE_VERSION}" \ npm-12 \ "node-gyp~13" \ @@ -70,7 +70,7 @@ ARG BUILDPLATFORM RUN echo "Installing on $BUILDPLATFORM" \ && apk add --no-cache \ "bash~5" \ - ca-certificates \ + "ca-certificates>=20260909-r3" \ "curl~8" \ "nginx-mainline~1" \ && update-ca-certificates From f1ce39adc47f658669021ab6b07e191521ecb9ec 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/2] chore: keep certificate trust fix focused Signed-off-by: rtemperini <29879569+rtemperini@users.noreply.github.com> --- .github/actions/publish-image/action.yaml | 2 +- .github/workflows/image-scan.yaml | 13 --- scripts/check_python_image_trust.py | 96 ----------------------- 3 files changed, 1 insertion(+), 110 deletions(-) delete mode 100644 scripts/check_python_image_trust.py diff --git a/.github/actions/publish-image/action.yaml b/.github/actions/publish-image/action.yaml index 612aae14e5..f64fa85637 100644 --- a/.github/actions/publish-image/action.yaml +++ b/.github/actions/publish-image/action.yaml @@ -33,7 +33,7 @@ runs: DOCKER_BUILDER: docker buildx build DOCKER_REGISTRY: ghcr.io run: | - export DOCKER_BUILD_ARGS="--pull --no-cache --push --platform linux/amd64,linux/arm64" + export DOCKER_BUILD_ARGS="--push --platform linux/amd64,linux/arm64" export RUNTIME_IMAGE_METADATA_DIR="$RUNNER_TEMP" make "build-$IMAGE" - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 diff --git a/.github/workflows/image-scan.yaml b/.github/workflows/image-scan.yaml index 95bcc1800d..b88d7df807 100644 --- a/.github/workflows/image-scan.yaml +++ b/.github/workflows/image-scan.yaml @@ -30,9 +30,6 @@ jobs: fail-fast: false matrix: include: - - build_target: kagent-adk - image_name: kagent-adk - tag_suffix: "" - build_target: controller image_name: controller tag_suffix: "" @@ -74,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 @@ -85,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@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 env: 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)