fix(models): reject oversized piped auth input (#109800)

readPipedStdin accumulated piped stdin without a size limit,
letting `models auth paste-token` and `paste-api-key` buffer
arbitrarily large input before any validation. Add a 1 MiB cap
matching the existing Matrix recovery-key and config-patch
stdin limits, and reject oversized input at the stream boundary.
This commit is contained in:
pick-cat
2026-07-18 13:05:30 +08:00
committed by GitHub
parent d2ac282f25
commit 6018174d8e
2 changed files with 26 additions and 4 deletions

View File

@@ -1473,6 +1473,21 @@ describe("modelsAuthLoginCommand", () => {
expect(mocks.updateConfig).not.toHaveBeenCalled();
});
it("rejects oversized piped auth input before buffering it", async () => {
const runtime = createRuntime();
restoreStdin?.();
const oversized = "x".repeat(1024 * 1024 + 1);
restoreStdin = withPipedStdin(oversized);
await expect(modelsAuthPasteApiKeyCommand({ provider: "openai" }, runtime)).rejects.toThrow(
"Piped auth input exceeds 1048576 bytes.",
);
expect(mocks.clackPassword).not.toHaveBeenCalled();
expect(mocks.upsertAuthProfileWithLock).not.toHaveBeenCalled();
expect(mocks.updateConfig).not.toHaveBeenCalled();
});
it("writes pasted API keys to the requested agent store", async () => {
const runtime = createRuntime();
useCoderAgentConfig();

View File

@@ -130,13 +130,20 @@ const password = async (params: Parameters<typeof clackPassword>[0]) =>
const select = async <T>(params: Parameters<typeof clackSelect<T>>[0]) =>
guardCancel(await clackSelect(styleSelectParams(params)));
const MODELS_AUTH_STDIN_MAX_BYTES = 1024 * 1024;
async function readPipedStdin(): Promise<string> {
process.stdin.setEncoding("utf8");
let input = "";
const chunks: Buffer[] = [];
let totalBytes = 0;
for await (const chunk of process.stdin) {
input += String(chunk);
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
totalBytes += buffer.byteLength;
if (totalBytes > MODELS_AUTH_STDIN_MAX_BYTES) {
throw new Error(`Piped auth input exceeds ${MODELS_AUTH_STDIN_MAX_BYTES} bytes.`);
}
chunks.push(buffer);
}
return input;
return Buffer.concat(chunks, totalBytes).toString("utf8");
}
async function readPastedSecret(params: {