Skip to content

Commit 298f34f

Browse files
committed
fix: validate bracketed IPv6 runtime hosts
1 parent 1b0af6e commit 298f34f

6 files changed

Lines changed: 39 additions & 2 deletions

File tree

go/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"fmt"
3737
"log"
3838
"net"
39+
"net/netip"
3940
"os"
4041
"os/exec"
4142
"regexp"
@@ -386,6 +387,10 @@ func parseCLIURL(url string) (string, int) {
386387
if err != nil {
387388
panic(fmt.Sprintf("Invalid port in URIConnection: %s", url))
388389
}
390+
addr, err := netip.ParseAddr(host)
391+
if err != nil || !addr.Is6() {
392+
panic(fmt.Sprintf("Invalid URIConnection format: %s", url))
393+
}
389394
port, err := strconv.Atoi(portStr)
390395
if err != nil || port <= 0 || port > 65535 {
391396
panic(fmt.Sprintf("Invalid port in URIConnection: %s", url))

go/client_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ func TestClient_URLParsing(t *testing.T) {
7676
}
7777
})
7878

79+
t.Run("should panic for bracketed non-IPv6 host", func(t *testing.T) {
80+
defer func() {
81+
if r := recover(); r == nil {
82+
t.Error("Expected panic for invalid bracketed host")
83+
}
84+
}()
85+
NewClient(&ClientOptions{Connection: URIConnection{URL: "[not-ipv6]:1234"}})
86+
})
87+
7988
t.Run("should parse https://host:port URL format", func(t *testing.T) {
8089
client := NewClient(&ClientOptions{
8190
Connection: URIConnection{URL: "https://example.com:443"},

nodejs/src/client.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { spawn, type ChildProcess } from "node:child_process";
1515
import { randomUUID } from "node:crypto";
1616
import { existsSync } from "node:fs";
1717
import { createRequire } from "node:module";
18-
import { Socket } from "node:net";
18+
import { isIPv6, Socket } from "node:net";
1919
import { dirname, join } from "node:path";
2020
import { fileURLToPath } from "node:url";
2121
import {
@@ -780,11 +780,16 @@ export class CopilotClient {
780780
// the existing parser behavior for other inputs.
781781
const ipv6Match = cleanUrl.match(/^\[([^\]]+)\]:(\d+)$/);
782782
if (ipv6Match) {
783+
const host = ipv6Match[1];
784+
if (!isIPv6(host)) {
785+
throw new Error(`Invalid cliUrl format: ${url}`);
786+
}
787+
783788
const port = parseInt(ipv6Match[2], 10);
784789
if (isNaN(port) || port <= 0 || port > 65535) {
785790
throw new Error(`Invalid port in cliUrl: ${url}`);
786791
}
787-
return { host: ipv6Match[1], port };
792+
return { host, port };
788793
}
789794

790795
// Parse host:port format

nodejs/test/client.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,6 +2199,15 @@ describe("CopilotClient", () => {
21992199
expect((client as any).isExternalServer).toBe(true);
22002200
});
22012201

2202+
it("should reject a bracketed non-IPv6 host", () => {
2203+
expect(() => {
2204+
new CopilotClient({
2205+
connection: RuntimeConnection.forUri("[not-ipv6]:1234"),
2206+
logLevel: "error",
2207+
});
2208+
}).toThrow(/Invalid cliUrl format/);
2209+
});
2210+
22022211
it("should parse https://host:port URL format", () => {
22032212
const client = new CopilotClient({
22042213
connection: RuntimeConnection.forUri("https://example.com:443"),

python/copilot/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from __future__ import annotations
1616

1717
import asyncio
18+
import ipaddress
1819
import inspect
1920
import logging
2021
import os
@@ -1655,6 +1656,10 @@ def _parse_cli_url(self, url: str) -> tuple[str, int]:
16551656
if ipv6_match:
16561657
host = ipv6_match.group(1)
16571658
port_text = ipv6_match.group(2)
1659+
try:
1660+
ipaddress.IPv6Address(host)
1661+
except ValueError as e:
1662+
raise ValueError(f"Invalid cli_url format: {url}") from e
16581663
else:
16591664
# Parse host:port format
16601665
parts = clean_url.split(":")

python/test_client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,10 @@ def test_parse_http_ipv6_url(self):
12141214
assert client._actual_host == "::1"
12151215
assert client._is_external_server
12161216

1217+
def test_reject_bracketed_non_ipv6_host(self):
1218+
with pytest.raises(ValueError, match="Invalid cli_url format"):
1219+
CopilotClient(connection=RuntimeConnection.for_uri("[not-ipv6]:1234"))
1220+
12171221
def test_parse_https_url(self):
12181222
client = CopilotClient(connection=RuntimeConnection.for_uri("https://example.com:443"))
12191223
assert client._runtime_port == 443

0 commit comments

Comments
 (0)