fix(ssrf): honor local-first provider URL flag in outbound guard (#9123)

This commit is contained in:
Markus Hartung
2026-08-21 19:52:54 -03:00
parent 3caa59107e
commit e6648ac960
3 changed files with 61 additions and 1 deletions

View File

@@ -0,0 +1 @@
- fix(ssrf): make `getProviderOutboundGuard()` (used for search-provider connection validation, image generation and remote image fetch) honor the local-first default `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` the same way the chat validation guard already does, so a LAN-hosted SearXNG/Brave search provider works with only the LOCAL flag set instead of silently requiring `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` ([#9123](https://github.com/diegosouzapw/OmniRoute/issues/9123)).

View File

@@ -56,8 +56,18 @@ export function arePrivateProviderUrlsAllowed() {
return false;
}
/**
* Guard mode for the provider OUTBOUND path (search-provider connection validation, image
* generation, remote image fetch, model discovery — anything that does not go through the
* chat validation path). Precedence — mirrors `getProviderValidationGuard()` (#9123):
* 1. explicit full opt-in (`arePrivateProviderUrlsAllowed`) → "none" (no checks; power users).
* 2. local-first default (`areLocalProviderUrlsAllowed`) → "block-metadata" (allow LAN, block IMDS).
* 3. otherwise → "public-only" (strict).
*/
export function getProviderOutboundGuard(): OutboundUrlGuardMode {
return arePrivateProviderUrlsAllowed() ? "none" : "public-only";
if (arePrivateProviderUrlsAllowed()) return "none";
if (areLocalProviderUrlsAllowed()) return "block-metadata";
return "public-only";
}
/**

View File

@@ -0,0 +1,49 @@
import { test } from "node:test";
import assert from "node:assert/strict";
const ENV_KEYS = [
"OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS",
"OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS",
"OUTBOUND_SSRF_GUARD_ENABLED",
];
function withEnv(overrides: Record<string, string | undefined>, fn: () => void) {
const saved: Record<string, string | undefined> = {};
for (const k of ENV_KEYS) saved[k] = process.env[k];
for (const k of ENV_KEYS) delete process.env[k];
Object.assign(process.env, overrides);
try {
fn();
} finally {
for (const k of ENV_KEYS) {
if (saved[k] === undefined) delete process.env[k];
else process.env[k] = saved[k];
}
}
}
test("#9123: setting ONLY OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS=true should relax the guard used for search-provider outbound calls (currently does not)", async () => {
const { areLocalProviderUrlsAllowed, getProviderOutboundGuard } = await import(
"../../src/shared/network/outboundUrlGuardPolicy.ts"
);
withEnv({ OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS: "true" }, () => {
assert.equal(areLocalProviderUrlsAllowed(), true);
assert.notEqual(
getProviderOutboundGuard(),
"public-only",
"BUG #9123: OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS should relax getProviderOutboundGuard() " +
"(used for search-provider outbound calls) the same way it already relaxes " +
"getProviderValidationGuard() for regular chat providers — it currently has no effect."
);
});
});
test("#9123 control: OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true DOES relax the same guard", async () => {
const { getProviderOutboundGuard } = await import(
"../../src/shared/network/outboundUrlGuardPolicy.ts"
);
withEnv({ OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS: "true" }, () => {
assert.equal(getProviderOutboundGuard(), "none");
});
});