Skip to content

fix(search): harden resolveBaseUrl against SSRF via provider_options.baseUrl (#3049) - #3050

Closed
zmf963 wants to merge 2 commits into
decolua:masterfrom
HotSec:fix/search-ssrf-baseurl
Closed

zmf963 wants to merge 2 commits into
decolua:masterfrom
HotSec:fix/search-ssrf-baseurl

Conversation

@zmf963

@zmf963 zmf963 commented Aug 5, 2026

Copy link
Copy Markdown

Summary

Fixes SSRF reported in #3049: client-supplied provider_options.baseUrl on /v1/search could override the SearXNG provider's server-side request target with no SSRF protection, allowing any API-key holder to make the server fetch internal/private addresses.

Vulnerability

// open-sse/handlers/search/callers.js (before)
export function resolveBaseUrl(config, params) {
  const override = getProviderSetting(params, "baseUrl");   // client-controlled
  return (override || config.baseUrl).replace(/\/+$/, "");
}

The resulting URL is fetched server-side (open-sse/handlers/search/index.js:103) with no SSRF guard → internal port scanning, cloud metadata access (169.254.169.254), local service probing. Verified end-to-end.

Fix

Validate client-supplied baseUrl overrides in resolveBaseUrl():

  • reject non-http(s) protocols (file://, gopher://, ...)
  • reject internal/private/loopback/metadata hosts via the existing assertPublicUrl() guard (src/shared/utils/ssrfGuard.js)

Provider-configured default baseUrl remains trusted (admin-controlled), so existing deployments are unaffected.

Verification

  • 8 unit tests added (tests/unit/search-ssrf-guard.test.js): public overrides pass, loopback/private/metadata/non-http all rejected
  • End-to-end: SSRF attempt now returns 400 {"error":{"message":"Blocked URL: private IP"}} and the internal target receives no request; public https://searxng.example.com override still passes the guard (502 only because network is unreachable)

Notes

CVE-2026-55641 (fixed 0.5.2) covered the Host-header auth bypass but did not remove the client-controlled baseUrl override — this is the residual SSRF.

zmf added 2 commits August 5, 2026 21:16
…baseUrl

Client-supplied provider_options.baseUrl could override the SearXNG
search provider's server-side request target with no SSRF protection,
allowing any API-key holder to make the server fetch internal/private
addresses (port scanning, cloud metadata 169.254.169.254, local services).

Fix: validate client-supplied baseUrl overrides in resolveBaseUrl():
- reject non-http(s) protocols
- reject internal/private/loopback/metadata hosts via assertPublicUrl()

Provider-configured default baseUrl remains trusted (admin-controlled).
Verified end-to-end: internal target no longer receives the request
(400 'Blocked URL: private IP'), public overrides still pass through.

Refs: decolua#3049
src/lib/oauth/utils/ui.js imports chalk and dashboard provider pages
import prop-types, but neither was declared in package.json — builds
fail with 'Module not found' on a fresh install. Add both to
dependencies.
@zmf963

zmf963 commented Aug 5, 2026

Copy link
Copy Markdown
Author

Hi @decolua — this PR fixes the SSRF reported in #3049 and is ready to merge:

  • Commit 1 (d05da3dc): SSRF fix — resolveBaseUrl() now rejects client-supplied provider_options.baseUrl that targets internal/private/loopback/metadata hosts (via the existing assertPublicUrl() guard) or non-http(s) protocols. 8 unit tests added.
  • Commit 2 (4c33d101): dependency fix — declares chalk and prop-types (imported directly in src/lib/oauth/utils/ui.js and dashboard provider pages but missing from package.json, causing build failures on fresh installs).

Verification:

  • yarn run build ✅ (Compiled successfully)
  • npx vitest run tests/unit/search-ssrf-guard.test.js ✅ (8/8)
  • End-to-end: SSRF attempt now returns 400 Blocked URL: private IP, internal target receives no request; public baseUrl overrides still pass through.

Both commits are on HotSec:fix/search-ssrf-baseurl. Happy to adjust anything.

@zmf963

zmf963 commented Aug 6, 2026

Copy link
Copy Markdown
Author

Superseded by a consolidated security hardening PR — combining this with the auth fixes from #3060 into a single, rebased PR for easier review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant