From 7d8552f22b75978723ef77557f5eff6e240cdc4f Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 12 Sep 2026 18:29:56 -0500 Subject: [PATCH] Preserve sandbox home in managed OpenCode runtime configuration Move the optional HOME helper and focused coverage ahead of its caller so the runtime hardening layer remains under the review file limit. Co-Authored-By: Paperclip --- .../src/server/runtime-config.test.ts | 35 ++++++++++++++++++- .../src/server/runtime-config.ts | 5 ++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/adapters/opencode-local/src/server/runtime-config.test.ts b/packages/adapters/opencode-local/src/server/runtime-config.test.ts index 14791829a8..94a980ffe5 100644 --- a/packages/adapters/opencode-local/src/server/runtime-config.test.ts +++ b/packages/adapters/opencode-local/src/server/runtime-config.test.ts @@ -2,10 +2,43 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; -import { prepareOpenCodeRuntimeConfig } from "./runtime-config.js"; +import { prepareManagedOpenCodeRemoteHomes, prepareOpenCodeRuntimeConfig } from "./runtime-config.js"; const cleanupPaths = new Set(); +describe("prepareManagedOpenCodeRemoteHomes", () => { + it("keeps the sandbox home while isolating managed credentials outside scoped folders", () => { + const env = { HOME: "/host/private", XDG_DATA_HOME: "/host/data" }; + prepareManagedOpenCodeRemoteHomes({ + env, + config: { managedAiConnection: true }, + runtimeRootDir: "/home/daytona/.paperclip-runtime/opencode", + runId: "run-1", + configDir: "/home/daytona/.paperclip-runtime/opencode/config", + workFolderHome: "/home/daytona", + }); + expect(env).toEqual({ + HOME: "/home/daytona", + XDG_CONFIG_HOME: "/home/daytona/.paperclip-runtime/opencode/config", + XDG_DATA_HOME: "/home/daytona/.paperclip-runtime/opencode/managed-auth/run-1/data", + XDG_CACHE_HOME: "/home/daytona/.paperclip-runtime/opencode/managed-auth/run-1/cache", + XDG_STATE_HOME: "/home/daytona/.paperclip-runtime/opencode/managed-auth/run-1/state", + }); + }); + + it("preserves the isolated remote home for execution without work folders", () => { + const env: Record = { HOME: "/host/private" }; + prepareManagedOpenCodeRemoteHomes({ + env, + config: { managedAiConnection: true }, + runtimeRootDir: "/remote/runtime", + runId: "run-1", + }); + expect(env.HOME).toBe("/remote/runtime/managed-auth/run-1"); + expect(env.XDG_DATA_HOME).toBe("/remote/runtime/managed-auth/run-1/data"); + }); +}); + afterEach(async () => { await Promise.all( [...cleanupPaths].map(async (filepath) => { diff --git a/packages/adapters/opencode-local/src/server/runtime-config.ts b/packages/adapters/opencode-local/src/server/runtime-config.ts index b77aa7c8e9..51367efe82 100644 --- a/packages/adapters/opencode-local/src/server/runtime-config.ts +++ b/packages/adapters/opencode-local/src/server/runtime-config.ts @@ -248,12 +248,15 @@ export function prepareManagedOpenCodeRemoteHomes(input: { runtimeRootDir: string | null | undefined; runId: string; configDir?: string; + workFolderHome?: string; }): void { if (!input.config.managedAiConnection) return; if (!input.runtimeRootDir) throw new Error("Managed OpenCode authentication requires an isolated remote runtime directory."); const home = path.posix.join(input.runtimeRootDir, "managed-auth", input.runId); Object.assign(input.env, { - HOME: home, + // Scoped work folders live beneath the sandbox's real home. OpenCode's + // credential/configuration stores still use the isolated XDG directories. + HOME: input.workFolderHome ?? home, XDG_CONFIG_HOME: input.configDir ?? path.posix.join(home, "config"), XDG_DATA_HOME: path.posix.join(home, "data"), XDG_CACHE_HOME: path.posix.join(home, "cache"),