1// @effect-diagnostics nodeBuiltinImport:off
2import * as NodeFS from "node:fs";
3import * as NodeOS from "node:os";
4import * as NodePath from "node:path";
5import * as NodeChildProcess from "node:child_process";
6import * as HostProcess from "@t3tools/shared/HostProcess";
7import { it as effectIt } from "@effect/vitest";
8import * as Effect from "effect/Effect";
9import { afterAll, assert, describe } from "vite-plus/test";
10import { symlinksSupported } from "@t3tools/shared/testing/symlinks";
11import { readWorkflowScript } from "./workflowScriptQuery.ts";
12
13const root = NodePath.join(NodeOS.homedir(), ".claude", "projects", "__wf_script_test__");
14NodeFS.mkdirSync(root, { recursive: true });
15const scriptPath = NodePath.join(root, "run.js");
16NodeFS.writeFileSync(scriptPath, "export const meta = {};\n");
17const outside = NodePath.join(NodeOS.tmpdir(), "wf-outside.js");
18NodeFS.writeFileSync(outside, "evil\n");
19const link = NodePath.join(root, "sneaky.js");
20if (symlinksSupported) {
21 NodeFS.rmSync(link, { force: true });
22 NodeFS.symlinkSync(outside, link);
23 if (!NodeFS.lstatSync(link).isSymbolicLink()) {
24 throw new Error("test setup: sneaky.js must be a symlink");
25 }
26}
27
28afterAll(() => {
29 NodeFS.rmSync(root, { recursive: true, force: true });
30 NodeFS.rmSync(outside, { force: true });
31});
32
33describe("workflow script containment", () => {
34 effectIt.effect.skipIf(HostProcess.Platform.defaultValue() === "win32")(
35 "rejects a FIFO without waiting for a writer",
36 () =>
37 Effect.gen(function* () {
38 const fifo = NodePath.join(root, "pipe.js");
39 NodeChildProcess.execFileSync("mkfifo", [fifo]);
40 const error = yield* Effect.flip(readWorkflowScript({ scriptPath: fifo }));
41 assert.equal(error.reason, "not-regular-file");
42 }),
43 );
44
45 effectIt.effect("serves a real script under the projects root", () =>
46 Effect.gen(function* () {
47 const result = yield* readWorkflowScript({ scriptPath });
48 assert.include(result.contents, "export const meta");
49 assert.equal(result.truncated, false);
50 }),
51 );
52
53 effectIt.effect.skipIf(!symlinksSupported)(
54 "rejects paths outside the root and symlink escapes",
55 () =>
56 Effect.gen(function* () {
57 const escaped = yield* Effect.exit(readWorkflowScript({ scriptPath: outside }));
58 assert.equal(escaped._tag, "Failure");
59 const sneaky = yield* Effect.exit(
60 readWorkflowScript({ scriptPath: link }).pipe(
61 Effect.flip,
62 Effect.map((error) => error.reason),
63 ),
64 );
65 assert.equal(sneaky._tag, "Success");
66 if (sneaky._tag === "Success") {
67 assert.equal(sneaky.value, "outside-root");
68 }
69 }),
70 );
71});