fix(viewer): prevent path-traversal SSRF in proxy (CWE-918)#898
fix(viewer): prevent path-traversal SSRF in proxy (CWE-918)#898sebastiondev wants to merge 1 commit into
Conversation
The viewer proxy in proxyToRestApi() forwards requests to the local REST API, auto-attaching the AGENTMEMORY_SECRET bearer token. A path like /agentmemory/../../admin passes the /agentmemory/ prefix check but gets normalized by fetch() URL parser to /admin, escaping the intended prefix while carrying the privileged bearer token. Add normalizeProxyPath() that uses new URL() to canonicalize the path (matching what fetch() will do internally) and then verifies the result still starts with /agentmemory/. Requests with traversal sequences (../, %2e%2e/, %2E%2E/) are rejected with 400 before reaching fetch(). Includes test suite covering literal, percent-encoded, and mixed-case traversal payloads, plus pass-through verification for legitimate paths. Signed-off-by: Sebastion <sebastion@sebastion.dev>
|
@sebastiondev is attempting to deploy a commit to the rohitg00's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds path normalization validation to prevent directory-traversal attacks ( ChangesPath-traversal defense for proxy requests
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
The viewer proxy in
src/viewer/server.tshas a path-traversal vulnerability (CWE-918) in theproxyToRestApi()function. An attacker with local HTTP access to the viewer port can escape the intended/agentmemory/path prefix and reach arbitrary upstream routes while the proxy auto-attaches theAGENTMEMORY_SECRETBearer token.This PR adds path normalization before the prefix check, closing the traversal gap.
Vulnerability Details
CWE: CWE-918 (Server-Side Request Forgery)
Affected file:
src/viewer/server.ts, functionproxyToRestApi()(line 417)Severity: Medium (exploitable under specific but realistic preconditions)
Data flow
req.urlat line 278.proxyToRestApi(), the pathname is checked withstartsWith("/agentmemory/")to decide routing./agentmemory/../../secret-routepasses this prefix check (it literally starts with/agentmemory/).fetch(), which internally normalizes..segments via its URL parser — resolving/agentmemory/../../secret-routeto/secret-route./secret-route(outside/agentmemory/) with the privileged Bearer token attached.The core issue is a TOCTOU mismatch: the prefix check operates on the raw path, but
fetch()normalizes it before making the request.Threat model
The vulnerability is exploitable when:
AGENTMEMORY_SECRETis set (REST API authentication is enabled)/agentmemory/In this configuration, the viewer intentionally acts as an unauthenticated bridge to the authenticated
/agentmemory/*API surface. The path traversal breaks that constraint by allowing requests to non-/agentmemory/routes with the auto-attached Bearer token.Before submitting, we verified that this isn't blocked by other defenses: the host-header allowlist and CORS restrictions prevent cross-origin attacks but don't prevent a local process from sending raw HTTP requests. The loopback bind prevents remote exploitation but doesn't protect against other processes on the same machine. In non-loopback (Fly) deployments, inbound Bearer is required, which makes the traversal redundant since the attacker would already know the secret.
Proof of Concept
Node.js's built-in HTTP server passes raw
..segments through inreq.urlwithout normalization — this was confirmed experimentally and is documented Node behavior.Fix Description
The fix adds a
normalizeProxyPath()function that:new URL(raw, "http://localhost")— this applies the same URL normalization thatfetch()uses internally, resolving all..segments, percent-encoded variants (%2e%2e,%2E%2E), and backslash tricks./agentmemory/.nullif the path escapes the prefix.This eliminates the TOCTOU gap: the prefix check and the actual upstream request now operate on the same normalized path. Invalid paths are rejected with HTTP 400 before reaching
fetch().Testing
A new test file
test/viewer-proxy-path.test.tscovers 7 scenarios:/agentmemory/../../admin%2e%2e/agentmemory/%2e%2e/%2e%2e/admin%2E%2E/agentmemory/%2E%2E/%2E%2E/admin/agentmemory/../other/agentmemory/foo/../../bar/agentmemory/livez/agentmemory/memories?latest=trueEach test spins up a recording upstream server and verifies both the HTTP status and that no request reached the upstream for blocked paths.
Files Changed
src/viewer/server.ts— AddednormalizeProxyPath()and integrated it intoproxyToRestApi()test/viewer-proxy-path.test.ts— New test file for path traversal defenseSubmitted by Sebastion — autonomous open-source security research from Foundation Machines. Free for public repos via the Sebastion AI GitHub App.
Summary by CodeRabbit