Skip to content

Commit fa4e94c

Browse files
committed
fix(init): skip binary repository reads
1 parent 420102e commit fa4e94c

4 files changed

Lines changed: 68 additions & 11 deletions

File tree

packages/cli/src/lib/init/tools/read-files.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from "node:fs";
2+
import { isLikelyBinary } from "../../scan/index.js";
23
import { MAX_FILE_BYTES } from "../constants.js";
34
import type { ReadFilesPayload, ToolResult } from "../types.js";
45
import { safePath } from "./shared.js";
@@ -42,23 +43,39 @@ async function readSingleFile(
4243
if (!stat.isFile()) {
4344
return null;
4445
}
45-
if (stat.size <= maxBytes) {
46-
return await fs.promises.readFile(absPath, "utf-8");
47-
}
46+
const buffer =
47+
stat.size <= maxBytes
48+
? await fs.promises.readFile(absPath)
49+
: await readFilePrefix(absPath, maxBytes);
4850

49-
const handle = await fs.promises.open(absPath, "r");
50-
try {
51-
const buffer = Buffer.alloc(maxBytes);
52-
await handle.read(buffer, 0, maxBytes, 0);
53-
return buffer.toString("utf-8");
54-
} finally {
55-
await handle.close();
51+
// Repository listings can contain executables, archives, images, and
52+
// other binary artifacts. Never decode those bytes into a prompt string:
53+
// replacement characters and JSON escaping can turn a bounded file read
54+
// into a much larger workflow payload with no useful project context.
55+
if (isLikelyBinary(buffer)) {
56+
return null;
5657
}
58+
59+
return buffer.toString("utf-8");
5760
} catch {
5861
return null;
5962
}
6063
}
6164

65+
async function readFilePrefix(
66+
absPath: string,
67+
maxBytes: number
68+
): Promise<Buffer> {
69+
const handle = await fs.promises.open(absPath, "r");
70+
try {
71+
const buffer = Buffer.alloc(maxBytes);
72+
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
73+
return buffer.subarray(0, bytesRead);
74+
} finally {
75+
await handle.close();
76+
}
77+
}
78+
6279
/**
6380
* Tool definition for batched file reads.
6481
*/

packages/cli/src/lib/init/workflow-inputs.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import fs from "node:fs";
22
import path from "node:path";
3+
import { isLikelyBinary } from "../scan/index.js";
34
import { MAX_FILE_BYTES } from "./constants.js";
45
import { detectSentry } from "./tools/detect-sentry.js";
56
import { listDir } from "./tools/list-dir.js";
@@ -131,7 +132,12 @@ export async function preReadCommonFiles(
131132
if (stat.size > MAX_FILE_BYTES) {
132133
continue;
133134
}
134-
const content = await fs.promises.readFile(absPath, "utf-8");
135+
const buffer = await fs.promises.readFile(absPath);
136+
if (isLikelyBinary(buffer)) {
137+
cache[filePath] = null;
138+
continue;
139+
}
140+
const content = buffer.toString("utf-8");
135141
if (totalBytes + content.length <= MAX_PREREAD_TOTAL_BYTES) {
136142
cache[filePath] = content;
137143
totalBytes += content.length;

packages/cli/test/lib/init/tools/filesystem-tools.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,26 @@ describe("filesystem tools", () => {
103103
expect((existsResult.data as any).exists["missing.txt"]).toBe(false);
104104
});
105105

106+
test("does not decode binary files into workflow prompt content", async () => {
107+
fs.writeFileSync(
108+
path.join(testDir, "sentry-wizard"),
109+
Buffer.from([0x7f, 0x45, 0x4c, 0x46, 0x02, 0x00, 0xff])
110+
);
111+
112+
const result = await executeTool(
113+
{
114+
type: "tool",
115+
operation: "read-files",
116+
cwd: testDir,
117+
params: { paths: ["sentry-wizard"] },
118+
},
119+
makeContext(testDir)
120+
);
121+
122+
expect(result.ok).toBe(true);
123+
expect((result.data as any).files["sentry-wizard"]).toBeNull();
124+
});
125+
106126
test("applies patchsets and injects auth tokens into env files", async () => {
107127
const result = await executeTool(
108128
{

packages/cli/test/lib/safe-read.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,18 @@ describe("workflow-inputs preReadCommonFiles FIFO safety", () => {
272272
expect(cache["package.json"]).toBe('{"name":"x"}');
273273
expect(cache["tsconfig.json"]).toBeNull();
274274
});
275+
276+
test("does not pre-read binary content disguised as a common config", async () => {
277+
writeFileSync(
278+
join(dir, "package.json"),
279+
Buffer.from([0x7f, 0x45, 0x4c, 0x46, 0x02, 0x00, 0xff])
280+
);
281+
282+
const listing: DirEntry[] = [
283+
{ name: "package.json", path: "package.json", type: "file" },
284+
];
285+
const cache = await preReadCommonFiles(dir, listing);
286+
287+
expect(cache["package.json"]).toBeNull();
288+
});
275289
});

0 commit comments

Comments
 (0)