← back

vulnerability writeup

The field two security patches missed: leaking Google credentials through Weaviate's apiEndpoint

Security fixes have a scope, and the scope is usually a name. You harden baseURL. You audit every place baseURL is read. You write a test for baseURL. You ship. And if one module in the codebase calls the same concept apiEndpoint instead, your fix has a hole exactly the shape of that module.

This is what happened in Weaviate. It had shipped two rounds of hardening against SSRF through user-controlled upstream hosts. Both were real, well-scoped improvements. Both were built around a field called baseURL. The Google-backed modules used a field called apiEndpoint, and through it, a user with nothing more than read access could receive the operator’s Google API key, or a live GCP OAuth token, delivered to a server of their choosing.

How Weaviate talks to Google

Weaviate is a vector database. To turn text or images into vectors, or to generate text from retrieved context, it calls out to external model APIs through modules. Three of those modules are backed by Google: text2vec-google, multi2vec-google, and generative-google.

When one of those modules makes a request to Google, it authenticates. By default it attaches the operator’s Google API key as a bearer credential. If the operator has set USE_GOOGLE_AUTH=true, the module instead obtains a GCP OAuth token with cloud-platform scope and attaches that. Cloud-platform scope is the broad one. A token with that scope is not a key to one API. It is a key to the project.

The module needs to know where to send the request. That is the apiEndpoint field. Under normal circumstances it points at a Google API host. Under the circumstances I found, it could point anywhere.

The two patches that came first

Weaviate had already been working on this class of problem. Most of its modules expose a field called baseURL for overriding the upstream host, which is useful for proxies and regional endpoints and self-hosted compatible services. It is also a textbook SSRF vector if left unvalidated.

PR #10878, merged 2026-03-27, introduced validation of baseURL values, opt-in behind the environment variable MODULES_VALIDATE_BASE_URL.

PR #11683, merged 2026-06-18, closed a bypass where the same override could be supplied through request headers, and touched 21 URL builders across the module layer.

Two passes. Both correct for what they targeted. Neither touched the Google modules’ apiEndpoint, because apiEndpoint is not baseURL, and the hardening was structured around the name.

The finding

With apiEndpoint unvalidated, the attack is straightforward. Set it to a host you control. Trigger a request. Read the Authorization header when it arrives. What arrives is the operator’s Google API key, or under USE_GOOGLE_AUTH=true, a live cloud-platform-scoped OAuth token.

There were two ways to set the field. The first is through the class schema configuration, which requires schema-write access. Serious, but bounded. The second is what made this a high-severity report: the Google modules also accept apiEndpoint as a query-time parameter through GraphQL. A user who can run a query can set it for that query. No schema change. No write permission. Ordinary read access, the kind you give to any application that searches the database, is enough to redirect the credential-bearing request to an attacker’s host.

That second path collapses the privilege requirement to almost nothing. If your Weaviate instance accepts queries from users, and your Google modules are configured, those users could exfiltrate the credential that your instance uses to talk to Google.

Why the earlier fixes structurally could not catch it

I want to be precise here because it is the interesting part. This was not a case of Weaviate forgetting to enable validation, or a config flag being off. PR #10878 and PR #11683 were built to validate baseURL. The Google modules had no baseURL. They had apiEndpoint, plus region and location fields that also participate in constructing the request target. The validation logic had nothing to attach to. It did not fail. It was never in the path.

This is a pattern worth naming in your own codebases. When a security control is keyed to an identifier, every place that expresses the same concept under a different identifier is uncovered, and no amount of testing the identifier will find it. The audit question is not “did we validate baseURL?” It is “what are all the fields that determine where an outbound request goes, and did we validate each of them?”

Disclosure and fix

I reported the issue to Weaviate through HackerOne. Weaviate Security confirmed it.

The fix is weaviate/weaviate PR #12961, merged 2026-09-07 into stable/v1.37. The title tells you the fix did what the earlier patches structurally could not: it went to the Google modules specifically, identified all three fields that shape the request target (apiEndpoint, region, location), and constrained them to Google API hosts. No more arbitrary destinations for a request carrying a Google credential.

Weaviate credited me for the report.

The broader lesson

I found this while looking at MCP-adjacent infrastructure for a pattern I had already seen in Google’s MCP Toolbox and in Anthropic’s and Microsoft’s MCP servers: a user-influenced URL crossing a trust boundary with nobody checking. Weaviate is not an MCP server, but it is exactly the kind of component that sits behind one, and the shape of the bug was identical.

What makes the Weaviate case distinct is the credential. In a typical SSRF, the payoff is reaching an internal endpoint. Here, the payoff was the request itself. Weaviate did not need to reach anything sensitive. It just needed to send its normal, authenticated request to the wrong host, and the credential walked out with it. When your service attaches a bearer token to outbound requests, every field that controls the destination of those requests is a credential-exfiltration vector, and it needs to be treated with that level of care regardless of what it is called.

Timeline

  1. 2026-03-27weaviate/weaviate PR #10878 (baseURL validation, opt-in via MODULES_VALIDATE_BASE_URL) merged
  2. 2026-06-18weaviate/weaviate PR #11683 (X-*-BaseURL header validation, 21 URL builders) merged
  3. Report submitted via HackerOne; confirmed by Weaviate Security
  4. 2026-09-07weaviate/weaviate PR #12961 (Google module apiEndpoint/region/location restriction) merged into stable/v1.37

References

About me

I'm Syed Anas Mohiuddin, an AI security researcher based in New York. I built mcp-safeguard, an open-source scanner that checks MCP server code and tool descriptions for exactly this kind of gap. See all my disclosures and papers: publications. More about my work: syedanas01.github.io.