mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-10 07:52:56 +00:00
fix(scripts): cap gh-read json bodies
This commit is contained in:
@@ -2,6 +2,7 @@ import { execFileSync, spawnSync } from "node:child_process";
|
||||
import { createPrivateKey, createSign } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { readBoundedResponseText } from "./lib/bounded-response.ts";
|
||||
import { parseStrictIntegerOption } from "./lib/dev-tooling-safety.ts";
|
||||
|
||||
const APP_ID_ENV = "OPENCLAW_GH_READ_APP_ID";
|
||||
@@ -11,6 +12,7 @@ const PERMISSIONS_ENV = "OPENCLAW_GH_READ_PERMISSIONS";
|
||||
const API_VERSION = "2022-11-28";
|
||||
const DEFAULT_GITHUB_FETCH_TIMEOUT_MS = 30_000;
|
||||
const GITHUB_ERROR_BODY_MAX_CHARS = 4096;
|
||||
const GITHUB_JSON_BODY_MAX_BYTES = 1024 * 1024;
|
||||
const DEFAULT_READ_PERMISSION_KEYS = [
|
||||
"actions",
|
||||
"checks",
|
||||
@@ -230,6 +232,19 @@ export async function readBoundedGitHubErrorText(
|
||||
return truncated ? `${text}\n[truncated]` : text;
|
||||
}
|
||||
|
||||
export async function readBoundedGitHubJson<T>(
|
||||
response: Response,
|
||||
maxBytes = GITHUB_JSON_BODY_MAX_BYTES,
|
||||
): Promise<T> {
|
||||
const text = await readBoundedResponseText(response, "GitHub API", maxBytes, {
|
||||
createTooLargeError: (message) =>
|
||||
Object.assign(new Error(message), {
|
||||
code: "ETOOBIG",
|
||||
}),
|
||||
});
|
||||
return JSON.parse(text) as T;
|
||||
}
|
||||
|
||||
export async function githubJson<T>(
|
||||
path: string,
|
||||
bearerToken: string,
|
||||
@@ -263,7 +278,7 @@ export async function githubJson<T>(
|
||||
fail(`${init?.method ?? "GET"} ${path} failed (${response.status}): ${text}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as T;
|
||||
return await readBoundedGitHubJson<T>(response);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
parsePermissionKeys,
|
||||
parseRepoArg,
|
||||
readBoundedGitHubErrorText,
|
||||
readBoundedGitHubJson,
|
||||
resolveGitHubFetchTimeoutMs,
|
||||
} from "../../scripts/gh-read.js";
|
||||
|
||||
@@ -68,11 +69,7 @@ describe("gh-read helpers", () => {
|
||||
});
|
||||
|
||||
it("times out stalled GitHub API response body reads", async () => {
|
||||
const response = {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () => new Promise(() => {}),
|
||||
} as Response;
|
||||
const response = new Response(new ReadableStream({}), { status: 200 });
|
||||
const request = githubJson("/app/installations", "token", undefined, {
|
||||
timeoutMs: 5,
|
||||
fetchImpl: (() => Promise.resolve(response)) as typeof fetch,
|
||||
@@ -94,6 +91,53 @@ describe("gh-read helpers", () => {
|
||||
expect(text.length).toBeLessThan(4200);
|
||||
});
|
||||
|
||||
it("reads bounded GitHub API JSON responses", async () => {
|
||||
await expect(readBoundedGitHubJson(new Response('{"id":123}'), 1024)).resolves.toEqual({
|
||||
id: 123,
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects oversized GitHub API JSON responses by content length", async () => {
|
||||
let canceled = false;
|
||||
const response = new Response(
|
||||
new ReadableStream({
|
||||
cancel() {
|
||||
canceled = true;
|
||||
},
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"content-length": "1025",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await expect(readBoundedGitHubJson(response, 1024)).rejects.toMatchObject({
|
||||
code: "ETOOBIG",
|
||||
message: "GitHub API response body exceeded 1024 bytes",
|
||||
});
|
||||
expect(canceled).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects oversized streamed GitHub API JSON responses", async () => {
|
||||
const encoder = new TextEncoder();
|
||||
const response = new Response(
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(encoder.encode('{"body":"'));
|
||||
controller.enqueue(encoder.encode("x".repeat(1024)));
|
||||
controller.enqueue(encoder.encode('"}'));
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(readBoundedGitHubJson(response, 1024)).rejects.toMatchObject({
|
||||
code: "ETOOBIG",
|
||||
message: "GitHub API response body exceeded 1024 bytes",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects invalid GitHub API timeout values", () => {
|
||||
expect(resolveGitHubFetchTimeoutMs("1000")).toBe(1000);
|
||||
expect(() => resolveGitHubFetchTimeoutMs("1s")).toThrow(
|
||||
|
||||
Reference in New Issue
Block a user