Skip to content

feat(security): verify sha256 of downloaded release assets before replace - #30

Merged
duyetbot merged 1 commit into
mainfrom
cursor/005-upgrade-checksum-ed20
Sep 3, 2026
Merged

feat(security): verify sha256 of downloaded release assets before replace#30
duyetbot merged 1 commit into
mainfrom
cursor/005-upgrade-checksum-ed20

Conversation

@duyet

@duyet duyet commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Closes #20 (Plan 005).

Rebased onto current main (after #44) and resolved the src/upgrade.rs conflict. Main kept the GitHub HTML/token fetch path. This branch still verifies checksums.txt after download.

Why

anyr update downloads a machine executable from GitHub Releases and renames it over the running binary. TLS is the transport check. A SHA-256 file next to the assets closes the partial-download hole and raises the bar for a swapped asset.

Older releases have no checksum file. HTTP 404 still warns on stderr and proceeds.

Scope

  • replace_current_binary still takes url: &str and returns Result<PathBuf, String>.
  • After download it streams Sha256 over the temp file in 64 KiB chunks, then matches checksums.txt in the same release directory.
  • Mismatch removes the temp file and returns hashes and paths only.
  • channel::parse_checksums reads sha256sum lines, skips garbage, and lowercases hex.
  • ci.yml beta job and release-binaries.yml notes job emit and upload checksums.txt.
  • Adds sha2 = "0.10". Version stays on 0.1.x.

Out of scope: signatures (cosign/minisign), setup.sh / npm wrapper, changing caller signatures.

Tradeoffs

404 is a warning, not a hard error, so already published releases keep updating. A present checksum file is mandatory.

Blast radius

Touches the self-replace path and both release upload jobs. Users on old releases still update. Users on new releases abort on a bad or truncated download instead of replacing the binary.

Verification

  • cargo test --locked --all-targets — 195 lib + 68 cli + 7 release_lock passed
  • cargo fmt --check — exit 0
  • YAML parse of ci.yml and release-binaries.yml — exit 0
  • control-anyr launch + doctor — isolated home, version 0.1.11
  • control-anyr cli -- update --check --dry-run with ANYR_RELEASES_JSON=tests/fixtures/releases.json — exit 0, update available, fixture key absent from stdout
  • Clippy baseline is unchanged on this path (unwrap_or_default in persist_channel predates this PR). Plan 007 owns the -D warnings gate.

Checklist

  • Version stays on 0.1.x (do not introduce 0.2 / 1.0)
  • Do not auto-merge release-please PRs
Open in Web Open in Cursor 

@duyet
duyet marked this pull request as ready for review August 28, 2026 05:17
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

anyr size and startup

Startup is wall time for a cold anyr --version / anyr --help (median of 21 runs). Size is the stripped release binary, or the .wasm for the browser demo.

Size budget: anyr-linux-x86_64 must stay ≤ 4.0 MiB (4194304 bytes) stripped.

Asset Kind Size --version median --help median
anyr-darwin-arm64 native 2.8 MiB 5.72 ms 4.85 ms
anyr-darwin-x86_64 native 3.0 MiB 33.33 ms 32.81 ms
anyr-linux-arm64 native 2.8 MiB 0.78 ms 0.8 ms
anyr-linux-x86_64 native 3.1 MiB 0.96 ms 0.95 ms
anyr-windows-x86_64.exe native 2.6 MiB 9.24 ms 9.96 ms
anyr.wasm wasm 176.5 KiB

Budget check: ok

raw timings
[
  {
    "asset": "anyr-darwin-arm64",
    "kind": "native",
    "path": "/Users/runner/work/cli/cli/anyr-darwin-arm64",
    "bytes": 2924712,
    "size": "2.8 MiB",
    "version": "0.1.11 (built 2026-09-03 17:58:30)",
    "target": "aarch64-apple-darwin",
    "os": "macos-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 4.48,
      "median_ms": 5.72,
      "p95_ms": 8.7,
      "mean_ms": 6.22
    },
    "startup_help": {
      "n": 21,
      "min_ms": 3.85,
      "median_ms": 4.85,
      "p95_ms": 8.1,
      "mean_ms": 5.26
    }
  },
  {
    "asset": "anyr-darwin-x86_64",
    "kind": "native",
    "path": "/Users/runner/work/cli/cli/anyr-darwin-x86_64",
    "bytes": 3138880,
    "size": "3.0 MiB",
    "version": "0.1.11 (built 2026-09-03 17:58:13)",
    "target": "x86_64-apple-darwin",
    "os": "macos-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 27.75,
      "median_ms": 33.33,
      "p95_ms": 41.75,
      "mean_ms": 34.06
    },
    "startup_help": {
      "n": 21,
      "min_ms": 29.12,
      "median_ms": 32.81,
      "p95_ms": 34.32,
      "mean_ms": 32.48
    }
  },
  {
    "asset": "anyr-linux-arm64",
    "kind": "native",
    "path": "/home/runner/work/cli/cli/anyr-linux-arm64",
    "bytes": 2888744,
    "size": "2.8 MiB",
    "version": "0.1.11 (built 2026-09-03 17:57:55)",
    "target": "aarch64-unknown-linux-gnu",
    "os": "ubuntu-24.04-arm",
    "startup_version": {
      "n": 21,
      "min_ms": 0.7,
      "median_ms": 0.78,
      "p95_ms": 0.92,
      "mean_ms": 0.78
    },
    "startup_help": {
      "n": 21,
      "min_ms": 0.73,
      "median_ms": 0.8,
      "p95_ms": 0.84,
      "mean_ms": 0.8
    }
  },
  {
    "asset": "anyr-linux-x86_64",
    "kind": "native",
    "path": "/home/runner/work/cli/cli/anyr-linux-x86_64",
    "bytes": 3233152,
    "size": "3.1 MiB",
    "version": "0.1.11 (built 2026-09-03 17:57:56)",
    "target": "x86_64-unknown-linux-gnu",
    "os": "ubuntu-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 0.92,
      "median_ms": 0.96,
      "p95_ms": 0.98,
      "mean_ms": 0.96
    },
    "startup_help": {
      "n": 21,
      "min_ms": 0.93,
      "median_ms": 0.95,
      "p95_ms": 1.05,
      "mean_ms": 0.97
    }
  },
  {
    "asset": "anyr-windows-x86_64.exe",
    "kind": "native",
    "path": "D:\\a\\cli\\cli\\anyr-windows-x86_64.exe",
    "bytes": 2681856,
    "size": "2.6 MiB",
    "version": "0.1.11 (built 2026-09-03T17:59:21Z)",
    "target": "x86_64-pc-windows-msvc",
    "os": "windows-latest",
    "startup_version": {
      "n": 21,
      "min_ms": 8.95,
      "median_ms": 9.24,
      "p95_ms": 9.84,
      "mean_ms": 9.35
    },
    "startup_help": {
      "n": 21,
      "min_ms": 8.92,
      "median_ms": 9.96,
      "p95_ms": 11.57,
      "mean_ms": 10.02
    }
  },
  {
    "asset": "anyr.wasm",
    "kind": "wasm",
    "path": "/home/runner/work/cli/cli/target/wasm-pkg/anyr_cli_bg.wasm",
    "bytes": 180713,
    "size": "176.5 KiB",
    "version": "wasm",
    "target": "wasm32-unknown-unknown",
    "os": ""
  }
]

…lace

Publish checksums.txt (sha256sum anyr-*) with stable and beta release
assets. anyr update fetches the sibling checksums.txt after download,
streams Sha256 over the temp file in 64 KiB chunks, and refuses to
replace on mismatch (temp removed; hashes/paths only). HTTP 404 is
treated as a legacy release: warn on stderr and proceed.

Adds sha2 0.10 (~30–50 KiB); stays within the 4 MiB linux x86_64 budget.

Closes #20

Co-authored-by: duyet <duyet@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/005-upgrade-checksum-ed20 branch from 24f3573 to 4fbf79c Compare September 3, 2026 17:55
@duyetbot
duyetbot merged commit 211c7bc into main Sep 3, 2026
9 checks passed
@duyetbot
duyetbot deleted the cursor/005-upgrade-checksum-ed20 branch September 3, 2026 19:13
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.

Plan 005: Verify a checksum before replacing the binary during self-update

3 participants