From a599b50065f369d79ed705cec1b13c1b755d42cf Mon Sep 17 00:00:00 2001 From: zengLingbiao Date: Sun, 23 Aug 2026 23:30:48 +0800 Subject: [PATCH] fix(ci): bound trufflehog installer download timeouts (#117480) Punchcard-Session: cobalt-orchard-harbor-y7 --- scripts/install-trufflehog.sh | 7 ++- test/scripts/install-trufflehog.test.ts | 62 ++++++++++++++++++++----- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/scripts/install-trufflehog.sh b/scripts/install-trufflehog.sh index 17cbdddd08e7..262ede833d6d 100755 --- a/scripts/install-trufflehog.sh +++ b/scripts/install-trufflehog.sh @@ -93,7 +93,12 @@ install_trufflehog() { url="https://github.com/trufflesecurity/trufflehog/releases/download/v${trufflehog_version}/${archive}" tmp_dir="$(mktemp -d)" - if ! curl -fsSL --retry 3 --output "$tmp_dir/$archive" "$url" || + # Bound individual transfers and the retry window. curl resets --max-time for + # each retry, while a started retry can outlive --retry-max-time. + if ! curl -fsSL \ + --connect-timeout 30 --max-time 300 \ + --retry 3 --retry-max-time 300 \ + --output "$tmp_dir/$archive" "$url" || ! ( cd "$tmp_dir" printf '%s %s\n' "$checksum" "$archive" | sha256sum -c - diff --git a/test/scripts/install-trufflehog.test.ts b/test/scripts/install-trufflehog.test.ts index dd9469646ed6..3f8e73f84a7a 100644 --- a/test/scripts/install-trufflehog.test.ts +++ b/test/scripts/install-trufflehog.test.ts @@ -1,15 +1,11 @@ import { execFileSync } from "node:child_process"; import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; +import { dirname, join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; -import { cleanupTempDirs, makeTempDir } from "../helpers/temp-dir.js"; +import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js"; const SCRIPT = "scripts/install-trufflehog.sh"; -const tempDirs = new Set(); - -afterEach(() => { - cleanupTempDirs(tempDirs); -}); +const tempDirs = useAutoCleanupTempDirTracker(afterEach); function runBash(command: string, env: NodeJS.ProcessEnv = {}): string { return execFileSync("/bin/bash", ["--noprofile", "--norc", "-c", command], { @@ -65,7 +61,7 @@ describe("scripts/install-trufflehog.sh", () => { }); it("does not download TruffleHog again when the pinned version is installed", () => { - const root = makeTempDir(tempDirs, "openclaw-trufflehog-install-"); + const root = tempDirs.make("openclaw-trufflehog-install-"); const binDir = join(root, "bin"); const downloadMarker = join(root, "downloaded"); mkdirSync(binDir); @@ -95,7 +91,7 @@ describe("scripts/install-trufflehog.sh", () => { }); it("creates a missing user-writable install directory without sudo", () => { - const root = makeTempDir(tempDirs, "openclaw-trufflehog-user-bin-"); + const root = tempDirs.make("openclaw-trufflehog-user-bin-"); const binDir = join(root, "nested", "bin"); const fakeBin = join(root, "fake-bin"); const sudoMarker = join(root, "sudo-used"); @@ -114,7 +110,7 @@ describe("scripts/install-trufflehog.sh", () => { }); it("does not change permissions on an existing writable install directory", () => { - const root = makeTempDir(tempDirs, "openclaw-trufflehog-existing-bin-"); + const root = tempDirs.make("openclaw-trufflehog-existing-bin-"); const binDir = join(root, "bin"); const fakeBin = join(root, "fake-bin"); const installMarker = join(root, "install-used"); @@ -135,10 +131,54 @@ describe("scripts/install-trufflehog.sh", () => { expect(existsSync(installMarker)).toBe(false); }); + it("passes bounded download options to curl and cleans up after curl times out", () => { + const root = tempDirs.make("openclaw-trufflehog-curl-"); + const binDir = join(root, "bin"); + const argsFile = join(root, "curl-args"); + mkdirSync(binDir); + writeFileSync( + join(binDir, "curl"), + '#!/bin/sh\nprintf "%s\\n" "$@" >"$CURL_ARGS_FILE"\nexit 28\n', + { mode: 0o755 }, + ); + + expect(() => + runBash( + `uname() { if [ "$1" = "-s" ]; then printf "Linux\\n"; else printf "x86_64\\n"; fi; }\nsource ${SCRIPT}\ninstall_trufflehog`, + { + CURL_ARGS_FILE: argsFile, + OPENCLAW_TRUFFLEHOG_BIN_DIR: join(root, "install"), + PATH: `${binDir}:${process.env.PATH ?? ""}`, + }, + ), + ).toThrow(); + + const archive = "trufflehog_3.95.9_linux_amd64.tar.gz"; + const args = readFileSync(argsFile, "utf8").trimEnd().split("\n"); + const outputPath = args[10] ?? ""; + expect(args.slice(0, 10)).toEqual([ + "-fsSL", + "--connect-timeout", + "30", + "--max-time", + "300", + "--retry", + "3", + "--retry-max-time", + "300", + "--output", + ]); + expect(outputPath).toBe(join(dirname(outputPath), archive)); + expect(args[11]).toBe( + `https://github.com/trufflesecurity/trufflehog/releases/download/v3.95.9/${archive}`, + ); + expect(existsSync(dirname(outputPath))).toBe(false); + }); + it("verifies the archive before extraction and replaces the binary atomically", () => { const script = readFileSync(SCRIPT, "utf8"); expect(script).toContain('"$binary" --no-update --version'); - const download = script.indexOf('curl -fsSL --retry 3 --output "$tmp_dir/$archive" "$url"'); + const download = script.indexOf("curl -fsSL"); const verify = script.indexOf("sha256sum -c -"); const extract = script.indexOf( 'tar --no-same-owner -xzf "$tmp_dir/$archive" -C "$tmp_dir" trufflehog',