Merge refreshed Storybook layer into runtime hardening

Preserve managed OpenCode credential isolation while keeping the sandbox work-folder HOME.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-12 18:30:41 -05:00
commit 57c2f528f1
294 files changed
+115930 -1966

No files matched your search

@@ -126,7 +126,7 @@ describe("opencode remote execution", () => {
}
});
it("prepares the workspace, syncs OpenCode skills, and restores workspace changes for remote SSH execution", async () => {
it.each([false, true])("prepares the workspace, syncs OpenCode skills, and restores workspace changes for remote SSH execution (managed=%s)", async (managed) => {
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-opencode-remote-"));
cleanupDirs.push(rootDir);
const workspaceDir = path.join(rootDir, "workspace");
@@ -153,6 +153,13 @@ describe("opencode remote execution", () => {
config: {
command: "opencode",
model: "opencode/gpt-5-nano",
...(managed ? {
managedAiConnection: { provider: "openrouter", method: "api_key" },
} : {}),
env: {
XDG_CONFIG_HOME: path.join(rootDir, "config"),
...(managed ? { HOME: "/var/folders/qa-managed", XDG_DATA_HOME: "/var/folders/qa-managed/data" } : {}),
},
},
context: {
paperclipWorkspace: {
@@ -233,6 +240,17 @@ describe("opencode remote execution", () => {
| [string, string, string[], { env: Record<string, string>; remoteExecution?: { remoteCwd: string } | null }]
| undefined;
expect(call?.[3].env.PAPERCLIP_WORKSPACE_CWD).toBe(managedRemoteWorkspace);
if (managed) {
const home = `${managedRemoteWorkspace}/.paperclip-runtime/opencode/managed-auth/run-1`;
expect(call?.[3].env.HOME).toBe(home);
expect(call?.[3].env.XDG_DATA_HOME).toBe(`${home}/data`);
expect(modelProbeCall?.[3].env.XDG_DATA_HOME).toBe(`${home}/data`);
expect(runSshCommand).toHaveBeenCalledWith(
expect.anything(),
expect.stringContaining(`${home}/.claude/skills`),
expect.anything(),
);
}
expect(JSON.parse(call?.[3].env.PAPERCLIP_WORKSPACES_JSON ?? "[]")).toEqual([
{
workspaceId: "workspace-1",
@@ -59,7 +59,7 @@ import {
requireOpenCodeModelId,
} from "./models.js";
import { removeMaintainerOnlySkillSymlinks } from "@paperclipai/adapter-utils/server-utils";
import { prepareOpenCodeRuntimeConfig } from "./runtime-config.js";
import { prepareOpenCodeRuntimeConfig, prepareManagedOpenCodeRemoteHomes } from "./runtime-config.js";
import { SANDBOX_INSTALL_COMMAND } from "../index.js";
import { resolveOpenCodeSkillsHome } from "./skills.js";
@@ -440,8 +440,18 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
if (localRuntimeConfigHome && preparedExecutionTargetRuntime.assetDirs.xdgConfig) {
preparedRuntimeConfig.env.XDG_CONFIG_HOME = preparedExecutionTargetRuntime.assetDirs.xdgConfig;
}
const remoteHomeDir = managedRemoteHomeDir
?? await readAdapterExecutionTargetHomeDir(runId, executionTarget, {
prepareManagedOpenCodeRemoteHomes({
env: preparedRuntimeConfig.env,
config,
runtimeRootDir: preparedExecutionTargetRuntime.runtimeRootDir,
runId,
configDir: preparedExecutionTargetRuntime.assetDirs.xdgConfig,
workFolderHome: executionTarget.transport === "sandbox" ? executionTarget.workFolderHome : undefined,
});
const remoteHomeDir = config.managedAiConnection
? preparedRuntimeConfig.env.HOME
: managedRemoteHomeDir
?? await readAdapterExecutionTargetHomeDir(runId, executionTarget, {
cwd,
env: preparedRuntimeConfig.env,
timeoutSec,
@@ -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<string>();
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<string, string> = { 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) => {
@@ -240,3 +240,26 @@ export async function prepareOpenCodeRuntimeConfig(input: {
},
};
}
/** Managed credentials must never leave host-only homes in a remote process. */
export function prepareManagedOpenCodeRemoteHomes(input: {
env: Record<string, string>;
config: Record<string, unknown>;
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, {
// 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"),
XDG_STATE_HOME: path.posix.join(home, "state"),
});
}
@@ -74,6 +74,7 @@ vi.mock("@paperclipai/adapter-utils/execution-target", async () => {
import { testEnvironment } from "./test.js";
describe("opencode remote environment diagnostics", () => {
const configHomes: string[] = [];
let configHome: string;
beforeEach(async () => {
@@ -85,9 +86,12 @@ describe("opencode remote environment diagnostics", () => {
vi.clearAllMocks();
vi.unstubAllEnvs();
await rm(configHome, { recursive: true, force: true });
await Promise.all(configHomes.splice(0).map(dir => rm(dir, { recursive: true, force: true })));
});
it("stages remote runtime config assets for sandbox hello probes", async () => {
it.each([false, true])("stages remote runtime config assets for sandbox hello probes (managed=%s)", async (managed) => {
const configHome = await mkdtemp(path.join(os.tmpdir(), "opencode-remote-test-config-"));
configHomes.push(configHome);
const remoteTarget: AdapterExecutionTarget = {
kind: "remote",
transport: "sandbox",
@@ -112,6 +116,13 @@ describe("opencode remote environment diagnostics", () => {
config: {
command: "opencode",
model: "anthropic/claude-sonnet-4-5",
...(managed ? {
managedAiConnection: { provider: "openrouter", method: "api_key" },
} : {}),
env: {
XDG_CONFIG_HOME: configHome,
...(managed ? { OPENAI_API_KEY: "", OPENROUTER_API_KEY: "fixture", HOME: "/var/folders/qa-managed", XDG_DATA_HOME: "/var/folders/qa-managed/data" } : {}),
},
},
executionTarget: remoteTarget,
environmentName: "QA Cloudflare",
@@ -134,6 +145,11 @@ describe("opencode remote environment diagnostics", () => {
| [string, AdapterExecutionTarget, string, string[], { cwd: string; env: Record<string, string> }]
| undefined;
expect(probeCall?.[4].cwd).toBe("/remote/workspace/.paperclip-runtime/runs/test/workspace");
if (managed) {
expect(probeCall?.[4].env.HOME).toContain("/remote/workspace/.paperclip-runtime/runs/test/workspace/.paperclip-runtime/opencode/managed-auth/");
expect(probeCall?.[4].env.XDG_DATA_HOME).toBe(`${probeCall?.[4].env.HOME}/data`);
expect(probeCall?.[4].env.XDG_CACHE_HOME).toBe(`${probeCall?.[4].env.HOME}/cache`);
}
expect(probeCall?.[4].env.XDG_CONFIG_HOME).toBe(
"/remote/workspace/.paperclip-runtime/runs/test/workspace/.paperclip-runtime/opencode/xdgConfig",
);
@@ -28,7 +28,7 @@ import {
import { discoverOpenCodeModels, ensureOpenCodeModelConfiguredAndAvailable } from "./models.js";
import { parseOpenCodeJsonl } from "./parse.js";
import { SANDBOX_INSTALL_COMMAND } from "../index.js";
import { prepareOpenCodeRuntimeConfig } from "./runtime-config.js";
import { prepareOpenCodeRuntimeConfig, prepareManagedOpenCodeRemoteHomes } from "./runtime-config.js";
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
if (checks.some((check) => check.level === "error")) return "fail";
@@ -115,7 +115,7 @@ export async function testEnvironment(
}
const openaiKeyOverride = "OPENAI_API_KEY" in envConfig ? asString(envConfig.OPENAI_API_KEY, "") : null;
if (openaiKeyOverride !== null && openaiKeyOverride.trim() === "") {
if (!config.managedAiConnection && openaiKeyOverride !== null && openaiKeyOverride.trim() === "") {
checks.push({
code: "opencode_openai_api_key_missing",
level: "warn",
@@ -172,6 +172,13 @@ export async function testEnvironment(
if (localRuntimeConfigHome && preparedExecutionTargetRuntime.assetDirs.xdgConfig) {
preparedRuntimeConfig.env.XDG_CONFIG_HOME = preparedExecutionTargetRuntime.assetDirs.xdgConfig;
}
prepareManagedOpenCodeRemoteHomes({
env: preparedRuntimeConfig.env,
config,
runtimeRootDir: preparedExecutionTargetRuntime.runtimeRootDir,
runId,
configDir: preparedExecutionTargetRuntime.assetDirs.xdgConfig,
});
}
const runtimeEnv = normalizeEnv(ensurePathInEnv({ ...process.env, ...preparedRuntimeConfig.env }));