mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-31 00:52:36 +00:00
fix(scripts): bound gh read error bodies
This commit is contained in:
@@ -10,6 +10,7 @@ const INSTALLATION_ID_ENV = "OPENCLAW_GH_READ_INSTALLATION_ID";
|
||||
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 DEFAULT_READ_PERMISSION_KEYS = [
|
||||
"actions",
|
||||
"checks",
|
||||
@@ -190,6 +191,45 @@ async function withGitHubFetchTimeout<T>(
|
||||
}
|
||||
}
|
||||
|
||||
export async function readBoundedGitHubErrorText(
|
||||
response: Response,
|
||||
maxChars = GITHUB_ERROR_BODY_MAX_CHARS,
|
||||
): Promise<string> {
|
||||
if (!response.body) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let text = "";
|
||||
let truncated = false;
|
||||
|
||||
try {
|
||||
while (text.length <= maxChars) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
text += decoder.decode();
|
||||
break;
|
||||
}
|
||||
|
||||
text += decoder.decode(value, { stream: true });
|
||||
if (text.length > maxChars) {
|
||||
text = text.slice(0, maxChars);
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (truncated) {
|
||||
await reader.cancel().catch(() => undefined);
|
||||
} else {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
return truncated ? `${text}\n[truncated]` : text;
|
||||
}
|
||||
|
||||
export async function githubJson<T>(
|
||||
path: string,
|
||||
bearerToken: string,
|
||||
@@ -219,7 +259,7 @@ export async function githubJson<T>(
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
const text = await readBoundedGitHubErrorText(response);
|
||||
fail(`${init?.method ?? "GET"} ${path} failed (${response.status}): ${text}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
normalizeRepo,
|
||||
parsePermissionKeys,
|
||||
parseRepoArg,
|
||||
readBoundedGitHubErrorText,
|
||||
resolveGitHubFetchTimeoutMs,
|
||||
} from "../../scripts/gh-read.js";
|
||||
|
||||
@@ -80,6 +81,19 @@ describe("gh-read helpers", () => {
|
||||
await expect(request).rejects.toThrow(/GitHub API GET \/app\/installations exceeded timeout/u);
|
||||
});
|
||||
|
||||
it("bounds GitHub API error response bodies", async () => {
|
||||
const tail = "tail-sentinel-should-not-appear";
|
||||
const response = new Response(`${"x".repeat(5000)}${tail}`, {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
const text = await readBoundedGitHubErrorText(response);
|
||||
|
||||
expect(text).toContain("[truncated]");
|
||||
expect(text).not.toContain(tail);
|
||||
expect(text.length).toBeLessThan(4200);
|
||||
});
|
||||
|
||||
it("rejects invalid GitHub API timeout values", () => {
|
||||
expect(resolveGitHubFetchTimeoutMs("1000")).toBe(1000);
|
||||
expect(() => resolveGitHubFetchTimeoutMs("1s")).toThrow(
|
||||
|
||||
Reference in New Issue
Block a user