diff --git a/.changeset/sandbox-writable-paths-input-width.md b/.changeset/sandbox-writable-paths-input-width.md new file mode 100644 index 00000000000..b48de18dcb9 --- /dev/null +++ b/.changeset/sandbox-writable-paths-input-width.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Widen the Additional Writable Paths input in the Sandboxing settings so longer filesystem paths are easier to read while typing. diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/SandboxingTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/SandboxingTab.tsx index 74f6e656faa..8ec16cb3963 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/SandboxingTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/SandboxingTab.tsx @@ -64,63 +64,67 @@ const SandboxingTab: Component = () => { - -
-
0 ? "1px solid var(--border-weak-base)" : "none", - }} - > -
- setNewPath(val)} - onKeyDown={(e: KeyboardEvent) => { - if (e.key === "Enter") addPath() - }} - hideLabel - label={language.t("settings.sandboxing.writablePaths.title")} - /> + {/* wide-input widens the input column so long filesystem paths are readable */} +
+ +
+
0 ? "1px solid var(--border-weak-base)" : "none", + }} + > +
+ setNewPath(val)} + onKeyDown={(e: KeyboardEvent) => { + if (e.key === "Enter") addPath() + }} + hideLabel + label={language.t("settings.sandboxing.writablePaths.title")} + /> +
+
- -
- - {(path, index) => ( -
- + {(path, index) => ( +
- {path} - - removePath(index())} /> -
- )} - -
-
+ + {path} + + removePath(index())} /> +
+ )} + +
+ +
) } diff --git a/packages/opencode/test/kilocode/config/config.test.ts b/packages/opencode/test/kilocode/config/config.test.ts index e405398721d..467282a3463 100644 --- a/packages/opencode/test/kilocode/config/config.test.ts +++ b/packages/opencode/test/kilocode/config/config.test.ts @@ -242,6 +242,41 @@ describe("kilocode indexing config", () => { }) }) +describe("kilocode sandbox writable paths config", () => { + test("honors sandbox_writable_paths from global config only, ignoring project config", async () => { + await using globalTmp = await tmpdir() + await using tmp = await tmpdir({ git: true }) + + const prev = Global.Path.config + ;(Global.Path as { config: string }).config = globalTmp.path + await clear() + await disposeAllInstances() + + try { + await writeConfig(globalTmp.path, { + $schema: "https://app.kilo.ai/config.json", + experimental: { sandbox_writable_paths: ["/tmp/global"] }, + }) + // A project kilo.json must not widen the sandbox: its writable paths are dropped at merge time. + await writeConfig(tmp.path, { + experimental: { sandbox_writable_paths: ["/tmp/project"] }, + }) + + await provideTestInstance({ + directory: tmp.path, + fn: async () => { + const config = await load() + expect(config.experimental?.sandbox_writable_paths).toEqual(["/tmp/global"]) + }, + }) + } finally { + ;(Global.Path as { config: string }).config = prev + await clear() + await disposeAllInstances() + } + }) +}) + describe("custom provider model config", () => { test("persists and removes reasoning across a global config reload", async () => { await using globalTmp = await tmpdir() diff --git a/packages/opencode/test/kilocode/sandbox/policy.test.ts b/packages/opencode/test/kilocode/sandbox/policy.test.ts index 03523526999..240c1e1f388 100644 --- a/packages/opencode/test/kilocode/sandbox/policy.test.ts +++ b/packages/opencode/test/kilocode/sandbox/policy.test.ts @@ -234,4 +234,34 @@ describe("sandbox policy", () => { expect(roots(ctx)).not.toContain(dirs.approved) expect(Exit.isFailure(result)).toBe(true) }) + + test("makes configured extra writable paths writable while unlisted paths stay denied", async () => { + await using tmp = await fixture() + const dirs = tmp.extra + const ctx = context(dirs.a, dirs.a, dirs) + const policy = profile(ctx, "deny", [dirs.approved]) + const result = await Effect.runPromise( + Effect.all({ + extra: runSandbox(policy, assertWrite(path.join(dirs.approved, "allowed.txt")).pipe(Effect.exit)), + other: runSandbox(policy, assertWrite(path.join(dirs.b, "denied.txt")).pipe(Effect.exit)), + }), + ) + + expect(policy.filesystem.allowWrite.map((rule) => rule.path)).toContain(dirs.approved) + expect(roots(ctx)).not.toContain(dirs.approved) + expect(Exit.isSuccess(result.extra)).toBe(true) + expect(Exit.isFailure(result.other)).toBe(true) + }) + + test("keeps .git denied inside a configured extra writable path", async () => { + await using tmp = await fixture() + const dirs = tmp.extra + const ctx = context(dirs.a, dirs.a, dirs) + const policy = profile(ctx, "deny", [dirs.approved]) + const result = await Effect.runPromise( + runSandbox(policy, assertWrite(path.join(dirs.approved, ".git", "config")).pipe(Effect.exit)), + ) + + expect(Exit.isFailure(result)).toBe(true) + }) })