The Validator Trusted Exactly the Hosts It Should Have Feared: Portkey Gateway SSRF
NVD published CVE-2026-110719 against Portkey-AI/gateway on 14 October 2026 (16:17:44Z), sourced to disclosure@vulncheck.com and reported by hieuPenguinnn — the same researcher behind this week’s holaOS channel-gateway pair, and likewise with a full technical writeup. Portkey’s gateway is not a side project: 13,194 stars, 1,347 forks, MIT, the LLM proxy this site last mentioned in May when Palo Alto Networks moved to acquire it. Versions 1.14.0 through 1.15.2 carry a server-side request forgery flaw that lets unauthenticated callers reach loopback and Docker-host services — and the current release is still 1.15.2, so there is no fixed version to upgrade to.
- CVE-2026-110719 (CVSS 4.0 6.9, 3.1 7.2, AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N, CWE-918,
gateway1.14.0 through 1.15.2). The gateway accepts a user-controlledx-portkey-custom-hostheader and uses it verbatim as the outbound base URL for provider requests. WhenTRUSTED_CUSTOM_HOSTSis unset — the default — the validator trustslocalhost,127.0.0.1,::1,host.docker.internal, and any*.localhosthost, returning true before its private-IP and metadata blocks run. The/v1/*routes require no admin authentication and the Ollama provider needs no API key, so an unauthenticated caller can make the gateway issue requests to internal services and relay their responses back.
A short-circuit in front of a good blocklist
The uncomfortable detail is that the validator is otherwise thorough. Per the researcher’s writeup, it blocks private IPv4/IPv6, reserved ranges, cloud metadata (169.254.169.x), blocked hosts and TLDs, and alternative IP encodings — but only for hosts that are not in the trusted set. We read src/middlewares/requestValidator/index.ts first-hand on main, and both halves are still there. The default set:
const TRUSTED_CUSTOM_HOSTS = (c?: Context) => {
const envVar = Environment(c)?.TRUSTED_CUSTOM_HOSTS;
if (!envVar) {
// Default allowed hosts for local development
return new Set(['localhost', '127.0.0.1', '::1', 'host.docker.internal']);
}
And the short-circuit:
const isTrustedHost =
trustedHosts.has(host) ||
// Allow subdomains of .localhost
(trustedHosts.has('localhost') && host.endsWith('.localhost'));
if (isTrustedHost) {
// Still validate port range if provided
if (url.port && !isValidPort(url.port)) return false;
return true;
}
Everything below that return true — the private-range checks, the metadata blocks, the encoding normalisation — never executes for exactly the destinations an SSRF guard exists to constrain. The comment says “for local development”, but the code path is the production request validator on the unauthenticated /v1/* routes, and nothing in the default deployment sets the environment variable that would replace the set. A development convenience became the production trust policy the moment it shipped as the default.
The exploit chain needs no credentials at any step, which is why PR:N and UI:N both hold. The request context prefers the request’s custom-host header when building the provider URL, the provider context concatenates it as the base URL, and the retry handler fetches the result — with the response relayed to the caller, making this a read-capable SSRF rather than a blind one. The Ollama provider is the key that turns the lock: its headers function returns nothing, its base URL comes from the caller-supplied custom host, and the config schema permits it without an API key. The researcher verified the chain against commit 669825cbe8 (25 May 2026) with source review on 13 October 2026. One scorekeeping note: the writeup’s badge proposes CVSS 3.1 8.6, while the assigned record carries 7.2 (3.1) and 6.9 (4.0) — the reporter scored the gateway’s position in front of internal services more aggressively than the CNA did. We record the assigned scores, not the proposal.
No fixed release; the mitigation is one environment variable
The repository’s latest release is v1.15.2 (12 January 2026), inside the affected range, and the validator file’s most recent commits (March 2026) are formatting and header-loop hardening — nothing touching the trusted-host logic. The short-circuit above was read on current main at time of writing. Until upstream ships a fix, the compensating control is visible in the first listing: set the TRUSTED_CUSTOM_HOSTS environment variable explicitly. Doing so replaces the loopback-permissive default set with only the hosts you name — but note the semantics carefully. Any host you list is fully exempted from the private-IP and metadata blocks, so list nothing internal and nothing wildcard-shaped. An empty-or-unset variable is the vulnerable state; there is no safe default to fall back to.
There is a lineage worth naming. Two days ago this site covered Check Point’s LiteLLM gateway chains — one of which is an SSRF in a credentials feature that redirects provider keys. Portkey is the same component class (the AI gateway every model call flows through) failing at the same boundary (which outbound destinations a caller may steer), via the inverse mistake: LiteLLM failed to validate where credentials go; Portkey validated everywhere except the hosts it pre-trusted. Gateway SSRF keeps recurring because the gateway’s job — accepting a destination-influencing request and issuing a server-side fetch — is the SSRF primitive. Every header, provider option, or config field that can steer the outbound URL needs the same destination validation, including the ones labelled development-only.
What to do
- Set
TRUSTED_CUSTOM_HOSTSexplicitly on every deployment, now. Unset is the vulnerable state. List only the exact public hosts your providers need — never loopback, neverhost.docker.internal, never*.localhost— because listed hosts skip all SSRF blocks. - Do not expose the gateway’s
/v1/*surface to untrusted callers without an auth layer in front of it. The routes carry no admin authentication by design in this design era; the custom-host header is one more reason the gateway must sit behind authentication, not serve as it. - Review gateway and sidecar logs for
x-portkey-custom-hostvalues pointing at loopback,host.docker.internal,*.localhost, or metadata addresses. The SSRF is read-capable with responses relayed, so historic values are evidence of what internal services answered, not just what was attempted. - Watch for the upstream fix and upgrade past 1.15.2 when it lands. The correct fix is fail-closed: no trusted-host bypass by default, or the trusted set consulted after — never before — the private/metadata blocks.
Verification note. The CVE identifier, NVD publication timestamp (14 October 2026, 16:17:44Z), CVSS 4.0 and 3.1 scores and vectors, CWE-918, affected-version range, vulnStatus Received, and the VulnCheck source identifier were read first-hand from the NVD 2.0 API. Advisory date, affected versions, reporter credit, and reference links were read first-hand from the VulnCheck advisory page; the route table, validator logic, header-to-URL data flow, Ollama provider details, verification commit and review date from the reporter’s HackMD writeup read first-hand. The TRUSTED_CUSTOM_HOSTS default set and the isTrustedHost short-circuit were read first-hand in current src/middlewares/requestValidator/index.ts on main; the latest release tag and date (v1.15.2, 12 January 2026), the validator commit history, and repository stars, forks, and license from the GitHub API. Brave Search corroboration found only CVE-mirror pages and no independent analysis. Nothing was installed, no gateway was deployed or contacted, no header was crafted, and no third-party request was sent; remediation guidance is our editorial analysis. We have no evidence of exploitation and imply none.
Sources:
- NVD — CVE-2026-110719, SSRF via x-portkey-custom-host header (6.9 / 7.2, CWE-918, 1.14.0 through 1.15.2)
- VulnCheck advisory — Portkey AI Gateway custom-host SSRF (CVE-2026-110719, 14 October 2026)
- Portkey-AI/gateway
src/middlewares/requestValidator/index.tsatca77129— validator referenced by the advisory - Portkey-AI/gateway releases — latest remains v1.15.2 (12 January 2026), inside the affected range