Skip to content

Commit 11fad18

Browse files
committed
add checksum verification
1 parent dadfa6a commit 11fad18

3 files changed

Lines changed: 93 additions & 2 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Rust CLI for Polymarket. Browse markets, place orders, manage positions, and interact with onchain contracts — from a terminal or as a JSON API for scripts and agents.
44

5+
> **Warning:** This is early, experimental software. Use at your own risk and do not use with large amounts of funds. APIs, commands, and behavior may change without notice. Always verify transactions before confirming.
6+
57
## Install
68

79
### Homebrew (macOS and Linux)

install.sh

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,43 @@ main() {
3737
exit 1
3838
fi
3939

40-
url="https://github.com/${REPO}/releases/download/${tag}/${BINARY}-${tag}-${target}.tar.gz"
40+
tarball_name="${BINARY}-${tag}-${target}.tar.gz"
41+
url="https://github.com/${REPO}/releases/download/${tag}/${tarball_name}"
42+
checksums_url="https://github.com/${REPO}/releases/download/${tag}/checksums.txt"
4143

4244
echo "Installing ${BINARY} ${tag} (${target})..."
4345

4446
tmpdir=$(mktemp -d)
4547
trap 'rm -rf "$tmpdir"' EXIT
4648

47-
curl -sSfL "$url" | tar xz -C "$tmpdir"
49+
curl -sSfL "$url" -o "$tmpdir/$tarball_name"
50+
curl -sSfL "$checksums_url" -o "$tmpdir/checksums.txt"
51+
52+
expected_hash=$(grep "$tarball_name" "$tmpdir/checksums.txt" | awk '{print $1}')
53+
if [ -z "$expected_hash" ]; then
54+
echo "Error: no checksum found for $tarball_name" >&2
55+
exit 1
56+
fi
57+
58+
if command -v sha256sum >/dev/null 2>&1; then
59+
actual_hash=$(sha256sum "$tmpdir/$tarball_name" | awk '{print $1}')
60+
elif command -v shasum >/dev/null 2>&1; then
61+
actual_hash=$(shasum -a 256 "$tmpdir/$tarball_name" | awk '{print $1}')
62+
else
63+
echo "Error: need sha256sum or shasum to verify download" >&2
64+
exit 1
65+
fi
66+
67+
if [ "$actual_hash" != "$expected_hash" ]; then
68+
echo "Error: checksum mismatch!" >&2
69+
echo " Expected: $expected_hash" >&2
70+
echo " Got: $actual_hash" >&2
71+
echo "The downloaded file may have been tampered with. Aborting." >&2
72+
exit 1
73+
fi
74+
75+
echo "Checksum verified."
76+
tar xzf "$tmpdir/$tarball_name" -C "$tmpdir"
4877

4978
if [ -w "$INSTALL_DIR" ]; then
5079
mv "$tmpdir/$BINARY" "$INSTALL_DIR/$BINARY"

src/commands/upgrade.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ pub fn execute() -> anyhow::Result<()> {
3232
let tmpdir = tempdir()?;
3333
let tarball = format!("{tmpdir}/{BINARY}.tar.gz");
3434

35+
let tarball_name =
36+
format!("{BINARY}-{latest_tag}-{target}.tar.gz");
37+
let checksums_url = format!(
38+
"https://github.com/{REPO}/releases/download/{latest_tag}/checksums.txt"
39+
);
40+
3541
println!("Downloading {latest_tag} ({target})...");
3642

3743
let status = Command::new("curl")
@@ -42,6 +48,17 @@ pub fn execute() -> anyhow::Result<()> {
4248
bail!("Download failed (HTTP error)");
4349
}
4450

51+
let checksums_file = format!("{tmpdir}/checksums.txt");
52+
let status = Command::new("curl")
53+
.args(["-sSfL", "-o", &checksums_file, &checksums_url])
54+
.status()
55+
.context("Failed to download checksums")?;
56+
if !status.success() {
57+
bail!("Failed to download checksums.txt — cannot verify integrity");
58+
}
59+
60+
verify_checksum(&tarball, &checksums_file, &tarball_name)?;
61+
4562
let status = Command::new("tar")
4663
.args(["xzf", &tarball, "-C", &tmpdir])
4764
.status()
@@ -129,6 +146,49 @@ fn tempdir() -> anyhow::Result<String> {
129146
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
130147
}
131148

149+
fn verify_checksum(file_path: &str, checksums_file: &str, expected_name: &str) -> anyhow::Result<()> {
150+
let checksums = fs::read_to_string(checksums_file).context("Failed to read checksums.txt")?;
151+
152+
let expected_hash = checksums
153+
.lines()
154+
.find_map(|line| {
155+
let mut parts = line.split_whitespace();
156+
let hash = parts.next()?;
157+
let name = parts.next()?;
158+
if name == expected_name || name.trim_start_matches("./") == expected_name {
159+
Some(hash.to_string())
160+
} else {
161+
None
162+
}
163+
})
164+
.context(format!("No checksum found for {expected_name} in checksums.txt"))?;
165+
166+
let output = Command::new("shasum")
167+
.args(["-a", "256", file_path])
168+
.output()
169+
.or_else(|_| Command::new("sha256sum").arg(file_path).output())
170+
.context("Failed to compute SHA256 (need shasum or sha256sum)")?;
171+
172+
if !output.status.success() {
173+
bail!("Failed to compute SHA256 of downloaded file");
174+
}
175+
176+
let actual_hash = String::from_utf8_lossy(&output.stdout)
177+
.split_whitespace()
178+
.next()
179+
.unwrap_or("")
180+
.to_string();
181+
182+
if actual_hash != expected_hash {
183+
bail!(
184+
"Checksum mismatch!\n Expected: {expected_hash}\n Got: {actual_hash}\n\nThe downloaded binary may have been tampered with. Aborting."
185+
);
186+
}
187+
188+
println!("Checksum verified.");
189+
Ok(())
190+
}
191+
132192
fn sudo_mv(from: &str, to: &str) -> std::io::Result<()> {
133193
let status = Command::new("sudo")
134194
.args(["mv", from, to])

0 commit comments

Comments
 (0)