mihomo-party-org/clash-party · error · GatewayError
transient
transient
Error message
endpoint escaped gateway origin
What it means
urlOf() builds the request URL by resolving a gateway endpoint against the gateway base URL and then verifies the resulting origin still matches the gateway's origin (the 'second line of defense'). If an endpoint value — often supplied by the server or a redirect — escapes to a different host (via backslash tricks, '@' userinfo, or encoding), this GatewayError('transient', ...) is thrown to block SSRF-style endpoint escape.
Source
Thrown at src/main/resolve/plugin/gateway.ts:43
constructor(kind: GatewayErrorKind, message: string, status?: number) {
super(message)
this.name = 'GatewayError'
this.kind = kind
this.status = status
}
}
interface RawResult {
status: number
json: Record<string, unknown> | undefined
text: string
}
function urlOf(t: GatewayTarget, ep: keyof IGatewayEndpoints): string {
const u = new URL(t.endpoints[ep], t.gateway)
// 第二道防线:拼出的 URL 必须仍落在网关 origin 上(防端点逃逸到其它 host,如反斜杠/编码技巧)。
if (u.origin !== new URL(t.gateway).origin) {
throw new GatewayError('transient', 'endpoint escaped gateway origin')
}
return u.toString()
}
function lookupFor(net: GatewayNet): LookupFunction | undefined {
return net.proxy ? undefined : (net.lookup ?? createGuardedLookup())
}
// DNS 解析失败 / 连接拒绝 / TLS 失败 → 缓存网关“不可达/已退役”信号(spec §5),交由编排层重新发现。
// 超时('Request timed out' / ETIMEDOUT)、5xx、429、SSRF/重定向/大小拦截仍按瞬时失败退避,不在此列。
const UNREACHABLE_CODES = new Set([
'ENOTFOUND',
'EAI_AGAIN',
'ECONNREFUSED',
'ECONNRESET',
'EHOSTUNREACH',
'ENETUNREACH',
'EHOSTDOWN',View on GitHub (pinned to 911e090537)
Solutions
- Fix the endpoint entry in your gateway target config to be a relative path (e.g. '/v1/config').
- If the gateway itself supplies endpoints, report/verify the gateway — a different origin means it is misbehaving or compromised.
- This is classified 'transient'; retrying may help only if the gateway has been fixed, otherwise the endpoint config must change.
Example fix
// before
endpoints: { config: 'https://other-host.example/config' }
// after
endpoints: { config: '/config' } // relative, stays on gateway origin Defensive patterns
Strategy: try-catch
Validate before calling
function endpointsStayOnOrigin(t: GatewayTarget): boolean {
const origin = new URL(t.gateway).origin
return Object.values(t.endpoints).every(
(ep) => { try { return new URL(ep, t.gateway).origin === origin } catch { return false } }
)
} Try / catch
try {
const url = urlOf(target, 'config')
} catch (e) {
if (e instanceof GatewayError && e.code === 'transient' && e.message === 'endpoint escaped gateway origin') {
// reject the target config / quarantine the gateway — do not follow the escaped URL
} else throw e
} Prevention
- Keep endpoint values relative paths in config; never absolute URLs to other hosts.
- Treat endpoint escape as a security signal: investigate the gateway, don't just retry.
- Re-validate endpoints whenever they come from server-supplied discovery data.
When it happens
Trigger: A GatewayTarget.endpoints entry contains a value that, when fed to new URL(ep, gateway), resolves to a different origin — e.g. an absolute URL like 'https://evil.example/x', a protocol-relative '//evil.example/', or 'https://gw.example\@evil.example'.
Common situations: A misconfigured or compromised gateway returning hostile endpoint URLs, or a local config with a typo'd absolute endpoint string instead of a relative path.
Related errors
- Plugin URL must use a public host
- Plugin URL must use https
- Plugin URL must not contain userinfo
- Invalid core path: directory traversal detected
- unreachable
AI-assisted analysis of mihomo-party-org/clash-party@911e090537 (2026-08-30).
Data as JSON: /api/errors/b206423124d4a681.
Report an issue: GitHub.