import { createHash } from "node:crypto"; import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createNativeSshCommandRunner } from "./native-ssh-command-runner.js"; import { MAX_REMOTE_DELIVERABLE_BYTES, readVerifiedRemoteWorkspaceFile } from "./remote-deliverable-file.js"; describe("native SSH deliverable output budget", () => { let root: string; beforeEach(async () => { root = await mkdtemp(join(tmpdir(), "paperclip-ssh-output-")); // Exercise the real SSH command adapter and its execFile output limit, // replacing only the network executable with a deterministic byte source. const executable = join(root, "ssh"); await writeFile(executable, `#!${process.execPath}\nprocess.stdout.write(require('node:fs').readFileSync(${JSON.stringify(join(root, "response"))}));\n`); await chmod(executable, 0o700); vi.stubEnv("PATH", `${root}:${process.env.PATH}`); }); afterEach(async () => { vi.unstubAllEnvs(); await rm(root, { recursive: true, force: true }); }); const createRunner = () => createNativeSshCommandRunner({ spec: { host: "fixture.invalid", port: 22, username: "fixture", remoteWorkspacePath: "/workspace", remoteCwd: "/workspace", privateKey: null, knownHosts: null, strictHostKeyChecking: true, }, defaultCwd: "/workspace", }); it.each([64, MAX_REMOTE_DELIVERABLE_BYTES])("returns exact verified bytes for a %i-byte file through the SSH adapter", async (byteSize) => { const body = Buffer.alloc(byteSize, 65); await writeFile(join(root, "response"), body.toString("base64")); const result = await readVerifiedRemoteWorkspaceFile({ runner: createRunner(), workspaceRoot: "/workspace", contentRef: "result.md", byteSize, sha256: createHash("sha256").update(body).digest("hex"), }); expect(result.equals(body)).toBe(true); }); it("stops a remote command that exceeds the maximum encoded envelope", async () => { await writeFile(join(root, "response"), Buffer.alloc(4 * Math.ceil(MAX_REMOTE_DELIVERABLE_BYTES / 3) + 1, 65)); const result = await createRunner().execute({ command: "node", args: ["unused"], timeoutMs: 10_000 }); expect(result.exitCode).not.toBe(0); }); it.each([0, 23])("rejects a large upload after an early SSH exit with code %i", async (exitCode) => { await writeFile(join(root, "ssh"), `#!${process.execPath}\nprocess.exit(${exitCode});\n`); const result = await createRunner().execute({ command: "node", args: ["unused"], timeoutMs: 5000, stdin: "A".repeat(4 * Math.ceil(MAX_REMOTE_DELIVERABLE_BYTES / 3)), }); expect(result.exitCode).not.toBe(0); expect(result.timedOut).toBe(false); }); it("still delivers every byte when SSH consumes the upload", async () => { await writeFile(join(root, "ssh"), `#!${process.execPath}\nconst hash = require('node:crypto').createHash('sha256');\nprocess.stdin.on('data', chunk => hash.update(chunk));\nprocess.stdin.on('end', () => process.stdout.write(hash.digest('hex')));\n`); const stdin = "synthetic upload bytes\0".repeat(32_768); const result = await createRunner().execute({ command: "node", args: ["unused"], stdin, timeoutMs: 5000 }); expect(result.exitCode).toBe(0); expect(result.stdout).toBe(createHash("sha256").update(stdin).digest("hex")); }); });