Skip to content

feat(security): verify binary SHA-256 checksum on download (B5)#15

Merged
beonde merged 1 commit intomainfrom
fix/eval-binary-checksums
Mar 27, 2026
Merged

feat(security): verify binary SHA-256 checksum on download (B5)#15
beonde merged 1 commit intomainfrom
fix/eval-binary-checksums

Conversation

@beonde
Copy link
Copy Markdown
Member

@beonde beonde commented Mar 27, 2026

Summary

Adds SHA-256 checksum verification for downloaded capiscio-core binaries (B5 - design partner eval, part 2/3).

Changes

  • After downloading the binary, fetch checksums.txt from the same release
  • Compute SHA-256 of the downloaded file and compare against the published checksum
  • On mismatch: delete the binary and raise RuntimeError with a clear error message
  • Graceful fallback: if checksums.txt is not available (older releases), log a warning and continue

Related PRs

  • capiscio-core PR #57: publishes checksums.txt in release
  • capiscio-node: same verification pattern (separate PR)

Evaluation Plan

Design partner eval item B5 (P1 — Security)

- Fetch checksums.txt from release assets after binary download
- Compute SHA-256 of downloaded binary and compare
- On mismatch: delete binary and raise RuntimeError with clear message
- Graceful fallback: warn if checksums.txt not available (older releases)

Addresses: design-partner-eval B5 (P1 security)
Copilot AI review requested due to automatic review settings March 27, 2026 22:53
@github-actions
Copy link
Copy Markdown

✅ All checks passed! Ready for review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds SHA-256 integrity verification to the capiscio-core binary download flow in capiscio-python, to help ensure the downloaded executable matches the published release artifact.

Changes:

  • Fetch checksums.txt for a given GitHub release and extract the expected checksum for the platform binary.
  • Compute the downloaded binary’s SHA-256 and compare it to the expected checksum; delete and error on mismatch.
  • Warn and continue when checksums cannot be obtained.

Comment on lines 2 to 6
import sys
import hashlib
import platform
import stat
import shutil
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sys and shutil appear to be unused in this module. Since this PR is already touching the import block, consider removing unused imports to avoid lint/IDE warnings and keep dependencies clear.

Suggested change
import sys
import hashlib
import platform
import stat
import shutil
import hashlib
import platform
import stat

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +87
for line in resp.text.strip().splitlines():
parts = line.split()
if len(parts) == 2 and parts[1] == filename:
return parts[0]
logger.warning(f"Binary {filename} not found in checksums.txt")
return None
except requests.exceptions.RequestException as e:
logger.warning(f"Could not fetch checksums.txt: {e}")
return None

Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_fetch_expected_checksum treats any RequestException (timeouts, DNS issues, connection errors, etc.) as “checksums not available” and continues without verification. That makes the integrity check bypassable if an attacker or network issue blocks the checksum fetch. Consider only falling back when the HTTP status is 404/410, and otherwise failing closed (or making this behavior explicitly configurable).

Suggested change
for line in resp.text.strip().splitlines():
parts = line.split()
if len(parts) == 2 and parts[1] == filename:
return parts[0]
logger.warning(f"Binary {filename} not found in checksums.txt")
return None
except requests.exceptions.RequestException as e:
logger.warning(f"Could not fetch checksums.txt: {e}")
return None
except requests.exceptions.HTTPError as e:
status_code = getattr(e.response, "status_code", None)
# Treat missing checksum file as "no checksum available" and continue without verification.
if status_code in (404, 410):
logger.warning(f"checksums.txt not found at {url} (status {status_code}); "
"continuing without checksum verification.")
return None
# For other HTTP errors, fail closed.
logger.error(f"Failed to fetch checksums.txt from {url}: {e}")
raise RuntimeError("Unable to fetch checksums for capiscio-core binary.") from e
except requests.exceptions.RequestException as e:
# Network/transport errors (timeouts, DNS issues, etc.) should cause a hard failure.
logger.error(f"Network error while fetching checksums.txt from {url}: {e}")
raise RuntimeError("Network error while fetching checksums for capiscio-core binary.") from e
for line in resp.text.strip().splitlines():
parts = line.split()
if len(parts) == 2 and parts[1] == filename:
return parts[0]
logger.warning(f"Binary {filename} not found in checksums.txt")
return None

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +83
logger.warning(f"Binary {filename} not found in checksums.txt")
return None
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If checksums.txt is successfully fetched but doesn’t contain an entry for the expected binary filename, the code currently logs a warning and disables verification by returning None. Given the file exists, missing the entry is suspicious and should likely be treated as an integrity failure (abort install) rather than silently proceeding with an unverified binary.

Suggested change
logger.warning(f"Binary {filename} not found in checksums.txt")
return None
# If checksums.txt was fetched successfully but does not contain an entry
# for the expected binary filename, treat this as an integrity failure
# rather than silently proceeding without verification.
logger.error(f"Binary {filename} not found in checksums.txt")
raise RuntimeError(
f"Integrity check failed: expected checksum entry for {filename} "
f"not found in checksums.txt for version v{version}"
)

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +146
# Verify checksum integrity
expected_hash = _fetch_expected_checksum(version, filename)
if expected_hash is not None:
if not _verify_checksum(target_path, expected_hash):
target_path.unlink()
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New behavior (fetching checksums.txt, computing SHA-256, and failing on mismatch) isn’t covered by tests in this PR. Since this code path is security-critical and also adds a second requests.get call, please add/update unit tests to cover: checksum file present + match, present + mismatch (deletes file + raises), and missing checksums (warns + continues).

Copilot uses AI. Check for mistakes.
@beonde beonde merged commit 2e2285a into main Mar 27, 2026
22 checks passed
@beonde beonde deleted the fix/eval-binary-checksums branch March 27, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants