Skip to content

Commit 0b292b8

Browse files
committed
fix(ipc): tighten workload validation on utils:get-workload-available-tools
Address Copilot review on #2037: the previous guard accepted any string for transport_type / proxy_mode and any number for port, so prototype keys like `__proto__` would fall through into createTransport, and `NaN` / non-http URLs could reach `new URL(...)` at the transport layer. - Restrict `transport_type` to {stdio, streamable-http, sse} and `proxy_mode` to {sse, streamable-http} via explicit allowlists - Require `port` to be a finite integer in [0, 65535] - Require `url` to be an http(s) URL parseable by `new URL` (empty string still tolerated; createTransport falls back to localhost)
1 parent 6d4d8b5 commit 0b292b8

1 file changed

Lines changed: 50 additions & 11 deletions

File tree

main/src/ipc-handlers/utils.ts

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,72 @@ import { getHeaders } from '../headers'
44
import { getInstanceId, isOfficialReleaseBuild } from '../util'
55
import { getWorkloadAvailableTools } from '../utils/mcp-tools'
66

7+
// Allowlists for fields that are later used to index into lookup tables or
8+
// construct URLs/connections in `createTransport`. Keeping these explicit makes
9+
// the IPC boundary reject unexpected values (including prototype keys like
10+
// `__proto__` or `constructor`) before they reach the transport layer.
11+
const VALID_TRANSPORT_TYPES = new Set(['stdio', 'streamable-http', 'sse'])
12+
const VALID_PROXY_MODES = new Set(['sse', 'streamable-http'])
13+
const MAX_TCP_PORT = 65535
14+
715
function isOptionalString(value: unknown): value is string | undefined {
816
return value === undefined || typeof value === 'string'
917
}
1018

11-
function isOptionalNumber(value: unknown): value is number | undefined {
12-
return value === undefined || typeof value === 'number'
13-
}
14-
1519
function isOptionalBoolean(value: unknown): value is boolean | undefined {
1620
return value === undefined || typeof value === 'boolean'
1721
}
1822

23+
function isOptionalEnum(
24+
value: unknown,
25+
allowed: ReadonlySet<string>
26+
): value is string | undefined {
27+
return (
28+
value === undefined || (typeof value === 'string' && allowed.has(value))
29+
)
30+
}
31+
32+
function isOptionalTcpPort(value: unknown): value is number | undefined {
33+
if (value === undefined) return true
34+
return (
35+
typeof value === 'number' &&
36+
Number.isInteger(value) &&
37+
value >= 0 &&
38+
value <= MAX_TCP_PORT
39+
)
40+
}
41+
42+
function isOptionalHttpUrl(value: unknown): value is string | undefined {
43+
if (value === undefined) return true
44+
if (typeof value !== 'string') return false
45+
// Empty string is tolerated: `createTransport` treats it as "no url" and
46+
// falls back to `http://localhost:<port>/mcp`.
47+
if (value === '') return true
48+
try {
49+
const parsed = new URL(value)
50+
return parsed.protocol === 'http:' || parsed.protocol === 'https:'
51+
} catch {
52+
return false
53+
}
54+
}
55+
1956
// Validates that an untrusted IPC payload matches the subset of `CoreWorkload`
20-
// that `getWorkloadAvailableTools` relies on. We do not exhaustively validate
21-
// every field on the generated type; we only enforce the shape required by the
22-
// consumer so malformed or malicious payloads are rejected at the boundary.
57+
// that `getWorkloadAvailableTools` / `createTransport` rely on. Only fields
58+
// consumed by the downstream code are validated; fields like labels or
59+
// created_at are ignored here because the consumer never reads them.
2360
function isCoreWorkload(value: unknown): value is CoreWorkload {
2461
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
2562
return false
2663
}
2764
const workload = value as Record<string, unknown>
2865

2966
if (!isOptionalString(workload.name)) return false
30-
if (!isOptionalString(workload.url)) return false
31-
if (!isOptionalString(workload.transport_type)) return false
32-
if (!isOptionalString(workload.proxy_mode)) return false
33-
if (!isOptionalNumber(workload.port)) return false
67+
if (!isOptionalHttpUrl(workload.url)) return false
68+
if (!isOptionalEnum(workload.transport_type, VALID_TRANSPORT_TYPES)) {
69+
return false
70+
}
71+
if (!isOptionalEnum(workload.proxy_mode, VALID_PROXY_MODES)) return false
72+
if (!isOptionalTcpPort(workload.port)) return false
3473
if (!isOptionalBoolean(workload.remote)) return false
3574

3675
return true

0 commit comments

Comments
 (0)