111 lines
4.6 KiB
TypeScript
111 lines
4.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { chmod, lstat, mkdir, readFile, readdir, symlink } from "node:fs/promises";
|
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join } from "node:path";
|
|
import { test } from "node:test";
|
|
import { FilePrivateArtifactStore } from "./artifact-store.ts";
|
|
|
|
async function temporaryRoot(): Promise<string> {
|
|
return mkdtemp(join(tmpdir(), "fast-browser-artifacts-test-"));
|
|
}
|
|
|
|
const owner = {
|
|
sessionId: "session/with-secret?no-path",
|
|
runId: "run/../../not-a-directory",
|
|
operationId: "operation-1",
|
|
} as const;
|
|
|
|
test("commits bounded private artifacts atomically with sanitized ownership paths", async () => {
|
|
const root = await temporaryRoot();
|
|
const store = new FilePrivateArtifactStore({ root, maxArtifactBytes: 32 });
|
|
try {
|
|
const writer = await store.open(owner, "diagnostics");
|
|
await writer.write(new TextEncoder().encode('{"level":"error"}'));
|
|
const metadata = await writer.commit();
|
|
assert.equal(metadata.kind, "diagnostics");
|
|
assert.equal(metadata.contentType, "application/json");
|
|
assert.equal(metadata.bytes, 17);
|
|
assert.equal(metadata.permissions, "0600");
|
|
assert.equal(metadata.localOnly, true);
|
|
assert.equal(metadata.modelForwarded, false);
|
|
assert.equal(metadata.owner.runId, owner.runId);
|
|
assert.ok(!metadata.path.includes(".."));
|
|
assert.ok(metadata.path.startsWith(root));
|
|
const stat = await lstat(metadata.path);
|
|
assert.equal(stat.isFile(), true);
|
|
assert.equal(stat.mode & 0o777, 0o600);
|
|
assert.equal(await readFile(metadata.path, "utf8"), '{"level":"error"}');
|
|
assert.equal((await readdir(root)).length, 1);
|
|
} finally {
|
|
await store.close();
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("rejects oversized chunks and removes incomplete private files", async () => {
|
|
const root = await temporaryRoot();
|
|
const store = new FilePrivateArtifactStore({ root, maxArtifactBytes: 4 });
|
|
try {
|
|
const writer = await store.open({ sessionId: "s", runId: "r", operationId: "o" }, "screenshot");
|
|
await assert.rejects(writer.write(new Uint8Array(5)), /size limit/);
|
|
await assert.rejects(writer.commit(), /closed/);
|
|
const sessionEntries = await readdir(root);
|
|
assert.equal(sessionEntries.length, 1);
|
|
const runEntries = await readdir(join(root, sessionEntries[0]!, (await readdir(join(root, sessionEntries[0]!)))[0]!));
|
|
assert.equal(runEntries.length, 0);
|
|
} finally {
|
|
await store.close();
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("aborts open writers on store close and does not expose foreign paths", async () => {
|
|
const root = await temporaryRoot();
|
|
const foreign = await temporaryRoot();
|
|
const store = new FilePrivateArtifactStore(root);
|
|
try {
|
|
const writer = await store.open({ sessionId: "s", runId: "r", operationId: "o" }, "diagnostics");
|
|
await writer.write(new TextEncoder().encode("private"));
|
|
await store.close();
|
|
await assert.rejects(writer.write(new Uint8Array([1])), /closed/);
|
|
const sessionPath = join(root, (await readdir(root))[0]!);
|
|
const runPath = join(sessionPath, (await readdir(sessionPath))[0]!);
|
|
assert.deepEqual(await readdir(runPath), []);
|
|
await assert.rejects(store.open({ sessionId: "foreign", runId: "r", operationId: "o" }, "diagnostics"), /closed/);
|
|
assert.equal(await readdir(foreign).then((items) => items.length), 0);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
await rm(foreign, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("fails closed for a symlink in the artifact root path", async () => {
|
|
const parent = await temporaryRoot();
|
|
const target = await temporaryRoot();
|
|
const root = join(parent, "root-link");
|
|
await symlink(target, root);
|
|
const store = new FilePrivateArtifactStore(root);
|
|
try {
|
|
await assert.rejects(store.open({ sessionId: "s", runId: "r", operationId: "o" }, "diagnostics"), /symlink/);
|
|
} finally {
|
|
await store.close();
|
|
await rm(parent, { recursive: true, force: true });
|
|
await rm(target, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("rejects broad permissions on an existing root without chmodding its parent", async () => {
|
|
const root = await temporaryRoot();
|
|
const parentMode = (await lstat(dirname(root))).mode & 0o777;
|
|
await chmod(root, 0o755);
|
|
const store = new FilePrivateArtifactStore(root);
|
|
try {
|
|
await assert.rejects(store.open({ sessionId: "s", runId: "r", operationId: "o" }, "diagnostics"), /permissions/);
|
|
assert.equal((await lstat(dirname(root))).mode & 0o777, parentMode);
|
|
} finally {
|
|
await store.close();
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|