fix(agents): broaden local-baseUrl detection to 127/8 and RFC 6598 CGNAT

This commit is contained in:
openperf
2026-05-03 02:03:04 +08:00
parent a740ff1b53
commit c97552a04c
2 changed files with 42 additions and 3 deletions

View File

@@ -89,16 +89,43 @@ describe("resolveLlmIdleTimeoutMs", () => {
it.each([
"http://localhost:11434",
"http://127.0.0.1:11434",
"http://127.0.0.2:11434",
"http://127.255.255.254:11434",
"http://0.0.0.0:11434",
"http://[::1]:11434",
"http://my-rig.local:11434",
"http://10.0.0.5:11434",
"http://172.16.5.10:11434",
"http://172.31.99.1:11434",
"http://192.168.1.20:11434",
"http://100.64.0.5:11434",
"http://100.127.255.254:11434",
])("disables the default idle watchdog for local provider baseUrl %s", (baseUrl) => {
expect(resolveLlmIdleTimeoutMs({ model: { baseUrl } })).toBe(0);
});
it.each([
"http://172.32.0.1:11434",
"http://192.169.1.1:11434",
"http://100.63.255.254:11434",
"http://100.128.0.1:11434",
])("keeps the default idle watchdog for non-private IPv4 baseUrl %s", (baseUrl) => {
expect(resolveLlmIdleTimeoutMs({ model: { baseUrl } })).toBe(DEFAULT_LLM_IDLE_TIMEOUT_MS);
});
it.each([
"http://10.0.0.5evil:11434",
"http://127.0.0.1foo:11434",
"http://192.168.1.20attacker.com:11434",
"http://10.0.0.5.evil.com:11434",
"http://1.2.3.4.5:11434",
])(
"keeps the default idle watchdog for numeric-looking hostnames that are not IPv4 literals (%s)",
(baseUrl) => {
expect(resolveLlmIdleTimeoutMs({ model: { baseUrl } })).toBe(DEFAULT_LLM_IDLE_TIMEOUT_MS);
},
);
it("keeps the default idle watchdog for remote provider baseUrls", () => {
expect(resolveLlmIdleTimeoutMs({ model: { baseUrl: "https://api.openai.com/v1" } })).toBe(
DEFAULT_LLM_IDLE_TIMEOUT_MS,

View File

@@ -33,7 +33,6 @@ function isLocalProviderBaseUrl(baseUrl: string): boolean {
}
if (
host === "localhost" ||
host === "127.0.0.1" ||
host === "0.0.0.0" ||
host === "::1" ||
host === "::ffff:7f00:1" ||
@@ -42,13 +41,26 @@ function isLocalProviderBaseUrl(baseUrl: string): boolean {
) {
return true;
}
// Require a strict IPv4 literal before parsing; `Number.parseInt` is
// permissive and would otherwise let `10.0.0.5evil` parse to [10,0,0,5]
// and disable the watchdog for a non-IP hostname.
if (!/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
return false;
}
const octets = host.split(".").map((part) => Number.parseInt(part, 10));
if (octets.length !== 4 || octets.some((p) => !Number.isInteger(p) || p < 0 || p > 255)) {
if (octets.some((p) => !Number.isInteger(p) || p < 0 || p > 255)) {
return false;
}
const [a, b] = octets;
// RFC 5735 loopback (127/8 — full range, not just .0.1; container/sandbox
// setups commonly bind 127.0.0.2+), RFC 1918 private IPv4, and RFC 6598
// shared CGNAT (100.64/10 — used by Tailscale and similar mesh VPNs).
return (
a === 10 || (a === 172 && b !== undefined && b >= 16 && b <= 31) || (a === 192 && b === 168)
a === 127 ||
a === 10 ||
(a === 172 && b !== undefined && b >= 16 && b <= 31) ||
(a === 192 && b === 168) ||
(a === 100 && b !== undefined && b >= 64 && b <= 127)
);
}