From 08eed90be4b50bf768be9d72fce4876cadfc531f Mon Sep 17 00:00:00 2001 From: mschwab Date: Thu, 28 May 2026 15:13:52 -0700 Subject: [PATCH 1/2] fix(security): rebuild proxy URL from trusted endpoint to clear py/full-ssrf The previous py/partial-ssrf fix validated the joined URL's scheme and netloc against the trusted deployment endpoint, but CodeQL upgraded the finding to py/full-ssrf because the final target_url still flowed directly from urljoin(endpoint, user_path) into client.stream(url=...). Rebuild target_url from urlunparse() using the trusted endpoint's scheme and netloc, with only path/params/query carried over from the joined URL. The validation still raises 400 for mismatches; the rebuild step makes the host portion definitely come from the DB-loaded endpoint, breaking the full-URL taint flow. Also added an upfront sanity check that rejects empty scheme/netloc on the endpoint so a malformed deployment record can't degrade the guard. Signed-off-by: mschwab --- .../src/nemo_agents_plugin/api/v2/gateway.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py b/plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py index 211650ed7c..85415088f9 100644 --- a/plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py +++ b/plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py @@ -28,7 +28,7 @@ import logging import os from typing import AsyncIterator -from urllib.parse import urljoin, urlparse +from urllib.parse import urljoin, urlparse, urlunparse import httpx from fastapi import APIRouter, Depends, HTTPException, Request @@ -158,10 +158,16 @@ async def _proxy( transfer encoding makes the original value invalid. """ endpoint_parsed = urlparse(endpoint) - target_url = urljoin(endpoint.rstrip("/") + "/", trailing_uri) - target_parsed = urlparse(target_url) - if target_parsed.scheme != endpoint_parsed.scheme or target_parsed.netloc != endpoint_parsed.netloc: + if not endpoint_parsed.scheme or not endpoint_parsed.netloc: + raise HTTPException(status_code=500, detail="Deployment endpoint is misconfigured.") + joined = urlparse(urljoin(endpoint.rstrip("/") + "/", trailing_uri)) + if joined.scheme != endpoint_parsed.scheme or joined.netloc != endpoint_parsed.netloc: raise HTTPException(status_code=400, detail="Invalid proxy target URI.") + # Rebuild from the trusted endpoint's scheme+netloc so the final URL never + # depends directly on user-supplied path text for the host portion. + target_url = urlunparse( + (endpoint_parsed.scheme, endpoint_parsed.netloc, joined.path, joined.params, joined.query, "") + ) if request.url.query: target_url = f"{target_url}?{request.url.query}" From 3e47c3739ec7ac4cce76a041bc7c5b7845d21517 Mon Sep 17 00:00:00 2001 From: mschwab Date: Thu, 28 May 2026 15:24:33 -0700 Subject: [PATCH 2/2] chore: strip explanatory comment from gateway SSRF rebuild Signed-off-by: mschwab --- plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py b/plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py index 85415088f9..eb0c658ac7 100644 --- a/plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py +++ b/plugins/nemo-agents/src/nemo_agents_plugin/api/v2/gateway.py @@ -163,8 +163,6 @@ async def _proxy( joined = urlparse(urljoin(endpoint.rstrip("/") + "/", trailing_uri)) if joined.scheme != endpoint_parsed.scheme or joined.netloc != endpoint_parsed.netloc: raise HTTPException(status_code=400, detail="Invalid proxy target URI.") - # Rebuild from the trusted endpoint's scheme+netloc so the final URL never - # depends directly on user-supplied path text for the host portion. target_url = urlunparse( (endpoint_parsed.scheme, endpoint_parsed.netloc, joined.path, joined.params, joined.query, "") )