Skip to content

Commit 394df99

Browse files
art049claude
andcommitted
feat: pin codspeed-go-runner installer downloads with sha256 verification
The introspected `go test` shim now installs `codspeed-go-runner` from a pinned URL whose installer bytes are verified against a sha256 recorded in `go.sh`. The mapping covers every released installer (0.1.1 through 1.2.0) and is stored as a `version sha256` table looked up via awk, so version bumps are a one-line edit. The pinned default replaces the previous "latest" fallback, which silently introduced breaking changes; users can still override via the `go-runner-version` CLI option. A CI-gated Rust test (`GITHUB_ACTIONS=true`) parses the table out of the embedded `go.sh`, downloads each installer in parallel, and asserts its bytes hash to the declared pin — same gating as the existing `binary_pins` network check. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b019f16 commit 394df99

3 files changed

Lines changed: 162 additions & 12 deletions

File tree

src/cli/shared.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pub struct ExecAndRunSharedArgs {
9696
pub allow_empty: bool,
9797

9898
/// The version of the go-runner to use (e.g., 1.2.3, 1.0.0-beta.1)
99-
/// If not specified, the latest version will be installed
99+
/// If not specified, the runner installs the pinned default version
100100
#[arg(long, env = "CODSPEED_GO_RUNNER_VERSION", value_parser = parse_version)]
101101
pub go_runner_version: Option<semver::Version>,
102102

src/executor/helpers/introspected_golang/go.sh

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,73 @@ debug_log "Called with arguments: $*"
1212
debug_log "Number of arguments: $#"
1313

1414

15+
GO_RUNNER_INSTALLER_SHA256S="
16+
0.1.1 f841950fe630dbc5c1bd534eb7cb8acf476b744297081710866092551110a68f
17+
0.1.2 cdbe066adccad7eb11ca53daf57a2c754666966d6ec9ab02e399f6f00a87f3eb
18+
0.2.0 10b75883bd8c09133281d652ea3ae1897f35606dd8a4a722d5c9b304666eaf7e
19+
0.3.0 d50608b9ceff1426badf5cb868bb76eca74428715006029519248d61c4781799
20+
0.4.0 dfb13bd30afef8a430680813b5605f46243962f6e31359f387d7c90ee9018be3
21+
0.4.1 94b8b82a095597e3bf4fea12cccd6bbc2e52e619d7953c3fcb695651ae5e804c
22+
0.4.2 5fca476647e269aedc91e5920265867de56e4dcada5bd83d99a507e437236e06
23+
0.5.0 82c68678904691903567f542117bcbf6aceca87b34db14c946915b988f12cf8f
24+
0.5.1 db2de37e815913ce72f761a8c25aeef4a64fe2407a870e0dc2ea149a3decd904
25+
0.6.0 e7bf7c37b07bc43610cf35140556b51cb03b4355914307f8100bef5f7f37c85d
26+
0.6.1 a38e0e3417abce9260f1b8c3ff7407bb93900a0ad83ee191725a0266f97c797c
27+
0.6.2 616200762cc2fa582fae56ea58e77ad6c056bd658f6907d5be56d39d59ec6616
28+
1.0.0 0540d8abe62357acefb85b9f1a9ff81dcfef70d6be8bea35096bf26a295a91f8
29+
1.0.1 c26f463883a77591e5a2e2f17f0995a989cbada0d4f5115f327900badac07918
30+
1.0.2 4e4ecfb1888ced253f0acbbc132db0b1d7e99351d40f3eff789a518a6130ee35
31+
1.1.0 d16e0e14bdfaea61a6da1d46d7b3b36f940b64335c8affbdc85b802d6e949a97
32+
1.2.0 072876ccd43b8c73c123df206eda4b1f82f9ff03b1330efe35e5eaa5c1b6cefe
33+
"
34+
35+
DEFAULT_GO_RUNNER_VERSION="1.2.0"
36+
37+
get_go_runner_installer_sha256() {
38+
if ! awk -v v="$1" '$1==v{print $2; f=1} END{exit !f}' <<<"$GO_RUNNER_INSTALLER_SHA256S"; then
39+
echo "ERROR: No pinned sha256 for codspeed-go-runner version $1" >&2
40+
exit 1
41+
fi
42+
}
43+
44+
sha256_file() {
45+
if command -v sha256sum >/dev/null 2>&1; then
46+
sha256sum "$1" | awk '{ print $1 }'
47+
elif command -v shasum >/dev/null 2>&1; then
48+
shasum -a 256 "$1" | awk '{ print $1 }'
49+
else
50+
echo "ERROR: Could not find sha256sum or shasum to verify downloaded installer" >&2
51+
exit 1
52+
fi
53+
}
54+
55+
install_go_runner() {
56+
local version="$1"
57+
local expected_sha256
58+
expected_sha256=$(get_go_runner_installer_sha256 "$version")
59+
local download_url="https://github.com/CodSpeedHQ/codspeed-go/releases/download/v${version}/codspeed-go-runner-installer.sh"
60+
local tmp_dir
61+
tmp_dir=$(mktemp -d)
62+
local installer_path="$tmp_dir/codspeed-go-runner-installer.sh"
63+
64+
cleanup_go_runner_installer() {
65+
rm -rf "$tmp_dir"
66+
}
67+
trap cleanup_go_runner_installer RETURN
68+
69+
curl -fsSL "$download_url" -o "$installer_path"
70+
71+
local actual_sha256
72+
actual_sha256=$(sha256_file "$installer_path")
73+
if [ "$actual_sha256" != "$expected_sha256" ]; then
74+
echo "ERROR: Hash mismatch for $download_url: expected $expected_sha256, got $actual_sha256" >&2
75+
exit 1
76+
fi
77+
78+
bash "$installer_path" --quiet
79+
}
80+
81+
1582
# Currently only walltime is supported
1683
if [ "${CODSPEED_RUNNER_MODE:-}" != "walltime" ]; then
1784
echo "CRITICAL: Go benchmarks can only be run with the walltime instrument"
@@ -47,17 +114,9 @@ case "$1" in
47114
# Find go-runner or install if not found
48115
GO_RUNNER=$(which codspeed-go-runner 2>/dev/null || true)
49116
if [ -z "$GO_RUNNER" ]; then
50-
# Build the installer URL with the specified version or use latest
51-
INSTALLER_VERSION="${CODSPEED_GO_RUNNER_VERSION:-latest}"
52-
if [ "$INSTALLER_VERSION" = "latest" ]; then
53-
DOWNLOAD_URL="http://github.com/CodSpeedHQ/codspeed-go/releases/latest/download/codspeed-go-runner-installer.sh"
54-
echo "::warning::Installing the latest version of codspeed-go-runner. This can silently introduce breaking changes. We recommend pinning a specific version via the \`go-runner-version\` option in the action." >&2
55-
else
56-
DOWNLOAD_URL="http://github.com/CodSpeedHQ/codspeed-go/releases/download/v${INSTALLER_VERSION}/codspeed-go-runner-installer.sh"
57-
fi
58-
59-
debug_log "Installing go-runner from: $DOWNLOAD_URL"
60-
curl -fsSL "$DOWNLOAD_URL" | bash -s -- --quiet
117+
INSTALLER_VERSION="${CODSPEED_GO_RUNNER_VERSION:-$DEFAULT_GO_RUNNER_VERSION}"
118+
debug_log "Installing go-runner v${INSTALLER_VERSION}"
119+
install_go_runner "$INSTALLER_VERSION"
61120
GO_RUNNER=$(which codspeed-go-runner 2>/dev/null || true)
62121
fi
63122

src/executor/helpers/introspected_golang/mod.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,94 @@ pub fn setup() -> Result<PathBuf> {
1717
script_file.set_permissions(perms)?;
1818
Ok(script_folder)
1919
}
20+
21+
#[cfg(test)]
22+
mod tests {
23+
use super::*;
24+
use crate::request_client::REQUEST_CLIENT;
25+
26+
fn pinned_go_runner_installers() -> Vec<(String, String)> {
27+
let start = INTROSPECTED_GO_SCRIPT
28+
.find("GO_RUNNER_INSTALLER_SHA256S=\"")
29+
.expect("GO_RUNNER_INSTALLER_SHA256S table not found in go.sh");
30+
let body_start = INTROSPECTED_GO_SCRIPT[start..]
31+
.find('\n')
32+
.map(|i| start + i + 1)
33+
.expect("malformed GO_RUNNER_INSTALLER_SHA256S table");
34+
let body_end = INTROSPECTED_GO_SCRIPT[body_start..]
35+
.find("\n\"")
36+
.map(|i| body_start + i)
37+
.expect("unterminated GO_RUNNER_INSTALLER_SHA256S table");
38+
39+
INTROSPECTED_GO_SCRIPT[body_start..body_end]
40+
.lines()
41+
.filter(|line| !line.trim().is_empty())
42+
.map(|line| {
43+
let mut parts = line.split_whitespace();
44+
let version = parts.next().expect("missing version").to_string();
45+
let sha256 = parts.next().expect("missing sha256").to_string();
46+
assert!(
47+
parts.next().is_none(),
48+
"unexpected extra column in GO_RUNNER_INSTALLER_SHA256S row: {line:?}"
49+
);
50+
(version, sha256)
51+
})
52+
.collect()
53+
}
54+
55+
#[test]
56+
fn pinned_go_runner_installers_parses_table() {
57+
let pins = pinned_go_runner_installers();
58+
assert!(!pins.is_empty(), "no go-runner installer pins parsed");
59+
for (version, sha256) in &pins {
60+
assert!(!version.is_empty(), "empty version in pin row");
61+
assert_eq!(sha256.len(), 64, "sha256 must be 64 hex chars: {sha256}");
62+
assert!(
63+
sha256.chars().all(|c| c.is_ascii_hexdigit()),
64+
"sha256 must be hex: {sha256}",
65+
);
66+
}
67+
}
68+
69+
// Network-bound: downloads every pinned go-runner installer and asserts its
70+
// bytes hash to the declared SHA-256. Skipped locally; CI sets
71+
// `GITHUB_ACTIONS=true`. Run after bumping a version to make sure the
72+
// release won't ship a stale or mistyped hash.
73+
#[test_with::env(GITHUB_ACTIONS)]
74+
#[tokio::test(flavor = "multi_thread")]
75+
async fn all_pinned_go_runner_installers_match_their_declared_sha256() {
76+
let pins = pinned_go_runner_installers();
77+
78+
let results = futures::future::join_all(pins.into_iter().map(|(version, expected)| async move {
79+
let url = format!(
80+
"https://github.com/CodSpeedHQ/codspeed-go/releases/download/v{version}/codspeed-go-runner-installer.sh"
81+
);
82+
let bytes = REQUEST_CLIENT
83+
.get(&url)
84+
.send()
85+
.await
86+
.map_err(|e| format!("{version} ({url}): request failed: {e}"))?
87+
.error_for_status()
88+
.map_err(|e| format!("{version} ({url}): {e}"))?
89+
.bytes()
90+
.await
91+
.map_err(|e| format!("{version} ({url}): read failed: {e}"))?;
92+
let actual = sha256::digest(bytes.as_ref());
93+
if actual != expected {
94+
Err(format!(
95+
"{version} ({url}): expected {expected}, got {actual}"
96+
))
97+
} else {
98+
Ok(())
99+
}
100+
}))
101+
.await;
102+
103+
let failures: Vec<_> = results.into_iter().filter_map(Result::err).collect();
104+
assert!(
105+
failures.is_empty(),
106+
"pinned go-runner installers failed verification:\n - {}",
107+
failures.join("\n - "),
108+
);
109+
}
110+
}

0 commit comments

Comments
 (0)