import express from "express"; import request from "supertest"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { environmentRoutes } from "../routes/environments.js"; import { HttpError } from "../errors.js"; import { environmentCustomImageTerminalConnectionRegistry, environmentCustomImageTerminalSessionStore, } from "../services/environment-custom-image-terminal-sessions.js"; const now = new Date("2026-06-25T20:00:00.000Z"); const mockIssueService = vi.hoisted(() => ({ clearExecutionWorkspaceEnvironmentSelection: vi.fn(), })); const mockProjectService = vi.hoisted(() => ({ clearExecutionWorkspaceEnvironmentSelection: vi.fn(), })); const mockInstanceSettingsService = vi.hoisted(() => ({ listCompanyIds: vi.fn(), })); const mockEnvironmentService = vi.hoisted(() => ({ list: vi.fn(), getById: vi.fn(), create: vi.fn(), update: vi.fn(), remove: vi.fn(), listLeases: vi.fn(), getLeaseById: vi.fn(), })); const mockEnvironmentCustomImageService = vi.hoisted(() => ({ getOverview: vi.fn(), getActiveTemplate: vi.fn(), getSessionById: vi.fn(), startSetupSession: vi.fn(), refreshSetupSession: vi.fn(), finishSetupSession: vi.fn(), cancelSetupSession: vi.fn(), rollbackTemplate: vi.fn(), relinkActiveTemplate: vi.fn(), disableTemplate: vi.fn(), cleanupExpiredSetupSessions: vi.fn(), })); const mockExecutionWorkspaceService = vi.hoisted(() => ({ clearEnvironmentSelection: vi.fn(), })); const mockSecretService = vi.hoisted(() => ({ create: vi.fn(), normalizeEnvBindingsForPersistence: vi.fn(), listBindingCompanyIdsForTarget: vi.fn(), resolveSecretValueForEphemeralAccess: vi.fn(), syncEnvBindingsForTarget: vi.fn(), syncSecretRefsForTarget: vi.fn(), })); const mockLogActivity = vi.hoisted(() => vi.fn()); vi.mock("../services/index.js", () => ({ issueService: () => mockIssueService, projectService: () => mockProjectService, instanceSettingsService: () => mockInstanceSettingsService, environmentService: () => mockEnvironmentService, environmentCustomImageService: () => mockEnvironmentCustomImageService, logActivity: mockLogActivity, })); vi.mock("../services/environments.js", () => ({ environmentService: () => mockEnvironmentService, })); vi.mock("../services/execution-workspaces.js", () => ({ executionWorkspaceService: () => mockExecutionWorkspaceService, })); vi.mock("../services/secrets.js", () => ({ secretService: () => mockSecretService, })); vi.mock("../services/environment-probe.js", () => ({ probeEnvironment: vi.fn(), })); vi.mock("../services/plugin-environment-driver.js", () => ({ // The runtime reads this published constant at import time. Mirror the real // value so the mocked module keeps the same reusable-lease method contract. REUSABLE_LEASE_WORKER_METHODS: ["environmentResumeLease", "environmentReleaseLease", "environmentDestroyLease"], listReadyPluginEnvironmentDrivers: vi.fn(async () => []), resolvePluginSandboxProviderDriverByKey: vi.fn(async () => null), validatePluginEnvironmentDriverConfig: vi.fn(async ({ config }) => config), validatePluginSandboxProviderConfig: vi.fn(async ({ provider, config }) => ({ normalizedConfig: config, pluginId: `plugin-${provider}`, pluginKey: `plugin.${provider}`, driver: { driverKey: provider, kind: "sandbox_provider", displayName: provider, configSchema: { type: "object" }, }, })), })); function createEnvironment(overrides: Record = {}) { return { id: "env-1", name: "Daytona", description: null, driver: "sandbox", status: "active", config: { provider: "daytona", snapshot: "base-snapshot", reuseLease: false, }, envVars: {}, metadata: null, createdAt: now, updatedAt: now, ...overrides, }; } function createTemplate(overrides: Record = {}) { return { id: "template-1", companyId: "company-1", environmentId: "env-1", provider: "daytona", templateKind: "snapshot", templateRef: "snapshot-secret-ref", sourceTemplateRef: "base-snapshot-secret", sourceEnvironmentConfigFingerprint: "sha256:base", status: "active", createdByUserId: "user-1", createdByAgentId: null, capturedAt: now, lastUsedAt: null, supersededByTemplateId: null, metadata: { safeLabel: "codex" }, createdAt: now, updatedAt: now, ...overrides, }; } function createSession(overrides: Record = {}) { return { id: "session-1", companyId: "company-1", environmentId: "env-1", templateId: "template-1", promotedTemplateId: null, provider: "daytona", providerLeaseId: "lease-secret", environmentLeaseId: null, status: "waiting_for_user", startedByUserId: "user-1", startedByAgentId: null, baseTemplateRef: "snapshot-secret-ref", expiresAt: new Date("2026-06-25T21:00:00.000Z"), finishedAt: null, failureReason: null, connectionSummary: { type: "ssh", username: "token", hostRedacted: true, portRedacted: true, instructions: "ssh token@203.0.113.10", }, connectionSecretRef: null, metadata: { setupRpcCompanyId: "company-1", safeLabel: "setup", connectUrl: "https://203.0.113.10/setup", }, createdAt: now, updatedAt: now, ...overrides, }; } function createApp(actor: Record) { const app = express(); app.use(express.json()); app.use((req, _res, next) => { (req as typeof req & { actor: Record }).actor = actor; next(); }); app.use("/api", environmentRoutes({} as never)); app.use((err: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status((err as { status?: number }).status ?? 500).json({ error: err instanceof Error ? err.message : String(err), }); }); return app; } function boardActor(overrides: Record = {}) { return { type: "board", userId: "user-1", source: "session", companyIds: ["company-1"], isInstanceAdmin: true, ...overrides, }; } function agentActor() { return { type: "agent", agentId: "agent-1", companyId: "company-1", source: "agent_key", runId: "run-1", }; } function loggedActivityJson() { return JSON.stringify(mockLogActivity.mock.calls); } function futureDate(minutes = 60) { return new Date(Date.now() + minutes * 60 * 1000); } describe("environment customImage setup routes", () => { beforeEach(() => { mockIssueService.clearExecutionWorkspaceEnvironmentSelection.mockReset(); mockProjectService.clearExecutionWorkspaceEnvironmentSelection.mockReset(); mockInstanceSettingsService.listCompanyIds.mockReset(); Object.values(mockEnvironmentService).forEach((mock) => mock.mockReset()); Object.values(mockEnvironmentCustomImageService).forEach((mock) => mock.mockReset()); mockExecutionWorkspaceService.clearEnvironmentSelection.mockReset(); Object.values(mockSecretService).forEach((mock) => mock.mockReset()); mockLogActivity.mockReset(); environmentCustomImageTerminalSessionStore.clear(); environmentCustomImageTerminalConnectionRegistry.clear(); mockInstanceSettingsService.listCompanyIds.mockResolvedValue(["company-1"]); mockEnvironmentService.getById.mockResolvedValue(createEnvironment()); mockEnvironmentCustomImageService.getOverview.mockResolvedValue({ activeTemplate: null, activeSession: null, latestSession: null, }); mockEnvironmentCustomImageService.getActiveTemplate.mockResolvedValue(null); mockEnvironmentCustomImageService.getSessionById.mockResolvedValue(null); mockEnvironmentCustomImageService.startSetupSession.mockResolvedValue({ session: createSession(), connectionPayload: { type: "ssh", command: "ssh token@203.0.113.10 -p 2222", }, }); mockEnvironmentCustomImageService.refreshSetupSession.mockResolvedValue({ session: createSession(), connectionPayload: { type: "ssh", command: "ssh token@203.0.113.10 -p 2222", }, }); mockEnvironmentCustomImageService.finishSetupSession.mockResolvedValue({ session: createSession({ status: "promoted", promotedTemplateId: "template-2", finishedAt: now, }), template: createTemplate({ id: "template-2", templateRef: "captured-template-secret", sourceTemplateRef: "snapshot-secret-ref", metadata: { sandboxId: "sandbox-secret" }, }), connectionPayload: null, }); mockEnvironmentCustomImageService.cancelSetupSession.mockResolvedValue(createSession({ status: "cancelled", finishedAt: now, failureReason: "operator requested", })); mockEnvironmentCustomImageService.rollbackTemplate.mockResolvedValue({ activeTemplate: createTemplate({ id: "template-1", templateRef: "old-template-secret" }), supersededTemplate: createTemplate({ id: "template-2", templateRef: "new-template-secret" }), }); mockEnvironmentCustomImageService.disableTemplate.mockResolvedValue(createTemplate({ id: "template-2", templateRef: "disabled-template-secret", status: "revoked", })); mockEnvironmentCustomImageService.relinkActiveTemplate.mockResolvedValue({ template: createTemplate({ id: "template-1", templateRef: "relinked-template-secret" }), classification: "knob_only", }); }); it("starts a setup session, returns the live payload, and logs redacted details", async () => { const res = await request(createApp(boardActor())) .post("/api/environments/env-1/custom-image-setup-sessions?companyId=company-1") .send({ ttlSeconds: 3600 }); expect(res.status).toBe(201); expect(res.body.connectionPayload.command).toContain("203.0.113.10"); expect(mockEnvironmentCustomImageService.startSetupSession).toHaveBeenCalledWith({ environmentId: "env-1", templateId: null, ttlSeconds: 3600, actor: { userId: "user-1", agentId: null, }, secretContextCompanyId: "company-1", }); const activity = loggedActivityJson(); expect(activity).not.toContain("203.0.113.10"); expect(activity).not.toContain("lease-secret"); expect(activity).not.toContain("snapshot-secret-ref"); }); it("refreshes active setup status and can return the live payload to board admins", async () => { mockEnvironmentCustomImageService.getSessionById.mockResolvedValue(createSession()); const res = await request(createApp(boardActor())) .get("/api/environment-custom-image-setup-sessions/session-1"); expect(res.status).toBe(200); expect(res.body.connectionPayload.command).toContain("203.0.113.10"); expect(mockEnvironmentCustomImageService.refreshSetupSession).toHaveBeenCalledWith({ sessionId: "session-1", includeConnectionPayload: true, }); }); it("mints a redacted terminal token for waiting SSH setup sessions", async () => { mockEnvironmentCustomImageService.getSessionById.mockResolvedValue(createSession({ expiresAt: futureDate(), })); mockEnvironmentCustomImageService.refreshSetupSession.mockResolvedValue({ session: createSession({ expiresAt: futureDate(), connectionSummary: { type: "ssh", username: "ssh-token-secret", hostRedacted: true, portRedacted: true, instructions: "ssh ssh-token-secret@203.0.113.10 -p 2222", }, }), connectionPayload: { type: "ssh", command: "ssh ssh-token-secret@203.0.113.10 -p 2222", expiresAt: futureDate(15).toISOString(), }, }); const res = await request(createApp(boardActor())) .post("/api/environment-custom-image-setup-sessions/session-1/terminal-session-token") .send({}); expect(res.status).toBe(201); expect(res.body).toMatchObject({ setupSessionId: "session-1", environmentId: "env-1", connectionType: "ssh", }); expect(typeof res.body.id).toBe("string"); expect(typeof res.body.token).toBe("string"); expect(typeof res.body.expiresAt).toBe("string"); expect(res.body.websocketPath).toContain( `/api/environment-custom-image-setup-sessions/session-1/terminal/ws?terminalSessionId=${encodeURIComponent(res.body.id)}`, ); expect(res.body.websocketPath).not.toContain("token="); expect(res.body.websocketPath).not.toContain(res.body.token); expect(mockEnvironmentCustomImageService.refreshSetupSession).toHaveBeenCalledWith({ sessionId: "session-1", includeConnectionPayload: true, }); const responseJson = JSON.stringify(res.body); expect(responseJson).not.toContain("ssh-token-secret"); expect(responseJson).not.toContain("203.0.113.10"); expect(responseJson).not.toContain("ssh "); const activity = loggedActivityJson(); expect(activity).not.toContain("ssh-token-secret"); expect(activity).not.toContain("203.0.113.10"); expect(activity).not.toContain("ssh "); }); it("denies terminal token minting to agent API key actors before customImage state is read", async () => { const res = await request(createApp(agentActor())) .post("/api/environment-custom-image-setup-sessions/session-1/terminal-session-token") .send({}); expect(res.status).toBe(403); expect(mockEnvironmentCustomImageService.getSessionById).not.toHaveBeenCalled(); expect(mockEnvironmentCustomImageService.refreshSetupSession).not.toHaveBeenCalled(); }); it("denies terminal token minting to non-admin board users before connection payload refresh", async () => { const res = await request(createApp(boardActor({ companyIds: ["company-2"], isInstanceAdmin: false, }))) .post("/api/environment-custom-image-setup-sessions/session-1/terminal-session-token") .send({}); expect(res.status).toBe(403); expect(mockEnvironmentCustomImageService.getSessionById).not.toHaveBeenCalled(); expect(mockEnvironmentCustomImageService.refreshSetupSession).not.toHaveBeenCalled(); }); it("rejects terminal tokens unless the refreshed setup session is waiting for the user", async () => { mockEnvironmentCustomImageService.getSessionById.mockResolvedValue(createSession({ expiresAt: futureDate(), })); mockEnvironmentCustomImageService.refreshSetupSession.mockResolvedValue({ session: createSession({ status: "starting", expiresAt: futureDate() }), connectionPayload: { type: "ssh", command: "ssh user@example.test", }, }); const res = await request(createApp(boardActor())) .post("/api/environment-custom-image-setup-sessions/session-1/terminal-session-token") .send({}); expect(res.status).toBe(409); expect(JSON.stringify(res.body)).not.toContain("user@example.test"); }); it("rejects terminal tokens for expired setup sessions", async () => { mockEnvironmentCustomImageService.getSessionById.mockResolvedValue(createSession()); mockEnvironmentCustomImageService.refreshSetupSession.mockResolvedValue({ session: createSession({ status: "waiting_for_user", expiresAt: new Date("2026-06-25T19:00:00.000Z"), }), connectionPayload: { type: "ssh", command: "ssh user@example.test", }, }); const res = await request(createApp(boardActor())) .post("/api/environment-custom-image-setup-sessions/session-1/terminal-session-token") .send({}); expect(res.status).toBe(409); }); it("rejects non-SSH or unsupported SSH terminal payloads without echoing secrets", async () => { mockEnvironmentCustomImageService.getSessionById.mockResolvedValue(createSession({ expiresAt: futureDate(), })); mockEnvironmentCustomImageService.refreshSetupSession.mockResolvedValueOnce({ session: createSession({ expiresAt: futureDate() }), connectionPayload: { type: "browser_terminal", command: "ssh ssh-token-secret@203.0.113.10", }, }); const unsupportedType = await request(createApp(boardActor())) .post("/api/environment-custom-image-setup-sessions/session-1/terminal-session-token") .send({}); expect(unsupportedType.status).toBe(422); expect(JSON.stringify(unsupportedType.body)).not.toContain("ssh-token-secret"); expect(JSON.stringify(unsupportedType.body)).not.toContain("203.0.113.10"); mockEnvironmentCustomImageService.refreshSetupSession.mockResolvedValueOnce({ session: createSession({ expiresAt: futureDate() }), connectionPayload: { type: "ssh", command: "ssh ssh-token-secret@203.0.113.10 -i /tmp/private-key", }, }); const unsupportedShape = await request(createApp(boardActor())) .post("/api/environment-custom-image-setup-sessions/session-1/terminal-session-token") .send({}); expect(unsupportedShape.status).toBe(422); expect(JSON.stringify(unsupportedShape.body)).not.toContain("ssh-token-secret"); expect(JSON.stringify(unsupportedShape.body)).not.toContain("203.0.113.10"); expect(loggedActivityJson()).not.toContain("ssh-token-secret"); }); it("rejects invalid and expired terminal payload expiries without minting tokens", async () => { mockEnvironmentCustomImageService.getSessionById.mockResolvedValue(createSession({ expiresAt: futureDate(), })); mockEnvironmentCustomImageService.refreshSetupSession.mockResolvedValueOnce({ session: createSession({ expiresAt: futureDate() }), connectionPayload: { type: "ssh", command: "ssh ssh-token-secret@ssh.app.daytona.io", expiresAt: "not-a-date", }, }); const invalidExpiry = await request(createApp(boardActor())) .post("/api/environment-custom-image-setup-sessions/session-1/terminal-session-token") .send({}); expect(invalidExpiry.status).toBe(422); expect(JSON.stringify(invalidExpiry.body)).not.toContain("ssh-token-secret"); mockEnvironmentCustomImageService.refreshSetupSession.mockResolvedValueOnce({ session: createSession({ expiresAt: futureDate() }), connectionPayload: { type: "ssh", command: "ssh ssh-token-secret@ssh.app.daytona.io", expiresAt: new Date(Date.now() - 60_000).toISOString(), }, }); const expiredPayload = await request(createApp(boardActor())) .post("/api/environment-custom-image-setup-sessions/session-1/terminal-session-token") .send({}); expect(expiredPayload.status).toBe(409); expect(JSON.stringify(expiredPayload.body)).not.toContain("ssh-token-secret"); }); it("denies agent API key actors before customImage state or payloads are read", async () => { const app = createApp(agentActor()); const start = await request(app) .post("/api/environments/env-1/custom-image-setup-sessions?companyId=company-1") .send({}); const status = await request(app) .get("/api/environment-custom-image-setup-sessions/session-1"); expect(start.status).toBe(403); expect(status.status).toBe(403); expect(mockEnvironmentCustomImageService.startSetupSession).not.toHaveBeenCalled(); expect(mockEnvironmentCustomImageService.getSessionById).not.toHaveBeenCalled(); expect(mockEnvironmentCustomImageService.refreshSetupSession).not.toHaveBeenCalled(); }); it("denies non-admin board users before connection payload refresh", async () => { mockEnvironmentCustomImageService.getSessionById.mockResolvedValue(createSession()); const res = await request(createApp(boardActor({ companyIds: ["company-2"], isInstanceAdmin: false, }))) .get("/api/environment-custom-image-setup-sessions/session-1"); expect(res.status).toBe(403); expect(mockEnvironmentCustomImageService.getSessionById).not.toHaveBeenCalled(); expect(mockEnvironmentCustomImageService.refreshSetupSession).not.toHaveBeenCalled(); }); it("denies single-company fallback when the board actor is not a member", async () => { mockInstanceSettingsService.listCompanyIds.mockResolvedValue(["company-2"]); const res = await request(createApp(boardActor({ companyIds: ["company-1"], isInstanceAdmin: false, }))) .post("/api/environments/env-1/custom-image-setup-sessions") .send({}); expect(res.status).toBe(403); expect(mockEnvironmentCustomImageService.startSetupSession).not.toHaveBeenCalled(); }); it("finishes and promotes a template while logging redacted template details", async () => { mockEnvironmentCustomImageService.getSessionById.mockResolvedValue(createSession()); const terminal = environmentCustomImageTerminalSessionStore.create({ setupSessionId: "session-1", companyId: "company-1", environmentId: "env-1", provider: "daytona", ssh: { username: "token-secret", host: "203.0.113.10", port: 2222 }, setupExpiresAt: futureDate(), }); const closeReasons: string[] = []; environmentCustomImageTerminalConnectionRegistry.add({ setupSessionId: "session-1", close: (reason) => closeReasons.push(reason), }); const res = await request(createApp(boardActor())) .post("/api/environment-custom-image-setup-sessions/session-1/finish") .send({ metadata: { safeLabel: "done" } }); expect(res.status).toBe(200); expect(res.body.template.templateRef).toBe("captured-template-secret"); expect(mockEnvironmentCustomImageService.finishSetupSession).toHaveBeenCalledWith({ sessionId: "session-1", metadata: { safeLabel: "done" }, }); expect(environmentCustomImageTerminalSessionStore.get({ id: terminal.session.id, token: terminal.token, })).toBeNull(); expect(closeReasons).toEqual(["setup_finished"]); const activity = loggedActivityJson(); expect(activity).not.toContain("captured-template-secret"); expect(activity).not.toContain("snapshot-secret-ref"); expect(activity).not.toContain("sandbox-secret"); }); it("cancels active sessions without logging lease details", async () => { mockEnvironmentCustomImageService.getSessionById.mockResolvedValue(createSession()); const res = await request(createApp(boardActor())) .post("/api/environment-custom-image-setup-sessions/session-1/cancel") .send({ reason: "operator requested" }); expect(res.status).toBe(200); expect(res.body.status).toBe("cancelled"); expect(mockEnvironmentCustomImageService.cancelSetupSession).toHaveBeenCalledWith({ sessionId: "session-1", reason: "operator requested", }); expect(loggedActivityJson()).not.toContain("lease-secret"); }); it("rolls back and disables active templates through company-scoped routes", async () => { const app = createApp(boardActor()); const rollback = await request(app) .post("/api/environments/env-1/custom-image-template/rollback?companyId=company-1") .send({}); const disable = await request(app) .delete("/api/environments/env-1/custom-image-template?companyId=company-1&deleteProviderTemplate=true"); expect(rollback.status).toBe(200); expect(disable.status).toBe(200); expect(mockEnvironmentCustomImageService.rollbackTemplate).toHaveBeenCalledWith({ environmentId: "env-1", }); expect(mockEnvironmentCustomImageService.disableTemplate).toHaveBeenCalledWith({ environmentId: "env-1", deleteProviderTemplate: true, }); const activity = loggedActivityJson(); expect(activity).not.toContain("new-template-secret"); expect(activity).not.toContain("old-template-secret"); expect(activity).not.toContain("disabled-template-secret"); }); it("relinks the active template through the company-scoped route", async () => { const res = await request(createApp(boardActor())) .post("/api/environments/env-1/custom-image-template/relink?companyId=company-1") .send({ confirmBootSourceDrift: true }); expect(res.status).toBe(200); expect(res.body.classification).toBe("knob_only"); expect(mockEnvironmentCustomImageService.relinkActiveTemplate).toHaveBeenCalledWith({ environmentId: "env-1", confirmBootSourceDrift: true, actor: { actorType: "user", actorId: "user-1", agentId: null, runId: null, agentApiKeyId: null, }, companyId: "company-1", }); }); it("defaults the confirmation flag to false and rejects unknown body keys", async () => { const app = createApp(boardActor()); const withoutFlag = await request(app) .post("/api/environments/env-1/custom-image-template/relink?companyId=company-1") .send({}); expect(withoutFlag.status).toBe(200); expect(mockEnvironmentCustomImageService.relinkActiveTemplate).toHaveBeenCalledExactlyOnceWith( expect.objectContaining({ confirmBootSourceDrift: false }), ); // The strict schema rejects unknown keys before the handler runs, so the // relink service is never reached for the malformed body. const unknownKey = await request(app) .post("/api/environments/env-1/custom-image-template/relink?companyId=company-1") .send({ confirmBootSourceDrift: false, unexpected: true }); expect(unknownKey.status).not.toBe(200); expect(mockEnvironmentCustomImageService.relinkActiveTemplate).toHaveBeenCalledTimes(1); }); it("propagates the drift conflict from the relink service", async () => { mockEnvironmentCustomImageService.relinkActiveTemplate.mockImplementationOnce(async () => { throw new HttpError(409, "Confirm the relink to keep the captured snapshot.", { classification: "boot_source_drift", driftedPaths: [{ path: "image", from: "fake:base", to: "fake:other" }], }); }); const res = await request(createApp(boardActor())) .post("/api/environments/env-1/custom-image-template/relink?companyId=company-1") .send({}); expect(res.status).toBe(409); }); it("denies agent API key actors before the relink service is called", async () => { const res = await request(createApp(agentActor())) .post("/api/environments/env-1/custom-image-template/relink?companyId=company-1") .send({}); expect(res.status).toBe(403); expect(mockEnvironmentCustomImageService.relinkActiveTemplate).not.toHaveBeenCalled(); }); it("denies non-admin board users before the relink service is called", async () => { const res = await request(createApp(boardActor({ companyIds: ["company-2"], isInstanceAdmin: false, }))) .post("/api/environments/env-1/custom-image-template/relink?companyId=company-1") .send({}); expect(res.status).toBe(403); expect(mockEnvironmentCustomImageService.relinkActiveTemplate).not.toHaveBeenCalled(); }); });