From 9ce0e1cc81e575e002de55cd287cb293af272bef Mon Sep 17 00:00:00 2001 From: Marius Date: Tue, 23 Jun 2026 21:06:59 +0200 Subject: [PATCH] fix(sandbox): share opaque network classifications --- .../src/kilocode/sandbox/network-tools.ts | 13 +++++++ .../opencode/src/kilocode/sandbox/network.ts | 3 +- script/check-model-tool-network.ts | 34 ++++++++++++++----- 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 packages/opencode/src/kilocode/sandbox/network-tools.ts diff --git a/packages/opencode/src/kilocode/sandbox/network-tools.ts b/packages/opencode/src/kilocode/sandbox/network-tools.ts new file mode 100644 index 0000000000..08293fbc5e --- /dev/null +++ b/packages/opencode/src/kilocode/sandbox/network-tools.ts @@ -0,0 +1,13 @@ +export const opaque = [ + { + id: "codebase_search", + file: "tool/warpgrep.ts", + client: { + name: "ad hoc network client", + count: 1, + reason: "opaque SDK traffic is denied by the common executeTool network boundary", + }, + }, + { id: "semantic_search", file: "kilocode/tool/semantic-search.ts" }, + { id: "lsp", file: "tool/lsp.ts" }, +] as const diff --git a/packages/opencode/src/kilocode/sandbox/network.ts b/packages/opencode/src/kilocode/sandbox/network.ts index dc6e3efe56..a374441d89 100644 --- a/packages/opencode/src/kilocode/sandbox/network.ts +++ b/packages/opencode/src/kilocode/sandbox/network.ts @@ -1,10 +1,11 @@ import { Effect, Layer } from "effect" import { FetchHttpClient } from "effect/unstable/http" import { assertNetwork, networkHttpLayer } from "@kilocode/sandbox" +import { opaque } from "./network-tools" const Builtin = Symbol("kilo.sandbox.builtinTool") const Remote = Symbol("kilo.sandbox.remoteMcp") -const indirect = new Set(["codebase_search", "semantic_search", "lsp"]) +const indirect = new Set(opaque.map((item) => item.id)) export const httpLayer = networkHttpLayer.pipe(Layer.provide(FetchHttpClient.layer)) diff --git a/script/check-model-tool-network.ts b/script/check-model-tool-network.ts index 5ba1e7758f..2ca17d9726 100644 --- a/script/check-model-tool-network.ts +++ b/script/check-model-tool-network.ts @@ -9,6 +9,7 @@ // outside the scanned directories. Runtime enforcement remains in @kilocode/sandbox. import path from "node:path" +import { opaque } from "../packages/opencode/src/kilocode/sandbox/network-tools" const root = path.resolve(import.meta.dir, "..") const source = path.join(root, "packages", "opencode", "src") @@ -33,12 +34,13 @@ const checks = [ /\bnew\s+(?:WarpGrepClient|OpenAI|QdrantClient|BedrockRuntimeClient|WebSocket|EventSource|StreamableHTTPClientTransport|SSEClientTransport)\s*\(/g, }, ] -const allow: Record = { - "tool/warpgrep.ts:ad hoc network client": { - count: 1, - reason: "opaque SDK traffic is denied by the common executeTool network boundary", - }, -} +const allow = new Map( + opaque.flatMap((item) => + "client" in item + ? [[`${item.file}:${item.client.name}`, { ...item.client, file: item.file, id: item.id }] as const] + : [], + ), +) const hits: Array<{ file: string; name: string; line: number }> = [] const glob = new Bun.Glob("**/*.ts") @@ -58,8 +60,8 @@ for (const dir of dirs) { } } -const invalid = hits.filter((hit) => !allow[`${hit.file}:${hit.name}`]) -const drift = Object.entries(allow).flatMap(([key, entry]) => { +const invalid = hits.filter((hit) => !allow.has(`${hit.file}:${hit.name}`)) +const clients = [...allow.entries()].flatMap(([key, entry]) => { const split = key.lastIndexOf(":") const file = key.slice(0, split) const name = key.slice(split + 1) @@ -67,11 +69,27 @@ const drift = Object.entries(allow).flatMap(([key, entry]) => { if (count === entry.count) return [] return [` packages/opencode/src/${file}: expected ${entry.count} ${name} site(s), found ${count} (${entry.reason})`] }) +const tools = ( + await Promise.all( + opaque.map(async (item) => { + const text = await Bun.file(path.join(source, item.file)).text() + const id = item.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + if (new RegExp(`Tool\\.define\\(\\s*["']${id}["']`).test(text)) return [] + return [` packages/opencode/src/${item.file}: opaque classification must match Tool.define("${item.id}")`] + }), + ) +).flat() +const drift = [...clients, ...tools] +const network = await Bun.file(path.join(source, "kilocode", "sandbox", "network.ts")).text() const registry = await Bun.file(path.join(source, "tool", "registry.ts")).text() const session = await Bun.file(path.join(source, "session", "tools.ts")).text() const mcp = await Bun.file(path.join(source, "mcp", "index.ts")).text() const structure = [ + ...(!network.includes('import { opaque } from "./network-tools"') || + !network.includes("opaque.map((item) => item.id)") + ? [" kilocode/sandbox/network.ts must derive runtime opaque tool IDs from network-tools.ts"] + : []), ...(!registry.includes("Layer.provide(ToolNetwork.httpLayer)") ? [" tool/registry.ts must provide the policy-aware ToolNetwork HTTP layer"] : []),