Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/control-plane/src/sandbox/lifecycle/manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,33 @@ describe("SandboxLifecycleManager", () => {
expect(storage.calls).toContain("updateSandboxStatus:failed");
});

it("fails spawn when getUserEnvVars rejects", async () => {
const sandbox = createMockSandbox({ status: "pending", created_at: Date.now() - 60000 });
const storage = createMockStorage(createMockSession(), sandbox);
storage.getUserEnvVars = vi.fn(async () => {
throw new Error("D1 decryption failure");
});
const broadcaster = createMockBroadcaster();
const wsManager = createMockWebSocketManager(false);
const provider = createMockProvider();

const manager = new SandboxLifecycleManager(
provider,
storage,
broadcaster,
wsManager,
createMockAlarmScheduler(),
createMockIdGenerator(),
createTestConfig()
);

await manager.spawnSandbox();

expect(provider.createSandbox).not.toHaveBeenCalled();
expect(storage.calls).toContain("updateSandboxStatus:failed");
expect(manager.isSpawning()).toBe(false);
});

it("skips spawn when already spawning", async () => {
const sandbox = createMockSandbox({ status: "spawning" });
const storage = createMockStorage(createMockSession(), sandbox);
Expand Down
29 changes: 6 additions & 23 deletions packages/control-plane/src/session/durable-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1686,30 +1686,13 @@ export class SessionDO extends DurableObject<Env> {
return undefined;
}

// Fetch global secrets
let globalSecrets: Record<string, string> = {};
try {
const globalStore = new GlobalSecretsStore(this.env.DB, this.env.REPO_SECRETS_ENCRYPTION_KEY);
globalSecrets = await globalStore.getDecryptedSecrets();
} catch (e) {
this.log.error("Failed to load global secrets, proceeding without", {
error: e instanceof Error ? e.message : String(e),
});
}
// Fail hard on secret loading — sandboxes must not silently lose secrets
const globalStore = new GlobalSecretsStore(this.env.DB, this.env.REPO_SECRETS_ENCRYPTION_KEY);
const globalSecrets = await globalStore.getDecryptedSecrets();

// Fetch repo secrets
let repoSecrets: Record<string, string> = {};
try {
const repoId = await this.ensureRepoId(session);
const repoStore = new RepoSecretsStore(this.env.DB, this.env.REPO_SECRETS_ENCRYPTION_KEY);
repoSecrets = await repoStore.getDecryptedSecrets(repoId);
} catch (e) {
this.log.warn("Failed to load repo secrets, proceeding without", {
repo_owner: session.repo_owner,
repo_name: session.repo_name,
error: e instanceof Error ? e.message : String(e),
});
}
const repoId = await this.ensureRepoId(session);
Comment thread
ColeMurray marked this conversation as resolved.
const repoStore = new RepoSecretsStore(this.env.DB, this.env.REPO_SECRETS_ENCRYPTION_KEY);
const repoSecrets = await repoStore.getDecryptedSecrets(repoId);

// Merge: repo overrides global
const { merged, totalBytes, exceedsLimit } = mergeSecrets(globalSecrets, repoSecrets);
Expand Down
Loading