Severity Daily

IT and AI security incidents, checked against the primary source

Tag: FastAPI

  • Google’s Agent Development Kit read any file for an unauthenticated caller, and the CVE landed 239 days after the fix

    Google’s Agent Development Kit read any file for an unauthenticated caller, and the CVE landed 239 days after the fix

    A CVE published this morning describes an unauthenticated arbitrary file read in Google’s Agent Development Kit; the code was fixed on January 8, 2026, and the record landed 239 days later with a changelog link for an advisory.

    What happened

    Google Cloud published CVE-2026-79707 at 11:17 a.m. UTC on Friday, September 4, 2026. The description is short and unambiguous: “A Path Traversal vulnerability in the builder endpoint in Google Cloud Agent Development Kit (ADK) versions 1.9.0 through 1.21.0 on Python allows an unauthenticated remote attacker to read arbitrary files using a crafted file_path query parameter.”

    Google scores it CVSS v4.0 8.7, High, on the vector AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N, with a Provider Urgency of Amber. The weakness is CWE-22. The affected package is google-adk on PyPI, from 1.9.0 up to but not including 1.22.0.

    The record carries two references and no advisory: a link to a heading in the project’s CHANGELOG.md, and a link to the commit. There is no Google Cloud security bulletin for it. There is no GitHub Security Advisory for it either — the GitHub Advisory Database listing for google-adk currently carries two entries, GHSA-rg7c-g689-fr3x (CVE-2026-4810, code injection and missing authentication, April 13, 2026) and GHSA-8qg5-x5vm-75jx (CVE-2026-18236, July 29, 2026), and this is neither of them.

    The code is public, so the timeline can be checked rather than inferred. Package upload times from PyPI: 1.9.0 was published July 31, 2025; 1.21.0, the last affected release, on December 12, 2025; and 1.22.0, which contains the fix, on January 8, 2026. The current release is 2.8.0, from August 26, 2026. The vulnerable window ran roughly five months. The gap between the fix shipping and the CVE describing it is 239 days.

    Downloading both wheels and reading the source shows exactly what the bug was. In 1.21.0, google/adk/cli/fast_api.py registers this route:

    @app.get("/builder/app/{app_name}", response_class=PlainTextResponse)
    async def get_agent_builder(app_name: str, file_path: Optional[str] = None, tmp: Optional[bool] = False):
        base_path = Path.cwd() / agents_dir
        agent_dir = base_path / app_name
        ...
        agent_file_path = agent_dir / file_path
        if not agent_file_path.is_file():
            return ""
        else:
            return FileResponse(path=agent_file_path, media_type="application/x-yaml", ...)

    The caller’s file_path goes straight into a path join and then into a FileResponse. The route declares no authentication dependency; a search of that module for Depends(, dependencies=, bearer, or API-key handling returns nothing.

    Two separate ways out of the directory follow from that one line. The obvious one is ../. The quieter one is that agent_dir / file_path uses pathlib’s join operator, and joining an absolute path discards the left-hand side entirely: Path("/agents/app") / "/etc/passwd" evaluates to /etc/passwd. No traversal sequence is needed, and a filter that only looks for .. would not see it. The media_type is set to application/x-yaml, but the response body is whatever the file contains.

    The fix in 1.22.0 addresses both. It adds _parse_file_path(), which rejects a path that starts with / and any path with a .. component; _has_parent_reference(); _get_app_root(), which validates the app name and confirms the resolved root stays under the agents directory; and _resolve_under_dir(), which resolves the final path and checks is_relative_to() before returning it. That is a correct fix, applied at four layers.

    The commit that carries it is titled “fix: Harden YAML builder tmp save/cleanup.” In the 1.22.0 release notes it sits in a list of bug fixes, between “Avoid local .adk storage in Cloud Run/GKE” and “Handle overriding of requirements when deploying to agent engine.” Nothing in the message, the changelog line, or the release notes says security, path traversal, or file disclosure.

    Why it matters

    The reflex reading is that this is a development server and therefore does not matter. That reading is wrong on two counts, and both are checkable in Google’s own materials.

    First, the vulnerable route is registered inside get_fast_api_app(), unconditionally. The function takes a required web parameter, and it is natural to assume the builder endpoints belong to the browser UI that flag controls. They do not. They are added after the base app is constructed, outside the if web: block, so they exist whether or not the web interface is being served. Running headless does not remove them.

    Second, get_fast_api_app() is not an internal detail. It is the function Google’s own Cloud Run deployment guide instructs developers to call from their main.py:

    from google.adk.cli.fast_api import get_fast_api_app
    app: FastAPI = get_fast_api_app(agents_dir=AGENT_DIR, session_service_uri=SESSION_SERVICE_URI,
                                   allow_origins=ALLOWED_ORIGINS, web=SERVE_WEB_INTERFACE)

    The same page’s example deployment command ends with --allow-unauthenticated, documented there as “Allows public access to the service. Remove this flag for private services.” Anyone who followed that guide between August 2025 and January 2026 and did not remove the flag put an unauthenticated arbitrary file read on the public internet, in a container that by construction holds agent configuration, and usually service-account material and API keys alongside it.

    There is a second detail in the version history worth noticing. When the endpoint first appeared in 1.9.0, it was decorated @working_in_progress("builder_get is not ready for use.") — the project’s own marker for code that is not meant to be relied on. By 1.21.0 that decorator is gone from the module entirely, while the unsafe path join is unchanged. The route was promoted out of work-in-progress status without the input handling being revisited. That is a specific and common failure mode: the guard rail that made the risk acceptable was the label, and the label was removed as a cleanup.

    The disclosure shape is the last thing, and it is the part that generalizes. Google Cloud published a second CVE from the same CNA at the same minute today: CVE-2026-4644, a privilege escalation in Integration Connectors, which does have a bulletin — GCP-2026-059, dated September 4, 2026 — and which says, in full, “No customer action is required. This vulnerability was patched on December 11, 2025.” For a managed service that is a defensible disclosure: Google fixed it in the fleet nine months ago, nobody has anything to do, and the CVE is a matter of record.

    ADK is not a managed service. It is a package a developer pins in a requirements file, and Google cannot upgrade it on anyone’s behalf. A team that pinned google-adk==1.20.0 last fall and has not moved since has been exposed for the entire period, is exposed right now, and until this morning had nothing in any feed to tell them. The two CVEs got the same treatment on the same day; only one of the two products could absorb it. The one that could not is the one that got a changelog anchor instead of an advisory.

    What to do

    • Upgrade google-adk to 1.22.0 or later. Current is 2.8.0. Anything from 1.9.0 through 1.21.0 is affected; 1.8.0 and earlier do not carry the read route at all.
    • Check what you actually run, not what you last installed. pip show google-adk in the running image; grep -r "google-adk" requirements*.txt pyproject.toml poetry.lock uv.lock across repositories. Pinned versions in container images are the ones that will be missed.
    • Find exposed deployments before you finish the upgrade. For Cloud Run: gcloud run services get-iam-policy SERVICE --region REGION and look for allUsers on roles/run.invoker. Anything ADK-derived and publicly invokable should be closed or put behind authentication now, independent of the package upgrade.
    • Test for it directly. On a build you control, request /builder/app/<app>?file_path=/etc/passwd against the service. A patched build returns an empty body and logs a rejection; a vulnerable one returns the file.
    • Treat credentials in reach of the process as exposed if the service was publicly invokable during the window. Service-account keys, model API keys, and anything in a mounted secret file were all readable by an unauthenticated caller who knew a valid app name.
    • There is no known exploitation of this CVE, and no exploit code has been published. It is not on the KEV catalog and carries no federal deadline.

    Sourcing note

    The CVE record was read from NVD at services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2026-79707: published September 4, 2026 at 11:17 a.m. UTC, last modified at 4:18 p.m. UTC, source identifier f45cbf4e-4146-4068-b7e1-655ffc2c548c (Google Cloud). Release dates for 1.9.0, 1.21.0, 1.22.0, and 2.8.0 are PyPI upload timestamps, read from the registry API rather than from the changelog. The vulnerable and fixed code was read from the 1.21.0 and 1.22.0 wheels downloaded from PyPI and unpacked locally; the 1.9.0 wheel was unpacked to confirm the @working_in_progress decorator and the introduction of the endpoint, and 1.8.0 to confirm the read route is absent there. Quotations of the code are from those files. The Cloud Run guidance and the --allow-unauthenticated example are quoted from Google’s ADK documentation.

    GCP-2026-059 and its “No customer action is required” sentence are quoted from Google Cloud’s security bulletins page. The statement that no GitHub Security Advisory exists for this CVE reflects the advisory database listing for google-adk as of publication; one may be added later. Google has not said who reported the bug, whether it was found internally or externally, or whether it was ever exploited; none of those are asserted here. Whether the eight-month gap between the January fix and today’s record was a backlog, a late external report against already-fixed code, or a deliberate delay is not stated anywhere in the record and is unresolved.

    Sources: NVD, CVE-2026-79707; google/adk-python commit 6f259f0; google-adk on PyPI; ADK Cloud Run deployment guide; Google Cloud security bulletins.

  • Starlette’s BadHost is KEV-listed at CVSS 6.5, and CISA filed the FastAPI dependency under a bug class it is not

    Starlette’s BadHost is KEV-listed at CVSS 6.5, and CISA filed the FastAPI dependency under a bug class it is not

    Three independent scorers rate CVE-2026-48710 a 6.5 medium, CISA’s catalog calls it request smuggling, and the researchers who found it call it an authentication bypass — it is now on a federal clock either way.

    What happened

    CISA added CVE-2026-48710 to the Known Exploited Vulnerabilities catalog on September 2, 2026. NVD’s record carries the fields verbatim: cisaExploitAdd of 2026-09-02, cisaActionDue of 2026-09-16. That is the 14-day band, not the three-day one.

    The catalog names it “Kludex Starlette HTTP Request/Response Smuggling Vulnerability.” The flaw is in Starlette, the lightweight ASGI framework that FastAPI is built on. NVD’s description: “Prior to version 1.0.1, the HTTP Host request header was not validated before being used to reconstruct request.url. Because the routing algorithm relies on the raw HTTP path while request.url is rebuilt from the Host header, a malformed header could make request.url.path differ from the path that was actually requested. Middleware and endpoints that apply security restrictions based on request.url (rather than the raw scope path) could therefore be bypassed.”

    The researchers call it BadHost. X41 D-Sec found it in January 2026 during a source-code audit of vLLM that OSTIF managed and the Alpha-Omega Project sponsored; the downstream notification work was sponsored in part by Amazon Web Services. X41’s advisory X41-2026-002 gives the timeline: identified January 27, 2026; proof of concept built and vendor notified February 4; patch released May 21; advisory published May 22.

    The mechanism, in X41’s words, is that Starlette builds a URL as "{scheme}://{host_header}{path}" without rejecting characters the Host header is not allowed to contain. Send Host: example.com/abc?bar= against a request for /foo, and the reconstructed URL parses as path /abc with the real path swallowed into the query string. Routing still dispatches to /foo, because routing uses the raw scope path. Any middleware that made its decision from request.url.path made it about a different request than the one that ran.

    Affected versions are 0.8.3 through 1.0.0. Version 1.0.1 validates the Host header against the grammar of RFC 9112 §3.2 and RFC 3986 §3.2.2 and falls back to scope["server"] for malformed values.

    Why it matters

    Start with the score, because it is the part that will cause the most trouble. Three separate entries on the NVD record — GitHub’s advisory database as Secondary, [email protected] as Primary, and a third secondary source — all land on 6.5 medium, on the identical vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N. Low confidentiality impact, low integrity impact, no availability impact, unchanged scope. X41, the only party that had built a working proof of concept, rated it High at 7.0.

    The scorers are not wrong about Starlette. A Host header parsing inconsistency, considered as a property of a framework, really does have low direct impact; the framework itself does not lose anything. The impact belongs entirely to whatever the application built on top of it decided to protect with that value. CVSS scores the component. Exploitation happens to the deployment. This CVE is a clean demonstration that those are different questions, and that a 6.5 is not a statement that nobody is being attacked through it.

    Any organization whose vulnerability program suppresses medium-severity findings in transitive Python dependencies has been correctly following its own policy and is now on a federal deadline anyway.

    Then there is the classification. CISA’s catalog title says HTTP request/response smuggling, which follows the record’s CWE-444. The record also carries CWE-1289, improper validation of unsafe equivalence in input, which is much closer to what happened. X41’s own advisory classifies it as CWE-436, interpretation conflict. Three classifications, three genuinely different mental models: a smuggling bug is about a proxy and an origin disagreeing on where a request ends; an interpretation conflict is about two parsers reading the same bytes differently; an equivalence bug is about a check treating unequal things as equal. The last two describe this. The first is the one that will appear in most dashboards.

    This matters for the same reason it mattered on the Kestra entry CISA added the same day: the catalog is increasingly an input to automated triage rather than a page a human reads. An engineer told to check for request smuggling looks at load balancers, reverse proxies, and CDN configuration. Nothing in that search finds a Python web framework three levels down a requirements file.

    Which raises the harder problem. Starlette is not a product. It is not in anyone’s asset inventory, it has no license entry, and almost nobody installed it deliberately — it arrives as a dependency of FastAPI, and FastAPI arrives as a dependency of whatever the team actually chose. The KEV entry identifies the vendor as “Kludex,” which is the GitHub username of the framework’s maintainer. That is CISA doing the only sensible thing available, and it is also a fair picture of where federal remediation deadlines now point: at a single-maintainer open-source library, named by handle.

    The blast radius is visible in the record’s own reference list. Red Hat alone has issued more than twenty separate RHSA errata for this one CVE, which is a reasonable proxy for how many distinct shipped products contain the library. OSTIF’s disclosure names the downstream projects the researchers checked: FastAPI, LiteLLM, vLLM, text generation inference projects, OpenAI shim proxies, MCP servers, and agent harnesses. Their summary is that it “hits very large and prominent projects.” They did not publish a count, and we are not going to invent one.

    That list is worth reading twice, because it is almost entirely AI-serving infrastructure — which is not a coincidence. The bug was found during an audit of vLLM. And it is not the only AI-stack entry in the September 2 batch: CISA added LiteLLM’s CVE-2026-59822 the same day, on the same September 16 deadline, and LiteLLM appears on the BadHost list of affected downstreams. Both records cite the same piece of research, Wiz’s AI infrastructure honeypot writeup. The pattern in this batch is not one framework. It is that the layer organizations stood up over the past two years to serve models has become a place attackers now look first, and that layer is Python web plumbing with a lot of hand-rolled path-based authorization in front of it.

    What to do

    Resolve Starlette’s version, not FastAPI’s. pip show starlette, or check the lockfile — anything from 0.8.3 through 1.0.0 is affected, and 1.0.1 or later is fixed. Pinning FastAPI without repinning the transitive dependency does nothing.

    If you cannot upgrade immediately, the code-level mitigation is to stop deriving authorization from the reconstructed URL. Middleware that reads request.url.path should read request.scope["path"] instead, which is the raw path the ASGI server received and the same value routing uses. A Host allowlist at the proxy, or Starlette’s own TrustedHostMiddleware, also cuts off the injection, and is the faster change in most deployments.

    Search your own code for the pattern rather than only checking versions. Any place that makes a security decision from request.url, str(request.url), or request.url.path — path prefix checks for admin routes, internal-only route guards, tenant scoping — is the vulnerable shape, and it stays a fragile shape after the upgrade.

    Federal agencies have until September 16, 2026. The required action includes CISA’s forensic triage clause and a discontinue-use provision where mitigations are unavailable, so the deliverable is not only a dependency bump. For AI gateways and model-serving endpoints specifically, check access logs for Host headers containing /, ?, or #; those characters are not legal in a Host header, so any request carrying them was malformed on purpose.

    Sourcing note

    KEV dates, the catalog vulnerability name, the required action text, the description, the CWE assignments, the three CVSS metric entries with their source identifiers, vulnStatus, and the reference list all come from NVD’s API record for CVE-2026-48710, which republishes CISA’s fields verbatim. The disclosure timeline, the 7.0 High rating, the CWE-436 classification, and the Host header example come from X41 D-Sec’s advisory X41-2026-002. The audit sponsorship and the list of affected downstream projects come from OSTIF’s own disclosure post.

    CISA’s alert page and KEV catalog feed both return HTTP 403 to automated requests, so the catalog was reached through NVD rather than directly. Note that NVD’s record for this CVE was still marked “Undergoing Analysis” with a lastModified of September 2, 2026 at 6:19 p.m. UTC when we retrieved it, roughly a day and a half after the addition.

    The Red Hat errata count is a count of URLs on NVD’s reference list for this CVE, not a Red Hat statement about product coverage. We did not independently verify exploitation; the exploitation determination is CISA’s, and CISA does not publish its evidence in the catalog. Unresolved: what exploitation CISA observed and against which downstream project, and whether the researchers’ 7.0 or the three concurring 6.5 scores better reflects the bug as deployed.