Files
ttstd-infinite-canvas/canvas-agent/test/dreamina-cli-staging.test.ts

144 lines
6.7 KiB
TypeScript

import assert from "node:assert/strict";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test } from "node:test";
import { stageReferences } from "../src/dreamina-cli-staging.js";
import { DreaminaCliError } from "../src/dreamina-cli-process.js";
type MediaCase = {
kind: "image" | "video" | "audio";
extension: string;
header: Buffer;
field: "referenceImages" | "referenceVideos" | "referenceAudios";
};
const mediaCases: MediaCase[] = [
{ kind: "image", extension: ".png", header: Buffer.from("89504e470d0a1a0a", "hex"), field: "referenceImages" },
{ kind: "video", extension: ".mp4", header: Buffer.from("000000106674797069736f6d", "hex"), field: "referenceVideos" },
{ kind: "audio", extension: ".mp3", header: Buffer.from("49443304", "hex"), field: "referenceAudios" },
];
function requestFor(media: MediaCase, reference: string) {
return {
operation: "multimodal2video",
idempotencyKey: `dreamina-staging-${media.kind}-0001`,
prompt: "fixture",
modelVersion: "seedance2.5",
ratio: "16:9",
videoResolution: "720p",
duration: 4,
[media.field]: [reference],
} as Parameters<typeof stageReferences>[0];
}
async function rejectedCode(
media: MediaCase,
reference: string,
root: string,
stateRoot: string,
) {
let result: Awaited<ReturnType<typeof stageReferences>> | undefined;
let error: unknown;
try {
result = await stageReferences(requestFor(media, reference), [root], stateRoot);
} catch (caught) {
error = caught;
} finally {
await result?.cleanup();
}
return error instanceof DreaminaCliError ? error.code : "accepted";
}
test("Dreamina safe staging rejects traversal, root escape, links, oversize, header mismatch, and TOCTOU for all media groups", async () => {
const box = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-staging-security-"));
const owned = path.join(box, "owned");
const outside = path.join(box, "outside");
const stateRoot = path.join(box, "state");
await Promise.all([fs.mkdir(owned), fs.mkdir(outside)]);
const failures: string[] = [];
try {
for (const media of mediaCases) {
const valid = path.join(owned, `valid-${media.kind}${media.extension}`);
await fs.writeFile(valid, media.header);
const nested = path.join(owned, `nested-${media.kind}`);
await fs.mkdir(nested);
const traversal = `${nested}${path.sep}..${path.sep}${path.basename(valid)}`;
if (await rejectedCode(media, traversal, owned, stateRoot) !== "dreamina_reference_invalid") {
failures.push(`${media.kind}:traversal`);
}
const escaped = path.join(outside, `escaped-${media.kind}${media.extension}`);
await fs.writeFile(escaped, media.header);
if (await rejectedCode(media, escaped, owned, stateRoot) !== "dreamina_reference_invalid") {
failures.push(`${media.kind}:root-escape`);
}
const symbolic = path.join(owned, `symbolic-${media.kind}${media.extension}`);
await fs.symlink(valid, symbolic, "file");
if (await rejectedCode(media, symbolic, owned, stateRoot) !== "dreamina_reference_invalid") {
failures.push(`${media.kind}:symlink`);
}
const hardSource = path.join(owned, `hard-source-${media.kind}${media.extension}`);
const hardLink = path.join(owned, `hard-link-${media.kind}${media.extension}`);
await fs.writeFile(hardSource, media.header);
await fs.link(hardSource, hardLink);
if (await rejectedCode(media, hardLink, owned, stateRoot) !== "dreamina_reference_invalid") {
failures.push(`${media.kind}:hardlink`);
}
const oversized = path.join(owned, `oversized-${media.kind}${media.extension}`);
const oversizedHandle = await fs.open(oversized, "w");
await oversizedHandle.write(media.header, 0, media.header.length, 0);
await oversizedHandle.truncate((media.kind === "image" ? 32 : 512) * 1024 * 1024 + 1);
await oversizedHandle.close();
if (await rejectedCode(media, oversized, owned, stateRoot) !== "dreamina_reference_invalid") {
failures.push(`${media.kind}:oversize`);
}
const mismatch = path.join(owned, `mismatch-${media.kind}${media.extension}`);
await fs.writeFile(mismatch, Buffer.from("not-media-header"));
if (await rejectedCode(media, mismatch, owned, stateRoot) !== "dreamina_reference_invalid") {
failures.push(`${media.kind}:header-mismatch`);
}
const changing = path.join(owned, `changing-${media.kind}${media.extension}`);
const changingHandle = await fs.open(changing, "w");
await changingHandle.write(media.header, 0, media.header.length, 0);
await changingHandle.truncate(16 * 1024 * 1024);
await changingHandle.close();
const staging = rejectedCode(media, changing, owned, stateRoot);
const mutator = (async () => {
for (let attempt = 0; attempt < 40; attempt += 1) {
await new Promise((resolve) => setTimeout(resolve, 1));
await fs.appendFile(changing, Buffer.from([attempt]));
}
})();
const [toctouCode] = await Promise.all([staging, mutator]);
if (toctouCode !== "dreamina_reference_invalid") failures.push(`${media.kind}:toctou`);
const exchange = path.join(owned, `exchange-${media.kind}${media.extension}`);
const replacement = path.join(owned, `exchange-replacement-${media.kind}${media.extension}`);
const parked = path.join(owned, `exchange-parked-${media.kind}${media.extension}`);
for (const candidate of [exchange, replacement]) {
const handle = await fs.open(candidate, "w");
await handle.write(media.header, 0, media.header.length, 0);
await handle.truncate(16 * 1024 * 1024);
await handle.close();
}
const exchanging = rejectedCode(media, exchange, owned, stateRoot);
await new Promise((resolve) => setTimeout(resolve, 1));
await fs.rename(exchange, parked);
await fs.rename(replacement, exchange);
if (await exchanging !== "dreamina_reference_invalid") failures.push(`${media.kind}:path-exchange`);
}
assert.deepEqual(failures, []);
} finally {
await fs.rm(box, { recursive: true, force: true });
}
});