mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 02:06:43 +00:00
fix(scripts): bound GHSA patch GitHub lookups (#110756)
* fix(scripts): bound GHSA patch subprocesses Co-authored-by: 唐梓夷0668001293 <tang.ziyi@xydigit.com> * fix(scripts): satisfy GHSA runner control-flow lint --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
co-authored by
Peter Steinberger
parent
ed458f14dc
commit
828bcd8231
+25
-16
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
// Applies GHSA patch payloads to advisory branches.
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { GHSA_COMMAND_TIMEOUT_MS, runGhCommand } from "./lib/ghsa-patch-subprocess.mjs";
|
||||
|
||||
function usage() {
|
||||
console.error(
|
||||
@@ -44,15 +45,19 @@ function parseArgs(argv) {
|
||||
}
|
||||
|
||||
function runGh(args) {
|
||||
const proc = spawnSync("gh", args, { encoding: "utf8" });
|
||||
if (proc.status !== 0) {
|
||||
fail(proc.stderr.trim() || proc.stdout.trim() || `gh ${args.join(" ")} failed`);
|
||||
try {
|
||||
return runGhCommand(args);
|
||||
} catch (error) {
|
||||
return fail(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
return proc.stdout;
|
||||
}
|
||||
|
||||
function deriveRepoFromOrigin() {
|
||||
const remote = execFileSync("git", ["remote", "get-url", "origin"], { encoding: "utf8" }).trim();
|
||||
const remote = execFileSync("git", ["remote", "get-url", "origin"], {
|
||||
encoding: "utf8",
|
||||
killSignal: "SIGKILL",
|
||||
timeout: GHSA_COMMAND_TIMEOUT_MS,
|
||||
}).trim();
|
||||
const httpsMatch = remote.match(/github\.com[/:]([^/]+)\/([^/.]+)(?:\.git)?$/);
|
||||
if (!httpsMatch) {
|
||||
fail(`Could not parse origin remote: ${remote}`);
|
||||
@@ -125,16 +130,20 @@ const payload = {
|
||||
};
|
||||
|
||||
const patchFile = writeTempJson(payload);
|
||||
runGh([
|
||||
"api",
|
||||
"-H",
|
||||
"X-GitHub-Api-Version: 2022-11-28",
|
||||
"-X",
|
||||
"PATCH",
|
||||
advisoryPath,
|
||||
"--input",
|
||||
patchFile,
|
||||
]);
|
||||
try {
|
||||
runGh([
|
||||
"api",
|
||||
"-H",
|
||||
"X-GitHub-Api-Version: 2022-11-28",
|
||||
"-X",
|
||||
"PATCH",
|
||||
advisoryPath,
|
||||
"--input",
|
||||
patchFile,
|
||||
]);
|
||||
} finally {
|
||||
fs.rmSync(patchFile, { force: true });
|
||||
}
|
||||
|
||||
if (restoredCvss) {
|
||||
runGh([
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
export const GHSA_COMMAND_TIMEOUT_MS: number;
|
||||
|
||||
export function runGhCommand(
|
||||
args: string[],
|
||||
params?: {
|
||||
spawnSyncImpl?: (
|
||||
command: string,
|
||||
args: string[],
|
||||
options: {
|
||||
encoding: "utf8";
|
||||
killSignal: "SIGKILL";
|
||||
timeout: number;
|
||||
},
|
||||
) => {
|
||||
error?: Error;
|
||||
status: number | null;
|
||||
stderr: string;
|
||||
stdout: string;
|
||||
};
|
||||
timeoutMs?: number;
|
||||
},
|
||||
): string;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
// GHSA patch performs multiple sequential GitHub API reads and writes. Keep enough
|
||||
// headroom for GitHub latency while preventing one stalled request from blocking
|
||||
// the maintainer command indefinitely.
|
||||
export const GHSA_COMMAND_TIMEOUT_MS = 60_000;
|
||||
|
||||
export function runGhCommand(args, params = {}) {
|
||||
const spawnSyncImpl = params.spawnSyncImpl ?? spawnSync;
|
||||
const proc = spawnSyncImpl("gh", args, {
|
||||
encoding: "utf8",
|
||||
killSignal: "SIGKILL",
|
||||
timeout: params.timeoutMs ?? GHSA_COMMAND_TIMEOUT_MS,
|
||||
});
|
||||
if (proc.error) {
|
||||
throw proc.error;
|
||||
}
|
||||
if (proc.status !== 0) {
|
||||
throw new Error(proc.stderr.trim() || proc.stdout.trim() || `gh ${args.join(" ")} failed`);
|
||||
}
|
||||
return proc.stdout;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { runGhCommand } from "../../scripts/lib/ghsa-patch-subprocess.mjs";
|
||||
|
||||
describe("GHSA patch subprocess", () => {
|
||||
it("bounds each GitHub lookup with a timeout and SIGKILL", () => {
|
||||
const spawnSyncImpl = vi.fn(() => ({
|
||||
status: 0,
|
||||
stdout: "result",
|
||||
stderr: "",
|
||||
}));
|
||||
|
||||
expect(runGhCommand(["api", "rate_limit"], { spawnSyncImpl })).toBe("result");
|
||||
expect(spawnSyncImpl).toHaveBeenCalledWith("gh", ["api", "rate_limit"], {
|
||||
encoding: "utf8",
|
||||
killSignal: "SIGKILL",
|
||||
timeout: 60_000,
|
||||
});
|
||||
});
|
||||
|
||||
it("throws the GitHub CLI error when a lookup exits non-zero", () => {
|
||||
const spawnSyncImpl = vi.fn(() => ({
|
||||
status: 1,
|
||||
stdout: "",
|
||||
stderr: "gh api failed",
|
||||
}));
|
||||
|
||||
expect(() => runGhCommand(["api", "rate_limit"], { spawnSyncImpl })).toThrow("gh api failed");
|
||||
});
|
||||
|
||||
it("propagates the timeout error from a stalled GitHub CLI process", () => {
|
||||
const timeout = Object.assign(new Error("spawnSync gh ETIMEDOUT"), { code: "ETIMEDOUT" });
|
||||
const spawnSyncImpl = vi.fn(() => ({
|
||||
error: timeout,
|
||||
status: null,
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
}));
|
||||
|
||||
expect(() => runGhCommand(["api", "rate_limit"], { spawnSyncImpl })).toThrow(timeout);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user