fix(updater): harden DMG downloads#787
Conversation
Commit synced downloads atomically so failed transfers preserve a valid cache. Bound stalled upstream I/O and stream cached-image hashing to avoid whole-file allocations.
There was a problem hiding this comment.
Pull request overview
This pull request hardens the updater’s upstream DMG download and wrapper-update flows to avoid corrupting an existing cached Codex.dmg on failed transfers, while also reducing memory usage when hashing DMGs to derive candidate package versions.
Changes:
- Switches DMG downloading to write into a unique temporary file in the destination directory and atomically
renameinto place only after flush +sync_all, preserving any existing cache on failures. - Centralizes reqwest client construction in
upstream::http_client()and applies connect/stalled-read timeouts consistently across upstream requests. - Streams DMG hashing for wrapper package version derivation (instead of reading the full DMG into memory) and adds test coverage for the streamed hashing behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| updater/src/wrapper_apply.rs | Uses streamed hashing to derive wrapper package versions from DMGs and adds a unit test for the new hashing approach. |
| updater/src/upstream.rs | Implements temp-file + atomic replace DMG downloads, adds shared HTTP client factory with timeouts, and expands download failure-mode tests. |
| updater/src/app.rs | Switches upstream-check cycle to use the shared upstream HTTP client configuration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let file = fs::File::open(dmg_path) | ||
| .with_context(|| format!("Failed to read {}", dmg_path.display()))?; | ||
| let mut reader = BufReader::new(file); |
Yo-DDV
left a comment
There was a problem hiding this comment.
Thanks for hardening the updater path. I checked current head c1af3bf7.
I ran:
cargo fmt --checkcargo check -p codex-update-managercargo test -p codex-update-manager upstreamcargo test -p codex-update-manager wrapper_apply::tests::derives_package_version_with_streamed_dmg_hashgit diff --check origin/main...HEAD
CI is green. The atomic download/cache behavior and streamed hash path look aligned from my side. Final review and merge stay with @ilysenko.
|
@ryan-mt Thank you for these hardening changes! |
|
Thanks for the update. I found two additional blockers:
|
Withdrawing — duplicates the owner's existing review.
|
Thank you for identifying and implementing the updater download-hardening direction. We incorporated that work into #834 with the outstanding review concerns addressed: downloads are published to immutable SHA-256-addressed paths so daemon and wrapper flows cannot replace each other's input, the completed file and parent directory are synced, and hashing is streamed in both the updater and acceptance engine. I am closing this PR as superseded by #834. |
Summary
Root cause
The updater created
Codex.dmgbefore the GET request succeeded. That truncated a valid cache and allowed failed transfers to leave partial data at the recorded path. The wrapper-update path also read the entire DMG into memory to derive its package version.Validation
cargo fmt --checkcargo check -p codex-update-managercargo test -p codex-update-manager upstream— 7 passedcargo test -p codex-update-manager wrapper_apply::tests::derives_package_version_with_streamed_dmg_hash— 1 passedupstream/mainfeature-picker race failures — 217 passedupstream/mainbaseline reproduced the same three feature-picker failures (214 passed, 3 failed)git diff --check