fix(cli): bound exec approvals stdin

This commit is contained in:
Vincent Koc
2026-06-07 04:08:13 +02:00
parent 51b64b8198
commit 801df108f0
2 changed files with 27 additions and 5 deletions

View File

@@ -1,9 +1,10 @@
// Exec approvals CLI tests cover approval command registration and output handling.
import { Readable } from "node:stream";
import { Command } from "commander";
import { beforeEach, describe, expect, it, vi } from "vitest";
import * as execApprovals from "../infra/exec-approvals.js";
import type { ExecApprovalsFile } from "../infra/exec-approvals.js";
import { registerExecApprovalsCli } from "./exec-approvals-cli.js";
import { registerExecApprovalsCli, testing } from "./exec-approvals-cli.js";
const mocks = vi.hoisted(() => {
const runtimeErrors: string[] = [];
@@ -560,4 +561,11 @@ describe("exec approvals CLI", () => {
});
expect(runtimeErrors).toHaveLength(0);
});
it("bounds approvals JSON read from stdin", async () => {
await expect(testing.readStdin(Readable.from(["12345"]), 5)).resolves.toBe("12345");
await expect(testing.readStdin(Readable.from(["12345", "6"]), 5)).rejects.toThrow(
"Exec approvals stdin exceeds 5 bytes.",
);
});
});

View File

@@ -46,6 +46,7 @@ type EffectivePolicyReport = {
note?: string;
};
const APPROVALS_GET_DEFAULT_TIMEOUT_MS = 60_000;
const EXEC_APPROVALS_STDIN_MAX_BYTES = 1024 * 1024;
type ExecApprovalsCliOpts = NodesRpcOpts & {
node?: string;
@@ -55,12 +56,21 @@ type ExecApprovalsCliOpts = NodesRpcOpts & {
agent?: string;
};
async function readStdin(): Promise<string> {
async function readStdin(
stream: NodeJS.ReadableStream = process.stdin,
maxBytes = EXEC_APPROVALS_STDIN_MAX_BYTES,
): Promise<string> {
const chunks: Buffer[] = [];
for await (const chunk of process.stdin) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
let total = 0;
for await (const chunk of stream) {
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
total += buffer.byteLength;
if (total > maxBytes) {
throw new Error(`Exec approvals stdin exceeds ${maxBytes} bytes.`);
}
chunks.push(buffer);
}
return Buffer.concat(chunks).toString("utf8");
return Buffer.concat(chunks, total).toString("utf8");
}
async function resolveTargetNodeId(opts: ExecApprovalsCliOpts): Promise<string | null> {
@@ -622,3 +632,7 @@ export function registerExecApprovalsCli(program: Command) {
applyParentDefaultHelpAction(approvals);
}
export const testing = {
readStdin,
};