Skip to content

cli: replace updater curl/tar with reqwest+flate2+tar, add integrity checks (#640)#641

Open
tiagonix wants to merge 1 commit into
Tracer-Cloud:mainfrom
tiagonix:640-native-updater-integrity-atomic-replace
Open

cli: replace updater curl/tar with reqwest+flate2+tar, add integrity checks (#640)#641
tiagonix wants to merge 1 commit into
Tracer-Cloud:mainfrom
tiagonix:640-native-updater-integrity-atomic-replace

Conversation

@tiagonix

@tiagonix tiagonix commented Feb 8, 2026

Copy link
Copy Markdown

📌 Summary

Replaces shell-based updater with native Rust implementation featuring cryptographic integrity verification, hardened archive extraction, and atomic replacement with rollback capability.

🔍 Related Issues/Tickets

Part of ongoing effort to eliminate external dependencies and harden security-critical operations.
Closes #640

✨ Changes Introduced

Security hardening:

  • SHA256 checksum verification prevents compromised/tampered downloads
  • Request timeout (30s) and redirect limits (5) prevent hang/redirect attacks
  • Path traversal protection rejects .., absolute paths, symlinks, and hardlinks
  • Extracts only expected tracer binary, ignoring other archive contents

Reliability improvements:

  • Native reqwest download with proper HTTP status handling
  • Native flate2+tar extraction with typed error context
  • Same-filesystem temp directory avoids EXDEV rename failures
  • Backup/rollback mechanism (.bak file) for failed updates
  • RAII cleanup guard ensures temp directory removal
  • Graceful runtime creation (no panic on error)

Testing:

  • Comprehensive unit tests for valid extraction, subdirectory handling, path traversal rejection, absolute path rejection, symlink rejection, missing binary detection, and checksum verification

✨ Infrastructure Impact

None. Changes are isolated to the updater module. Downloads continue from existing S3 bucket structure.

✅ Checklist

  • Code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have tested the changes and they work as expected
  • Documentation has been updated if needed
  • Tests have been added or updated

🛠️ How to Test

  1. Install tracer using the branch-specific installer command above
  2. Run tracer update to verify download, checksum verification, and replacement succeed
  3. Run cargo test -p tracer to verify unit tests pass
  4. Test failure scenarios: corrupt checksum file, missing binary in archive

📌 Additional Notes

This eliminates runtime dependencies on curl and tar binaries, improving portability across minimal container images and systems with restricted PATH. The security improvements are essential for self-updater operations where compromise of the download channel could lead to arbitrary code execution.

…checks (Tracer-Cloud#640)

Replace curl/tar process spawning with native Rust download (reqwest) and
extraction (flate2+tar), improving portability and error context.

Harden archive extraction to reject path traversal, absolute paths, and
symlinks/hardlinks—extracting only the expected tracer binary entry.

Add critical security measures for self-updater:
- SHA256 checksum verification of downloaded tarballs
- Request timeout (30s) and redirect limit (5) to prevent hangs/attacks
- Backup/rollback mechanism (.bak file) for failed updates
- Graceful runtime creation error handling (no panic)

Place temp files alongside the target binary to avoid cross-filesystem
rename failures (EXDEV). Add comprehensive unit tests covering valid and
malicious archive entries.

Files changed:
- Cargo.toml: Add sha2 workspace dependency
- src/tracer/Cargo.toml: Add sha2 dependency
- src/tracer/src/cli/handlers/update/updater.rs: Core implementation
@WatchTree-19

Copy link
Copy Markdown

hey @tiagonix, dropping a review since this PR's been sitting a while and it's substantive Rust work. two thoughts plus one nit:

  1. tokio runtime creation in update(). the new Runtime::new() + rt.block_on(update_impl()) works fine when update() is called from a sync context. if the CLI ever dispatches update from within an existing runtime (e.g. a future async command handler calls into it), block_on will panic with "Cannot start a runtime from within a runtime". the defensive shape is something like:
let result = match tokio::runtime::Handle::try_current() {
    Ok(handle) => tokio::task::block_in_place(|| handle.block_on(update_impl())),
    Err(_) => Runtime::new()?.block_on(update_impl()),
};

probably fine for now since update() is sync-dispatched, but worth a comment in the code for the next person.

  1. backup integrity vs rollback assumption. if std::fs::copy(current_binary_path, &backup_path) fails (permissions, disk full), the warning-and-continue path means the subsequent rename failure has no backup to roll back from. the rollback block does if backup_path.exists() defensively which is good, but the user-facing flow loses the distinction between "rollback succeeded" and "rollback couldn't run because backup didn't exist". on write-protected systems where the binary is root-owned, this is the most likely failure path. one option: fail fast on backup creation failure so an update never runs without rollback safety. another: keep the warning-and-continue but make the post-failure error surface explicitly that no backup existed.

  2. nit: EXPECTED_BINARY_NAME is matched on file_name() only, so any entry whose final component is "tracer" extracts. if a tarball ever ships tools/tracer (utility helper) and bin/tracer (the actual binary), iteration order decides which one wins. tar iteration is deterministic by entry order so this is hypothetical. tighter check (require the path to be exactly "tracer" or "bin/tracer") would future-proof. not blocking.

security hardening (sha256, path traversal rejection, symlink/hardlink rejection, single-binary extraction) is solid. lgtm with the two thoughts above.

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.

Replace updater shell-outs with native Rust download/extract, harden archive handling, add integrity verification, and fix atomic replacement

2 participants