Repository navigation
[Bug]: fake-IP allowance unreachable for IPv6-mapped answers — classifyIpv6 hex regex misses the explicit zero group (::ffff:0:c612:1b) #2810
Description
Client or integration
Codex App
Area
Proxy and routing
Summary
On machines using Clash / Surge / Mihomo fake-IP DNS, provider model discovery still fails with blocked by destination policy: ... resolves to non-global address, even though #1748 added the fake-IP allowance.
The root cause is that the fake-IP opt-in in resolvePublicAddresses() is unreachable for IPv6-mapped answers. classifyIpv6() decodes hex IPv4-mapped IPv6 with:
const hexMapped = hostname.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);That pattern assumes exactly two hex groups (::ffff:c612:1b). But the resolver here returns an explicit zero group:
cli-chat-proxy.grok.com has address 198.18.0.27
cli-chat-proxy.grok.com has IPv6 address ::ffff:0:c612:1b
::ffff:0:c612:1b does not match, so it falls through to the generic tail:
return { kind: "private", detail: "non-global address" };It is therefore classified private with detail "non-global address" — not "benchmark address". The allowance in resolvePublicAddresses() is gated on that exact detail string:
if (benchmarkAllowed && assessment?.kind === "private" && assessment.detail === "benchmark address") {so the branch never runs, and the request is rejected regardless of HTTP(S)_PROXY being correctly configured. Node's ipaddress-style parsers agree the address is not a canonical ipv4_mapped value, which is why it reaches the hex path at all.
Security note: this same gap means loopback/private/link-local addresses in that zero-group form also skip IPv4 classification today. See the table below — fixing the regex tightens the policy rather than loosening it.
Reproduction
- Run Clash / Surge / Mihomo with fake-IP DNS enabled (
198.18.0.0/15). - Export a working proxy, e.g.
export HTTPS_PROXY=http://127.0.0.1:1082. - Configure any provider whose hostname resolves through fake-IP (reproduced with
xai, but it affects every provider on this machine). ocx restartthenocx provider test xai.
Observed:
xai: failed
upstream /models blocked by destination policy: provider URL hostname cli-chat-proxy.grok.com resolves to non-global address (::ffff:0:c612:1b)
Expected: discovery succeeds by riding the configured proxy as an ordinary hostname CONNECT, per #1748.
Note the destination is reachable — the block is purely the classification:
curl -x http://127.0.0.1:1082 https://cli-chat-proxy.grok.com/v1/models -> 401 (expected unauthenticated response)
Inference through the proxy also works fine; only discovery is blocked, so the failure is masked by the configured-model fallback.
Suggested fix
Make the zero group optional in src/lib/destination-policy.ts:
- const hexMapped = hostname.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
+ const hexMapped = hostname.match(/^::ffff:(?:0{1,4}:)?([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);Verified locally against v2.33.0. Decoding behaviour before/after:
| Input | old | new | decoded | intent |
|---|---|---|---|---|
::ffff:0:c612:1b |
miss | hit | 198.18.0.27 | fake-IP → allow via proxy |
::ffff:c612:1b |
hit | hit | 198.18.0.27 | unchanged |
::ffff:0:7f00:1 |
miss | hit | 127.0.0.1 | now correctly blocked |
::ffff:0:c0a8:1 |
miss | hit | 192.168.0.1 | now correctly blocked |
::ffff:0:a9fe:a9fe |
miss | hit | 169.254.169.254 | now correctly blocked |
::ffff:0:0:c612:1b |
miss | miss | — | 4 groups still rejected |
After applying it, all seven configured providers recovered live discovery, and the seven blocked by destination policy warnings disappeared from ocx sync:
| Provider | before | after |
|---|---|---|
| xai | blocked | connected — 2 models |
| anthropic | blocked | connected — 10 models |
| kimi | blocked | connected — 4 models |
| deepseek | blocked | connected — 3 models |
| minimax-cn | blocked | connected — 8 models |
| zhipu-bigmodel-coding | blocked | connected — 10 models |
| google-antigravity | blocked | connected — 6 models |
Happy to send this as a PR with a regression test over the address matrix above if useful.
Version
2.33.0
Operating system
macOS 26.5.2 (arm64)
Provider and model
xai / grok-4.6 (reproduces on all configured providers)
Logs or error output
$ ocx sync
[opencodex] Provider model discovery for "minimax-cn" was blocked by destination policy: provider URL hostname api.minimaxi.com resolves to non-global address (::ffff:0:c612:24) [urlClass=provider-models, fallback=configured].
[opencodex] Provider model discovery for "xai" was blocked by destination policy: provider URL hostname cli-chat-proxy.grok.com resolves to non-global address (::ffff:0:c612:1b) [urlClass=provider-models, fallback=configured].
[opencodex] Provider model discovery for "google-antigravity" was blocked by destination policy: provider URL hostname daily-cloudcode-pa.googleapis.com resolves to non-global address (::ffff:0:c612:29) [urlClass=provider-models, fallback=configured].
[opencodex] Provider model discovery for "anthropic" was blocked by destination policy: provider URL hostname api.anthropic.com resolves to non-global address (::ffff:0:c612:28) [urlClass=provider-models, fallback=configured].
[opencodex] Provider model discovery for "zhipu-bigmodel-coding" was blocked by destination policy: provider URL hostname open.bigmodel.cn resolves to non-global address (::ffff:0:c612:26) [urlClass=provider-models, fallback=configured].
[opencodex] Provider model discovery for "deepseek" was blocked by destination policy: provider URL hostname api.deepseek.com resolves to non-global address (::ffff:0:c612:25) [urlClass=provider-models, fallback=configured].
[opencodex] Provider model discovery for "kimi" was blocked by destination policy: provider URL hostname api.kimi.com resolves to non-global address (::ffff:0:c612:27) [urlClass=provider-models, fallback=configured].
$ ocx sync # after the one-line fix
[opencodex] Provider outbound proxy mode preserves Bun proxy/NO_PROXY routing and validates the URL plus available local DNS results.
+ 18 models appended to Codex catalogChecks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
lidge-jun commentedon Aug 28, 2026
리뷰 · 우선순위 75 / 80
설명
이 이슈는 Clash / Surge / Mihomo fake-IP DNS를 쓰는 맥에서 provider
ocx sync/ocx provider test가 전부blocked by destination policy … resolves to non-global address로 죽는 버그다. #1748이 이미allowBenchmarkAddresses로 198.18.0.0/15(벤치마크·fake-IP) 답을 허용하는데, 그 허용 분기가detail === "benchmark address"에만 걸려 있다. 지금dev(8d9e286)의src/lib/destination-policy.tsclassifyIpv6헥스 IPv4-mapped 정규식은/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i라서::ffff:c612:1b는 맞지만, 리졸버가 돌려 주는::ffff:0:c612:1b(명시적 0 그룹)는 안 맞는다. 그러면 85–90행의classifyIpv4로 안 내려가고 108행kind: "private", detail: "non-global address"로 떨어져, 356행 근처 fake-IP 허용 조건(detail === "benchmark address")에 영원히 안 걸린다. 그래서 HTTPS_PROXY가 올바르고 curl -x로도 401까지 가는데도 discovery만 막힌다. 이슈 작성자가 적은 표대로, 정규식을/^::ffff:(?:0{1,4}:)?([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i로 고치면 fake-IP(198.18.x)는 다시 benchmark로 분류되고,::ffff:0:7f00:1/::ffff:0:c0a8:1/::ffff:0:a9fe:a9fe처럼 예전엔 놓치던 loopback·사설·메타데이터 래핑도 이제 제대로 막힌다. 즉 이 픽스는 허용을 느슨하게 하는 게 아니라 분류를 정확히 해서 보안을 더 단단히 한다.같은 파일·같은 함수를 건드리는 열린 PR #2798(NAT64
64:ff9b::/96임베디드 IPv4 디코드)은 접두사가 완전히 다른 축이다. #2798은ipv6Hextets확장기를 추가하지만 지금 diff는 이 헥스::ffff:정규식을 고치지 않는다. 그래서 #2810은 #2798과 중복이 아니라 옆집 버그다. 다만 메인테이너가ipv6Hextets로 IPv4-mapped 형태까지 통일 디코드하는 쪽이 더 깔끔하다고 보면, 한 줄 정규식 대신 #2798에 주소 행렬 테스트를 얹는 식으로 묶을 수는 있다. types.ts/config.ts 스플릿이나 preview 배포와는 무관하다. 재현·수정 검증·프로바이더 7개 복구 로그까지 이슈에 이미 있어서 PR만 받으면 된다.라인 85 (
classifyIpv6hexMapped 정규식) -::ffff:0:c612:1b처럼 명시적 0 그룹이 있는 IPv4-mapped를 놓쳐 108행 non-global로 떨어진다. 제안대로 optional(?:0{1,4}:)?를 넣거나 hextet 확장기 경로로 동일 디코드가 필요하다.라인 356 (
resolvePublicAddressesbenchmarkAllowed 분기) - 허용이detail === "benchmark address"에만 묶여 있어, 잘못 분류된 non-global fake-IP는 프록시가 있어도 절대 통과하지 못한다. 분류가 고쳐지면 이 분기는 그대로 써도 된다.경로
tests/destination-policy-resolved.test.ts(#1748 스위트) - 지금 케이스는 점표기/일반 벤치마크 위주라::ffff:0:c612:1b/::ffff:0:7f00:1행렬이 없다. 이슈 표의 6행을 회귀 테스트로 넣어야 다시 깨지지 않는다.경로 #2798 (
ipv6Hextets+ NAT64) - 같은classifyIpv6를 고치지만::ffff:0:…는 건드리지 않음. 병합 순서만 조심하면 충돌은 작다.경로 재현 - Clash fake-IP +
HTTPS_PROXY+ocx provider test xai(또는ocx sync)에서::ffff:0:c612:…로그가 뜨는지가 고정 재현 신호다.메인테이너의 판단이 필요한 지점
::ffff:0:…형태도 fake-IP 경로라는 한 줄을 남길지.너의 추천
버그로 유지하고 작성자 PR을 받아라. 최소 변경은 라인 85 정규식 +
tests/destination-policy-resolved.test.ts에 이슈 표 행렬( hit/miss + loopback/사설/메타데이터 차단)이다. #2798과 축이 다르니 막지 말고, 둘 다 머지하면 destination-policy의 IPv6 래핑 구멍이 한 번에 줄어든다. 우선순위 높은 proxy/SSRF 분류 버그다.이 댓글은 grok-bot이 작성했습니다
gaoran1209 commentedon Aug 28, 2026
Opened #2812 with the one-line regex fix plus the regression matrix from the report.
Verified locally:
tsc --noEmitclean,privacy:scanpassed, and 55 pass / 0 fail acrossdestination-policy-resolved,provider-outbound-private-network, andproxy-env. Reverting only the source file makes the 6 new assertions fail, so the tests genuinely guard the regression.On the maintainer decision points:
ipv6Hextets, I'm happy to rebase onto fix(security): classify NAT64-embedded IPv4 instead of refusing the wrapper #2798 and fold it in there instead; the address matrix carries over unchanged.resolvePublicAddresses(line ~356) alone, as your triage suggested — once classification is correct the existingbenchmark addressbranch works as-is.::ffff:0:…spelling if you want it documented there.One caveat: I could not run the full
bun run testsuite locally because the harness needs process/network permissions my environment denies, so I scoped verification to the files this change can affect and left the rest to CI.fix(security): decode IPv4-mapped IPv6 with an explicit zero group
fix(security): reach the fake-IP opt-in for explicit-zero mapped answers
fix(security): reach the fake-IP opt-in for explicit-zero mapped answ…
lidge-jun commentedon Aug 28, 2026
Fixed on
devby commit c986d1d (PR #2842).The
allowBenchmarkAddressesfake-IP opt-in now reaches the explicit-zero IPv4-mapped spelling, so a Clash/Surge/Mihomo resolver answering::ffff:0:c612:1bfor 198.18.0.27 is accepted instead of refused.The decode lives in the benchmark gate rather than in
classifyIpv6, deliberately:::ffff:0:<hi>:<lo>is a reserved address under RFC 4291 whose tail merely resembles an IPv4, so declaring it equivalent would have admitted things like::ffff:0:5db8:d822(tail 93.184.216.34) as ordinary public destinations. Both directions are pinned by tests.Verified with
tests/destination-policy-resolved.test.tsat 45 pass / 0 fail (9 new tests, driven RED first), plus an independent adversarial review of the SSRF boundary. Credit to @gaoran1209, who diagnosed this in #2812.This PR targeted
dev, so GitHub did not auto-close the issue — closing manually.fix(security): reach the fake-IP opt-in for explicit-zero mapped answ…
fix(security): reach the fake-IP opt-in for explicit-zero mapped answ…