Skip to content

Verify self-update archives against GitHub release digests before install#168

Merged
F16shen merged 1 commit into
mainfrom
codex/propose-fix-for-cdn-update-vulnerability
May 12, 2026
Merged

Verify self-update archives against GitHub release digests before install#168
F16shen merged 1 commit into
mainfrom
codex/propose-fix-for-cdn-update-vulnerability

Conversation

@sonald
Copy link
Copy Markdown
Collaborator

@sonald sonald commented May 12, 2026

Motivation

  • The stable self-update flow previously trusted CDN-provided version metadata and downloaded archives without cryptographic verification, allowing a malicious CDN or configured update endpoint to supply a higher version and a malicious installer that would be executed with sudo.
  • The change binds the plain latest metadata to the authoritative GitHub release for the matching tag and ensures the downloaded archive matches a trusted SHA256 digest before it can be installed.

Description

  • Added get_release_by_tag, checksum discovery, and verification helpers (_sha256_file, _parse_checksum_text, get_expected_archive_sha256, verify_archive) to src/aish/cli/update_manager.py and a SHA256_PATTERN constant and GITHUB_API_RELEASE_TAG endpoint constant.
  • When resolving a stable latest version, the code now fetches the corresponding GitHub release metadata and uses its assets/checksum files as the trust root instead of accepting a version-only CDN response alone.
  • download_release now verifies the downloaded archive against the expected SHA256 digest (using hmac.compare_digest) and removes/rejects the archive if verification fails, preventing unverified installers from being returned for installation.
  • Updated tests/test_update_manager.py to exercise trusted release metadata lookup, checksum-parsed digests, download verification, download-base overrides, and rejection of checksum mismatches.

Testing

  • Ran linters/formatters: uv run ruff check src/aish/cli/update_manager.py tests/test_update_manager.py and uv run black --check src/aish/cli/update_manager.py tests/test_update_manager.py, both passed after applying formatting.
  • Ran unit tests for the change: uv run python -m pytest tests/test_update_manager.py which passed (12 passed).
  • Ran the full test suite: uv run python -m pytest which completed but reported one unrelated failing test (tests/terminal/pty/test_pty_control_protocol.py::test_pty_manager_execute_command_honors_explicit_timeout), with the rest of the suite passing (1 failed, 631 passed, 15 skipped); this failure is not related to the update manager changes.

Codex Task

Summary by CodeRabbit

  • New Features

    • Self-update downloads now include SHA256 checksum verification for archive validation.
    • Enhanced error handling for version and release information retrieval.
  • Tests

    • Expanded test coverage for update verification and checksum validation workflows.

Review Change Stack

@github-actions
Copy link
Copy Markdown
Contributor

Thanks for the pull request. A maintainer will review it when available.

Please keep the PR focused, explain the why in the description, and make sure local checks pass before requesting review.

Contribution guide: https://github.com/AI-Shell-Team/aish/blob/main/CONTRIBUTING.md

@github-actions
Copy link
Copy Markdown
Contributor

This pull request description looks incomplete. Please update the missing sections below before review.

Missing items:

  • Summary
  • User-visible Changes
  • Compatibility
  • Testing
  • Change Type
  • Scope

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

The PR adds cryptographic SHA256 verification to the self-update flow, fetches trusted release metadata per tag from GitHub API, parses checksum assets, verifies downloaded archives using constant-time digest comparison, and refactors all update tests to use reusable mock helpers and validate the complete verification path.

Changes

Update Integrity Verification

Layer / File(s) Summary
Security foundation and GitHub metadata fetching
src/aish/cli/update_manager.py
hmac import, GitHub release-tag API URL template, SHA256 pattern regex, get_release_by_tag() method to fetch release metadata by tag, and updated get_latest_release() to use the new helper and normalize failures to UpdateCheckError.
Archive verification utilities and download integration
src/aish/cli/update_manager.py
SHA256 computation, checksum asset parsing, expected digest resolution from release assets, and archive verification using constant-time hmac.compare_digest. Integrated into download_release() to reject mismatches, clean up on failure, and delete destination on exception.
Test mock helpers and infrastructure
tests/test_update_manager.py
hashlib import and make_response() / make_release_response() helper functions for consistent mocked HTTP responses and GitHub release metadata (including assets with digests).
Existing tests refactored with mock helpers
tests/test_update_manager.py
Latest-release, check-for-updates, and no-update tests now use mock helpers, mock sequential client.get calls, and validate endpoint URLs and release content.
Download tests with SHA256 verification and new digest tests
tests/test_update_manager.py
Download-release and base-URL-override tests enhanced with computed SHA256 digests attached to asset metadata and both client.stream and client.get mocking. Added tests for checksum-mismatch rejection and _parse_checksum_text() SHA256SUMS-style parsing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • AI-Shell-Team/aish#127: Modifies UpdateManager release metadata handling and download_release flow in parallel.
  • AI-Shell-Team/aish#123: Also updates get_latest_release() and download_release() for release fetching, but shifts to CDN-based metadata rather than tag-specific verification.

Suggested labels

size: S, tests, security

Poem

🐰 A rabbit's leap of faith rewarded:
Checksums verified, tamper thwarted!
SHA256 dances, digests align,
GitHub's trust and Python's design.
Self-updates safe—bunny approved! 🔐

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding SHA256 verification for self-update archives against GitHub release digests before installation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/propose-fix-for-cdn-update-vulnerability

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/aish/cli/update_manager.py (1)

371-386: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistent partial-download cleanup on httpx.HTTPError.

The generic Exception handler unlinks dest_path (Line 385), but the httpx.HTTPError handler does not. If _download_with_progress opens dest_path for writing and then a network error is raised during iter_bytes streaming (after open(...)), a partial/corrupt archive will be left in dest_dir. A subsequent retry would then operate against (or have to clean up) a stale file.

🛠️ Proposed fix
         except httpx.HTTPError as e:
             self.console.print(f"[red]Download failed from CDN: {e}[/red]")
+            dest_path.unlink(missing_ok=True)
             return None
 
         except Exception as e:
             self.console.print(f"[red]Unexpected error during download: {e}[/red]")
             dest_path.unlink(missing_ok=True)
             return None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aish/cli/update_manager.py` around lines 371 - 386, The HTTPError except
block in the download logic leaves partial files behind; update the
httpx.HTTPError handler in the same method where _download_with_progress and
verify_archive are used to unlink dest_path (use
dest_path.unlink(missing_ok=True)) before returning None, matching the cleanup
currently done in the generic Exception handler so partial/corrupt archives are
removed on network failures.
🧹 Nitpick comments (1)
src/aish/cli/update_manager.py (1)

300-346: ⚡ Quick win

Avoid the duplicate get_release_by_tag round-trip per update.

For the stable update flow, the release metadata is fetched twice for the same tag: once in get_latest_release (Line 177), then again here via verify_archiveget_expected_archive_sha256get_release_by_tag. That doubles GitHub API traffic (relevant for the 60/hour unauthenticated rate limit) and adds a second network round-trip on the critical install path, with no added security benefit since both calls hit the same endpoint.

Consider plumbing the already-fetched release dict (or just its assets) into verify_archive/get_expected_archive_sha256, or memoizing get_release_by_tag per tag_name on the instance. The pre-release path (Line 154-172) similarly already has the release data and could pass it through.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aish/cli/update_manager.py` around lines 300 - 346, The code currently
fetches the same GitHub release twice; fix by either (A) adding an optional
release/assets parameter to get_expected_archive_sha256 and updating
verify_archive (and the callers like get_latest_release/pre-release flow) to
pass the already-fetched release dict (or release["assets"]) so no second
network call is made, or (B) implement simple memoization on the instance (e.g.,
self._release_cache keyed by tag_name) and have get_release_by_tag consult/store
this cache; update verify_archive/get_expected_archive_sha256 to use the cached
release when available. Ensure symbols touched include
get_expected_archive_sha256, verify_archive, get_release_by_tag, and callers
such as get_latest_release/pre-release code paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/aish/cli/update_manager.py`:
- Around line 371-386: The HTTPError except block in the download logic leaves
partial files behind; update the httpx.HTTPError handler in the same method
where _download_with_progress and verify_archive are used to unlink dest_path
(use dest_path.unlink(missing_ok=True)) before returning None, matching the
cleanup currently done in the generic Exception handler so partial/corrupt
archives are removed on network failures.

---

Nitpick comments:
In `@src/aish/cli/update_manager.py`:
- Around line 300-346: The code currently fetches the same GitHub release twice;
fix by either (A) adding an optional release/assets parameter to
get_expected_archive_sha256 and updating verify_archive (and the callers like
get_latest_release/pre-release flow) to pass the already-fetched release dict
(or release["assets"]) so no second network call is made, or (B) implement
simple memoization on the instance (e.g., self._release_cache keyed by tag_name)
and have get_release_by_tag consult/store this cache; update
verify_archive/get_expected_archive_sha256 to use the cached release when
available. Ensure symbols touched include get_expected_archive_sha256,
verify_archive, get_release_by_tag, and callers such as
get_latest_release/pre-release code paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 99fbe0a1-4fe8-4ab1-bde6-c1b3ff1b32fe

📥 Commits

Reviewing files that changed from the base of the PR and between 7d627de and d15c2ac.

📒 Files selected for processing (2)
  • src/aish/cli/update_manager.py
  • tests/test_update_manager.py

@F16shen F16shen merged commit a15e5b0 into main May 12, 2026
16 checks passed
@F16shen F16shen deleted the codex/propose-fix-for-cdn-update-vulnerability branch May 12, 2026 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants