Files
OmniRoute/tests
diegosouzapw bd65c6d89e fix(search): refuse a tenant-supplied provider_options.baseUrl
GHSA-3f8g-pfh9-j687: a caller with search scope could set
`provider_options.baseUrl` and redirect the SaaS search builders, so the
OPERATOR's third-party API key went to a caller-chosen host — `key=`/`api_key=`
in the query for google-pse/searchapi, `X-API-Key`/`Authorization` for
you.com/linkup/nimble/ollama — and the parsed response came back, making the
same primitive an SSRF with readback.

My own GHSA-j7j4-g9qc-q69c fix is the one this slipped past. That fix added
`parseAndValidateNonMetadataUrl` to this exact resolver, which blocks IMDS and
nothing else: against key exfiltration it does nothing at all, because the
attacker just names their own public host. I treated a credential-destination
problem as an SSRF problem.

The j7j4 regression test missed it for a sharper reason worth recording: its
fixture was `searxng-search`, the one keyless provider where redirecting the base
URL leaks no credential. The test picked the benign case and passed.

The two override sources are not equally trusted, and reading them through one
`getProviderSettingString()` call erased the distinction:

  - `providerSpecificData` comes from the stored provider connection
    (`credentials?.providerSpecificData`) — operator config. This is how an
    operator points at a self-hosted searxng, so it keeps working on
    loopback/LAN under block-metadata, and IMDS stays rejected.
  - `providerOptions` is `body.provider_options` — caller input. Now refused
    outright rather than validated. No provider needs a per-request
    caller-chosen fetch target; the self-hosted story is served by the operator
    field. If one ever does, it needs a per-provider opt-in plus a host
    allow-list, not a validated free-form URL.

Operator config now also wins over a caller value instead of being shadowed by
it.

Behaviour change, deliberate: a caller that today sends
`provider_options.baseUrl` gets an explicit refusal instead of a silently
redirected request. Silently ignoring it would leave the caller believing the
override applied.

tests/unit/search-baseurl-client-override-3f8g.test.ts — 23 tests, red before
the fix. It iterates EVERY keyed provider in the registry instead of one
fixture, and asserts the registry still has keyed providers so the suite cannot
degrade to zero coverage.

search-baseurl-ssrf-guard.test.ts had one assertion encoding the wrong contract
(caller-supplied loopback allowed). Moved to the operator source with the
reasoning inline — the j7j4 metadata cases are untouched.

Reported by @skeletonsec.

Closes GHSA-3f8g-pfh9-j687
2026-09-03 12:52:59 -03:00
..
2026-08-26 14:25:01 -03:00