Skip to content

hub: verify content hashes, harden download resume & accounting#19

Merged
REPPL merged 1 commit into
mainfrom
fix/download-integrity
Jul 18, 2026
Merged

hub: verify content hashes, harden download resume & accounting#19
REPPL merged 1 commit into
mainfrom
fix/download-integrity

Conversation

@REPPL

@REPPL REPPL commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Download-logic review follow-up (separate from the settings PR #18). All in
internal/hub, full suite green.

Content integrity (the main one)

LFS files — the model weights — carry a sha256 in LFS.OID, but the downloader
validated size only. A corrupt-but-right-length body, or corruption in a
resumed prefix (the appended tail was size-checked, the prefix never was), was
silently accepted. The finished .part is now streamed through sha256 and
compared to LFS.OID before rename; a mismatch is rejected and removed. Non-LFS
files carry a git-blob sha1 (not a content hash) and stay size-checked.

Resume hardening

  • Wrong Content-Range: a 206 that doesn't resume where we asked (e.g. a proxy
    that returns the whole body as "partial") was blindly appended onto the
    .part. The offset/total are now validated; a bad range discards the partial
    and restarts cleanly instead of corrupting or failing the download.
  • Size-unknown (size 0): disabled every integrity check, so an empty/truncated
    file was renamed in as "complete". A size-0 download returning nothing is now
    rejected; a size-0 existing file is re-fetched, not adopted.

Accounting / robustness

  • Duplicate tree entries de-duplicated in WantedFiles — a manifest listing
    the same path twice no longer spawns two goroutines racing on one file, nor
    double-counts it (which kept progress under 100%).
  • Leftover .part beside a completed file is cleaned up; reported progress is
    clamped to [0, total] so a pathological pre-existing-file state can't render a
    negative or >100% bar.

Tests cover hash match/mismatch, wrong-Content-Range restart, empty-file
rejection, dedup, and validContentRange — each fails against the old code.

Assisted-by: Claude:claude-fable-5

Correctness/integrity gaps found in a download-logic review:

- Content integrity: LFS files (the weights) carry a sha256 in LFS.OID, but the
  downloader validated size only — a corrupt-but-right-length body, or corruption
  in a resumed prefix, was silently accepted. Now the finished .part is streamed
  through sha256 and compared to LFS.OID before it is renamed into place; a
  mismatch is rejected and removed. Non-LFS files carry a git-blob sha1 (not a
  content hash) and remain size-checked.
- Resume: a 206 whose Content-Range does not start where we asked (a buggy
  proxy that returns the whole body as "partial") was blindly appended onto the
  .part. The offset/total are now validated; a wrong range discards the partial
  and restarts cleanly instead of corrupting or failing the file.
- Empty/size-unknown: a size-0 manifest entry disabled every integrity check, so
  an empty or truncated file was renamed into place as "complete". A size-0
  download that returns nothing is now rejected, and a size-0 existing file is
  re-fetched rather than adopted.
- Duplicate tree entries are de-duplicated in WantedFiles, so a manifest listing
  the same path twice no longer spawns two goroutines racing on one file (and no
  longer double-counts it so progress can't reach 100%).
- A leftover .part beside a completed file is now cleaned up; reported progress
  is clamped to [0, total] so a pathological pre-existing-file state can't show a
  negative or >100% bar.

Tests cover hash match/mismatch, wrong-Content-Range restart, empty-file
rejection, dedup, and validContentRange; each fails against the old code.

Assisted-by: Claude:claude-fable-5
@REPPL
REPPL merged commit aa8cd3d into main Jul 18, 2026
6 checks passed
@REPPL
REPPL deleted the fix/download-integrity branch July 18, 2026 13:40
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.

1 participant