fix: harden native workspace root and docker gates

This commit is contained in:
2026-08-11 14:11:42 +02:00
parent ee87a59e1f
commit a5916f6177
16 changed files with 401 additions and 96 deletions
+30 -2
View File
@@ -1,2 +1,30 @@
import { describe, expect, it } from "vitest"; import { mkdtempSync,mkdirSync } from "node:fs"; import {join} from "node:path"; import {tmpdir,getuid} from "node:os"; import {WorkspaceFsAtV1} from "../src/workspaces/workspace-fs-at.js"; import {VerifiedWorkspaceLockRootLeaseFactory} from "../src/workspaces/workspace-lock-root-lease.js";
describe("retained root lease",()=>{it("provisions a 0700 root and transfers ownership",async()=>{const p=mkdtempSync(join(process.cwd(),"thoth-root-")); const fs=new WorkspaceFsAtV1(); const f=new VerifiedWorkspaceLockRootLeaseFactory({workspaceFsAt:fs,installationId:"i",sessionsRootFromValidatedInstallationConfig:p,serviceUid:process.getuid!(),provisionedWorkspaceMode:0o700}); const l=await f.acquireOrProvision(f.canonicalInput("abc-workspace")); expect(l.identity.workspaceId).toBe("abc-workspace"); const t=l.transfer(); await expect(l.close()).resolves.toBeUndefined(); await t.close();});});
import { describe, expect, it, afterEach } from "vitest";
import { mkdtempSync, renameSync, mkdirSync, rmSync, statSync, chmodSync, symlinkSync } from "node:fs";
import { join } from "node:path";
import { WorkspaceFsAtV1 } from "../src/workspaces/workspace-fs-at.js";
import { VerifiedWorkspaceLockRootLeaseFactory } from "../src/workspaces/workspace-lock-root-lease.js";
const roots: string[] = [];
afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
function factory(sessionsRootFromValidatedInstallationConfig: string) { return new VerifiedWorkspaceLockRootLeaseFactory({ workspaceFsAt: new WorkspaceFsAtV1(), installationId: "i", sessionsRootFromValidatedInstallationConfig, serviceUid: process.getuid!(), provisionedWorkspaceMode: 0o700 }); }
describe("retained canonical workspace root", () => {
it("provisions exact identity, transfers once, and rejects a second owner", async () => {
const parent = mkdtempSync(join(process.cwd(), "thoth-root-")); roots.push(parent); const f = factory(parent); const input = f.canonicalInput("abc-workspace");
const lease = await f.acquireOrProvision(input); const st = statSync(join(parent, "abc-workspace")); expect(st.uid).toBe(process.getuid!()); expect(st.mode & 0o777).toBe(0o700); expect(lease.identity.inode).toBe(BigInt(st.ino));
const transferred = lease.transfer(); expect(() => lease.transfer()).toThrow(); await transferred.close();
expect(() => f.canonicalInput("../outside")).toThrow(); expect(() => f.canonicalInput("/tmp/x")).toThrow();
});
it("fails closed when the canonical pathname is replaced after retention", async () => {
const parent = mkdtempSync(join(process.cwd(), "thoth-root-")); roots.push(parent); const f = factory(parent); const lease = await f.acquireOrProvision(f.canonicalInput("abc-workspace"));
renameSync(join(parent, "abc-workspace"), join(parent, "old")); mkdirSync(join(parent, "abc-workspace"), { mode: 0o700 });
await expect(lease.acquireWriterLock()).rejects.toThrow(/preprocessing/); await lease.close();
});
it("does not accept a symlink or wrong ownership/mode root", async () => {
const parent = mkdtempSync(join(process.cwd(), "thoth-root-")); roots.push(parent); const other = mkdtempSync(join(process.cwd(), "thoth-other-")); roots.push(other); symlinkSync(other, join(parent, "abc-workspace"));
const f = factory(parent); await expect(f.acquireOrProvision(f.canonicalInput("abc-workspace"))).rejects.toThrow(); rmSync(join(parent, "abc-workspace")); mkdirSync(join(parent, "abc-workspace"), { mode: 0o755 }); await expect(f.acquireOrProvision(f.canonicalInput("abc-workspace"))).rejects.toThrow();
});
it("closes every transferred root on partial ordered acquisition", async () => {
const parent = mkdtempSync(join(process.cwd(), "thoth-root-")); roots.push(parent); const f = factory(parent); const a = await f.acquireOrProvision(f.canonicalInput("aaa-workspace")); const b = await f.acquireOrProvision(f.canonicalInput("bbb-workspace"));
const { runUnderOrderedWorkspaceWriterLocks } = await import("../src/workspaces/preprocessing-state.js"); await runUnderOrderedWorkspaceWriterLocks([a, b], async set => { expect(set.workspaceIds).toEqual(["aaa-workspace", "bbb-workspace"]); }); await expect(b.close()).resolves.toBeUndefined();
});
});