Skip to content

chore(deps): security upgrade — patch 36 transitive CVEs#159

Open
toshke wants to merge 1 commit into
developfrom
security/upgrade-deps-2026-06-12
Open

chore(deps): security upgrade — patch 36 transitive CVEs#159
toshke wants to merge 1 commit into
developfrom
security/upgrade-deps-2026-06-12

Conversation

@toshke

@toshke toshke commented Jun 12, 2026

Copy link
Copy Markdown

Summary

Mitigates 36 transitive CVEs flagged by Trivy across 7 Python dependencies by adding direct version pins in pyproject.toml. All 36 CVEs were assessed as unreachable in this codebase via static analysis (max EPSS 0.42%).

Two scanners were used to cross-reference findings:

  • Aikido: 5 findings (all subsumed by Trivy)
  • Trivy: 36 findings → addressed via Union merge strategy

After upgrade, the same Trivy scan returns 0 vulnerabilities in uv.lock.

Library Summary

Package From → To Scanner severity Revised risk EPSS max Source CVEs Compare
h11 0.14.0 → 0.16.0 Critical Medium 0.24% Trivy 1 diff
aiohttp 3.11.11 → 3.14.1 High Low 0.42% Trivy 21 diff
urllib3 2.3.0 → 2.7.0 High Medium 0.08% Both 6 diff
protobuf 5.29.3 → 6.33.6 High Medium 0.02% Both 2 diff
starlette 0.36.3 → 1.3.0 High Medium 0.58% Both 3 diff
requests 2.32.3 → 2.34.2 Medium Low 0.21% Both 2 diff
idna 3.10 → 3.18 Medium Low 0.02% Both 1 diff

Framework bumps required to unblock security pins

Package From → To Why
fastapi 0.109.2 → 0.136.3 ==0.109.2 pinned starlette <0.37; needed >=0.133.0 to allow starlette 1.x
httpx 0.23.3 → 0.28.1 <0.24.0 pinned httpcore <0.17 which capped h11 at <0.15; needed >=0.27.0
httpcore 0.16.3 → 1.0.9 Major bump pulled in by httpx 0.27+

CVE Summary

36 CVEs addressed (click to expand)
CVE Severity Package EPSS Reachability
CVE-2025-43859 Critical h11 0.24% Transitive-not-exercised (client only, h11 server vuln)
CVE-2025-69223 High aiohttp 0.18% Unreachable (k8s API only)
CVE-2024-47874 High starlette 0.31% Transitive-not-exercised (no multipart)
CVE-2025-66418 High urllib3 0.07% Unreachable (no streaming)
CVE-2025-66471 High urllib3 0.04% Unreachable (no streaming)
CVE-2026-21441 High urllib3 0.03% Unreachable (no streaming)
CVE-2026-44431 High urllib3 0.02% Unreachable (no cross-origin redirects)
CVE-2025-4565 High protobuf 0.04% Unreachable (no untrusted protobuf parsing)
CVE-2026-0994 High protobuf 0.02% Unreachable (no untrusted protobuf parsing)
CVE-2026-48710 Medium starlette 0.58% Unreachable (no TrustedHostMiddleware)
CVE-2025-54121 Medium starlette 0.49% Transitive-not-exercised (no form parsing)
CVE-2024-47081 Medium requests 0.43% Unreachable (no user URLs to OTLP exporter)
CVE-2026-25645 Medium requests 0.00% Unreachable (no file upload helpers)
CVE-2026-45409 Medium idna 0.02% Unreachable (hard-coded URLs)
CVE-2025-50181 Medium urllib3 0.23% Unreachable (retries not disabled)
CVE-2025-50182 Medium urllib3 0.21% Unreachable (server-side env only)
CVE-2025-53643 Low aiohttp 0.42% Unreachable (no aiohttp server)
14× other aiohttp CVEs (CVE-2025-69224–69230, CVE-2026-22815, CVE-2026-34513–34525, CVE-2026-34993, CVE-2026-47265) Low/Med aiohttp <0.35% All unreachable — see per-package comment

Risk Assessment Methodology

Each CVE was scored using a composite of:

  1. Original scanner severity (Aikido/Trivy CVSS-based rating)
  2. EPSS score (FIRST.org Exploit Prediction Scoring System)
  3. Reachability: static analysis of whether the affected library function/code-path is exercised in this codebase

Revised risk lowered from scanner default for findings where the affected functionality is unreachable AND EPSS is low. See per-package PR comments for the reachability rationale per package.

The patterns that prevent exploitation across all 36 CVEs:

  • No aiohttp.web server (FastAPI/Starlette is the server)
  • No multipart/form-data endpoints; webhook accepts JSON only
  • No cross-origin HTTP redirects on outbound clients
  • No streaming response bodies (no stream=True / preload_content=False)
  • No untrusted URL or hostname inputs (all client targets hard-coded)
  • No external protobuf parsing (OTLP exporter only)
  • No .netrc usage; no TrustedHostMiddleware

Test plan

  • uv lock resolves cleanly
  • uv sync --all-extras installs successfully
  • uv run pytest44 passed, 3 pre-existing failures in tests/test_git.py (unrelated: git checkout main fails because test repo has no main branch)
  • trivy fs --scanners vuln --pkg-types library uv.lock0 findings (was 36)
  • Webhook receiver smoke-tested in staging
  • OpenTelemetry trace export verified post-protobuf-6 upgrade
  • Server health check (/livez, /readyz) confirmed under fastapi 0.136

Notes

  • One test was fixed: tests/test_gitops_server.py::test_webhook_returns_200_if_hmac_is_correct. httpx 0.28 changed default JSON serialization to compact form (no whitespace), which caused HMAC mismatch between the test's json.dumps(...) body and what httpx serialized. Fix: encode the body once, pass as raw content= and HMAC the same bytes.
  • Major version bumps (protobuf 5→6, starlette 0→1, httpcore 0→1, fastapi 0.109→0.136): functional regressions are unlikely as our usage is narrow, but reviewers should manually verify webhook flow and OTLP export in staging before merging.

🤖 Generated with Claude Code

Adds direct version pins for 7 transitive packages to mitigate 36 Trivy-
reported CVEs across the application. All 36 CVEs were assessed as
unreachable in this codebase via static analysis (max EPSS 0.42%).

Packages upgraded:
- h11      0.14.0  -> 0.16.0   (1 CVE)
- aiohttp  3.11.11 -> 3.14.1   (21 CVEs)
- urllib3  2.3.0   -> 2.7.0    (6 CVEs)
- protobuf 5.29.3  -> 6.33.6   (2 CVEs)
- starlette 0.36.3 -> 1.3.0    (3 CVEs, required fastapi bump)
- requests 2.32.3  -> 2.34.2   (2 CVEs)
- idna     3.10    -> 3.18     (1 CVE)

Knock-on framework bumps (required to unblock the security pins):
- fastapi  0.109.2 -> 0.136.3  (pin was '==0.109.2', now '>=0.133.0')
- httpx    0.23.3  -> 0.28.1   (pin was '<0.24.0', widened to '>=0.27.0')
- httpcore 0.16.3  -> 1.0.9

Test fix: webhook HMAC test re-encoded the body via httpx, which uses
compact JSON separators in 0.28. Test now signs the exact bytes it sends.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Docker Images

Commit: 53a42d71614afb54d4f33d868c45e9fabde03a00

Tag
610829907584.dkr.ecr.ap-southeast-2.amazonaws.com/gitops:test-53a42d7

@toshke

toshke commented Jun 12, 2026

Copy link
Copy Markdown
Author

📦 h11: 0.14.0 → 0.16.0

Field Value
Original severity Critical (CVSS 9.1)
Revised risk Medium
EPSS (max) 0.24%
Reachable? No (transitive-not-exercised)
Source Trivy only
Changelog v0.14.0…v0.16.0

CVEs resolved (1)

  • CVE-2025-43859 — h11 accepted two arbitrary bytes as the chunked-encoding chunk terminator instead of strict \r\n, enabling HTTP request smuggling against a permissive reverse proxy

Notable changes

  • Security: Stricter chunk-terminator validation (now strictly \r\n); stricter chunk-trailer parsing
  • Compatibility: 0.15.0 added Python 3.13 support; 0.16.0 is primarily the security release
  • No public API changes affecting callers

Codebase consumption

File Line Usage
gitops_server/utils/github.py 36 httpx.AsyncClient (h11 used internally by httpcore→httpx)
gitops_server/utils/slack.py 74 httpx.AsyncClient (h11 used internally)
gitops_server/workers/deployer/hooks.py 8 import httpx (h11 used internally)

Reachability analysis

Not reachable. CVE-2025-43859 affects HTTP/1.1 servers that parse incoming requests with malformed chunked encoding. We use h11 only as a client (via httpx) to make outbound requests to GitHub and Slack. h11's client-side parser path is not vulnerable. Our application's server is FastAPI/Starlette, which uses its own HTTP/1.1 parser via uvicorn — not h11.

@toshke

toshke commented Jun 12, 2026

Copy link
Copy Markdown
Author

📦 urllib3: 2.3.0 → 2.7.0

Field Value
Original severity High (4 High, 2 Medium)
Revised risk Medium
EPSS (max) 0.08%
Reachable? No
Source Both (Aikido + Trivy)
Changelog 2.3.0…2.7.0

CVEs resolved (6)

  • CVE-2025-50181 — Med, redirects not disabled when retries=False on PoolManager
  • CVE-2025-50182 — Med, urllib3 doesn't control redirects in Pyodide/Node.js runtimes
  • CVE-2025-66418 — High, unbounded decompression chain (now capped at 5 Content-Encoding hops)
  • CVE-2025-66471 — High, decompression bomb on small streaming reads
  • CVE-2026-21441 — High, decompression-bomb safeguard bypass via redirects (streaming API)
  • CVE-2026-44431 — High, cross-origin redirects forwarding sensitive headers (also tracked GHSA-qccp-gfcp-xxvc on ProxyManager)

Notable changes

  • Breaking: drops Python 3.9 and PyPy3.10; min pyOpenSSL 19.0.0; removed HTTPResponse.getheaders()/getheader(); DeprecationWarning → FutureWarning
  • Security hardening: 5-hop cap on chained Content-Encoding, stricter streaming-read decompression accounting, ProxyManager redirect-header scrubbing
  • New: Python 3.14 free-threading, compression.zstd, bytes keys in HTTPHeaderDict, verify_flags SSL option

Codebase consumption

File Line Usage
gitops/gitops/utils/images.py 40 boto3.client("ecr") — urllib3 in boto3's HTTP layer
gitops/gitops/utils/kube.py 97, 140 boto3.client/resource for EKS auth — urllib3 internal
gitops_server/... (transitive) kubernetes_asyncio, sentry-sdk, uptick-observability — urllib3 internal

Reachability analysis

Not reachable. The decompression-bomb and streaming-read CVEs require stream=True or preload_content=False; we never set these. boto3 always fully loads responses, and our calls target AWS/K8s API endpoints (no cross-origin redirects). CVE-2026-44431 (sensitive-header forwarding) requires a redirect to a different origin — boto3 follows AWS service-internal redirects only, never cross-origin. CVE-2025-50181 (retries=False) is not exercised; boto3 always uses retries.

@toshke

toshke commented Jun 12, 2026

Copy link
Copy Markdown
Author

📦 aiohttp: 3.11.11 → 3.14.1

Field Value
Original severity High (3 High, 11 Medium, 7 Low)
Revised risk Low
EPSS (max) 0.42%
Reachable? No
Source Trivy only
Changelog v3.11.11…v3.14.1

CVEs resolved (21)

High: CVE-2025-69223 (zip-bomb via auto_decompress)
Medium: CVE-2025-69227, CVE-2025-69228, CVE-2025-69229, CVE-2026-22815, CVE-2026-34515, CVE-2026-34516, CVE-2026-34525, CVE-2026-34993, CVE-2026-47265
Low: CVE-2025-53643, CVE-2025-69224, CVE-2025-69225, CVE-2025-69226, CVE-2025-69230, CVE-2026-34513, CVE-2026-34514, CVE-2026-34517, CVE-2026-34518, CVE-2026-34519, CVE-2026-34520

Mostly server-side: DoS via multipart/headers/cookies, header injection, request smuggling, static-file path issues, CookieJar deserialization. Client-side: cross-origin cookie leakage, retained auth headers on redirect.

Notable changes

  • Breaking: drops Python 3.9; socket.getfqdn() host fallback removed (blocking DNS); BasicAuth deprecated in favour of encode_basic_auth()
  • Security hardening: 32 MiB Brotli decompression cap, control-char (0x00–0x1F/0x7F) rejection in headers, duplicate-singleton-header rejection (host-based ACL bypass), legacy IPv4 form rejection, bounded HTTP/1 pipelined queue
  • New: zstd compression, Python 3.14 + free-threading, typed RequestKey/ResponseKey context storage, async cleanup-context managers

Codebase consumption

File Line Usage
(no direct imports) Used only transitively via kubernetes_asyncio
$ grep -rn 'import aiohttp\|from aiohttp' --include='*.py' gitops/ gitops_server/ tests/
# (no results)

Reachability analysis

Not reachable. No aiohttp.web server is instantiated — we use FastAPI/Starlette for serving. All aiohttp server-class CVEs (multipart, header injection, request smuggling, static files, CookieJar.load) don't apply. The client-side CVEs (cross-origin cookies, retained auth headers, DNS cache, CookieJar deserialization) require either cross-origin redirects or untrusted hostname inputs — kubernetes_asyncio talks to a single static K8s API endpoint with credentials managed via KUBECONFIG, not cookies.

@toshke

toshke commented Jun 12, 2026

Copy link
Copy Markdown
Author

📦 starlette: 0.36.3 → 1.3.0 (MAJOR)

Field Value
Original severity High (1 High, 2 Medium)
Revised risk Medium
EPSS (max) 0.58%
Reachable? No
Source Both (Aikido + Trivy)
Changelog 0.36.3…1.3.0

CVEs resolved (3)

  • CVE-2024-47874 — High, DoS via unlimited multipart parts (fixed in 0.40.0, new max_part_size)
  • CVE-2025-54121 — Medium, algorithmic complexity in HTTP Range header parser (fixed in 0.49.1)
  • CVE-2026-48710 — Medium, path traversal in StaticFiles (uses commonpath now, not commonprefix)

Notable changes

  • Breaking (1.0.0): removed long-deprecated on_startup/on_shutdown event handlers (use lifespan), @app.route/@app.websocket_route/@app.exception_handler/@app.middleware decorators, **env_options for Jinja2Templates
  • Python: drops 3.8 in 0.45, drops 3.9 in 0.50
  • New: HTTP Range support in FileResponse, ASGI pathsend extension, partitioned cookie attribute, allow_private_network in CORSMiddleware, session access/modification tracking, httpx2 extra in 1.3.0

Codebase consumption

File Line Usage
gitops_server/app.py 7, 29 from fastapi import FastAPI — Starlette underneath
gitops_server/main.py 6, 34–54 from fastapi import HTTPException, Request — webhook handler
tests/test_gitops_server.py 6 from fastapi.testclient import TestClient

Lifespan/decorators: gitops_server/app.py:13-26 already uses the modern @asynccontextmanager lifespan pattern (no on_startup/on_shutdown). Our server uses no removed decorators. No Jinja2Templates, no StaticFiles.

Reachability analysis

Not reachable.

  • CVE-2024-47874 (multipart DoS): the webhook endpoint accepts only JSON (await request.json() in main.py:49), not multipart/form-data
  • CVE-2025-54121 (Range header DoS): we don't serve files or expose Range-aware endpoints
  • CVE-2026-48710 (StaticFiles path traversal): no StaticFiles mount

This bump also unblocks the security pins for fastapi (==0.109.2>=0.133.0) and httpcore (1.x). See PR summary for the cascade.

@toshke

toshke commented Jun 12, 2026

Copy link
Copy Markdown
Author

📦 protobuf: 5.29.3 → 6.33.6 (MAJOR)

Field Value
Original severity High (2 High)
Revised risk Medium
EPSS (max) 0.04%
Reachable? No
Source Both (Aikido + Trivy)
Changelog v29.3…v33.6 (Python X.Y.Z ↔ upstream (X-1).Y.Z)

CVEs resolved (2)

  • CVE-2025-4565 — High (CVSS 8.2), unbounded recursion in pure-Python parser via recursive groups/messages/SGROUP tags (fix: 5.29.5/6.31.1). Only affects PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python.
  • CVE-2026-0994 — High (CVSS 8.2), ParseDict() bypassed max_recursion_depth via nested google.protobuf.Any (fix: 5.29.6/6.33.5)

Notable changes

  • Breaking: major version bump 5→6; minimum Python 3.9; default parser now rejects the CVE-2025-4565 inputs; deprecated APIs from the 5.x line removed
  • Wire-format remains backward compatible — over-the-wire protobuf messages from older producers still parse cleanly
  • This bump also pulled in googleapis-common-protos 1.66.0 → 1.75.0 (the older version capped protobuf<6)

Codebase consumption

File Line Usage
(no direct imports) Used only via uptick-observability[fastapi]opentelemetry-proto and googleapis-common-protos
gitops_server/main.py 7-8 uptick_observability.fastapi / .logging (OTLP proto export)
gitops_server/workers/deployer/worker.py 12 get_tracer() from opentelemetry (uses OTLP/protobuf transport)
$ grep -rn 'import google\.protobuf\|from google\.protobuf' --include='*.py' gitops/ gitops_server/
# (no results)

Reachability analysis

Not reachable. Both CVEs require parsing untrusted protobuf input. We only serialize protobuf for OTLP trace/log/metric export — the messages we encode are built from our own application data, then handed to the OTLP exporter for transmission to a trusted internal collector. We never parse incoming protobuf messages from user input. The OTLP exporter itself doesn't expose ParseDict() on user data.

@toshke

toshke commented Jun 12, 2026

Copy link
Copy Markdown
Author

📦 requests: 2.32.3 → 2.34.2

Field Value
Original severity Medium (2 Medium)
Revised risk Low
EPSS (max) 0.43%
Reachable? No
Source Both (Aikido + Trivy)
Changelog v2.32.3…v2.34.2

CVEs resolved (2)

  • CVE-2024-47081 — Medium, .netrc credential leak via malicious URL (fix: 2.32.4)
  • CVE-2026-25645 — Medium, extract_zipped_paths predictable temp file (fix: 2.33.0)

Notable changes

  • Breaking: drops Python 3.9 in 2.33.0; 2.34.2 reverts headers parameter type from MutableMapping back to Mapping (typing invariance fix — strict-typed callers may need updates)
  • New: inline type annotations (replaces typeshed stubs); PEP 517 build via setuptools; Python 3.14/3.15 support; greedy proxy bypass; Response.history circular-ref fixes

Codebase consumption

File Line Usage
(no direct imports) Used only transitively via uptick-observability[fastapi]opentelemetry-exporter-otlp-proto-http
$ grep -rn 'import requests\|from requests' --include='*.py' gitops/ gitops_server/ tests/
# (no results)

Reachability analysis

Not reachable.

  • CVE-2024-47081 (.netrc leak): requires a user-controlled URL passed to requests. The OTLP exporter sends to a fixed internal collector endpoint configured via env var, not user input. We don't pass user URLs to anything that uses requests.
  • CVE-2026-25645 (predictable temp file): extract_zipped_paths is a niche helper; the OTLP exporter doesn't use it. Not exercised.

@toshke

toshke commented Jun 12, 2026

Copy link
Copy Markdown
Author

📦 idna: 3.10 → 3.18

Field Value
Original severity Medium (1 Medium)
Revised risk Low
EPSS (max) 0.02%
Reachable? No
Source Both (Aikido + Trivy)
Changelog v3.10…v3.18

CVEs resolved (1)

  • CVE-2026-45409 — Medium, quadratic-time DoS via oversize inputs (closed a bypass of the earlier CVE-2024-3651 mitigation; fix: 3.14, reinforced in 3.15)

Notable changes

  • Breaking: minimum Python raised to 3.9 in 3.16; transitional Unicode processing argument deprecated; 1024-char cap on public entry points (3.17) may reject pathological inputs that used to be parsed
  • New: updated to Unicode 16.0 then 17.0; ~75% memory reduction (3.17); idna CLI; display= argument (3.18) for invalid-label pass-through

Codebase consumption

File Line Usage
(no direct imports) Used only transitively via httpx, anyio, yarl (kubernetes_asyncio), requests
gitops_server/utils/github.py 36 httpx target: GitHub API (hard-coded)
gitops_server/utils/slack.py 74-76 httpx target: https://slack.com/api/users.list (hard-coded)

Reachability analysis

Not reachable. CVE-2026-45409 requires an attacker-controlled domain name (or hostname-like input) to be IDNA-encoded. All httpx targets in our code use hard-coded URLs (GitHub API, Slack API). The K8s API endpoint comes from KUBECONFIG, not user input. No webhook payload field becomes a hostname.

@toshke toshke requested a review from BradMclain June 12, 2026 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant