LC-210-BINARY-INTEGRITY: verify desktop update artifact signature/checksum before self-replace #277

Closed
opened 2026-05-31 03:13:41 +02:00 by longjacksonle · 0 comments

Problem

The desktop self-updater downloads a release binary and self-replaces the running executable with no signature or checksum verification. LC-210 closed the SSRF / redirect-target vector on the fetch, but integrity of the artifact is still unverified: a redirect to a public attacker-controlled host, or a compromised update mirror, still serves a binary that apply() will chmod +x and swap in to run with the user's privileges. The SSRF guard is necessary but not sufficient.

This is the deferral named in the LC-210 PR's "what this PR does NOT close" section (#276). It was recommended for its own follow-up ticket and was never filed.

Where

  • desktop/src/update.rs::apply (lines ~140-179): streams artifact.url to a temp file, sets mode 0o755, calls self_replace::self_replace(&tmp_path). No hash check, no signature check between download and replace.
  • desktop/src/update.rs::fetch_manifest (lines ~74-81): the manifest itself is unsigned, so even a per-artifact hash carried in the manifest is only as trustworthy as the manifest fetch.

Failure mode

Supply-chain RCE on the update path. Any of: a mirror compromise, a TLS-trust break, or an operator pointing LETS_CHAT_UPDATE_URL at a host that is later compromised, results in an attacker binary being executed on the next --update.

Proposed scope

  • Sign the manifest (Ed25519 or detached GPG) over version + per-platform url + per-platform sha256.
  • Embed the public key at compile time in the desktop binary.
  • In apply(): verify the manifest signature, then verify the downloaded artifact's sha256 against the signed manifest value, BEFORE chmod +x / self_replace. Fail closed on any mismatch.
  • Decide key-rotation story (single embedded key vs a small keyring) as part of design.
  • Add a test that a tampered artifact (wrong bytes) and a tampered manifest (bad signature) are both rejected before replace.

Acceptance

apply() refuses to replace the running binary unless both the manifest signature and the artifact hash verify. Covered by tests. Out of scope: TUF-style full update framework; this is the minimum sign-and-verify.

Anchored to LC-210 (desktop self-updater). Severity: security (highest of the post-arc sweep), though exploitation requires mirror compromise or a TLS break.

## Problem The desktop self-updater downloads a release binary and self-replaces the running executable with **no signature or checksum verification**. LC-210 closed the SSRF / redirect-target vector on the *fetch*, but integrity of the *artifact* is still unverified: a redirect to a public attacker-controlled host, or a compromised update mirror, still serves a binary that `apply()` will `chmod +x` and swap in to run with the user's privileges. The SSRF guard is necessary but not sufficient. This is the deferral named in the LC-210 PR's "what this PR does NOT close" section (#276). It was recommended for its own follow-up ticket and was never filed. ## Where - `desktop/src/update.rs::apply` (lines ~140-179): streams `artifact.url` to a temp file, sets mode 0o755, calls `self_replace::self_replace(&tmp_path)`. No hash check, no signature check between download and replace. - `desktop/src/update.rs::fetch_manifest` (lines ~74-81): the manifest itself is unsigned, so even a per-artifact hash carried in the manifest is only as trustworthy as the manifest fetch. ## Failure mode Supply-chain RCE on the update path. Any of: a mirror compromise, a TLS-trust break, or an operator pointing `LETS_CHAT_UPDATE_URL` at a host that is later compromised, results in an attacker binary being executed on the next `--update`. ## Proposed scope - Sign the manifest (Ed25519 or detached GPG) over `version` + per-platform `url` + per-platform `sha256`. - Embed the public key at compile time in the desktop binary. - In `apply()`: verify the manifest signature, then verify the downloaded artifact's sha256 against the signed manifest value, BEFORE `chmod +x` / `self_replace`. Fail closed on any mismatch. - Decide key-rotation story (single embedded key vs a small keyring) as part of design. - Add a test that a tampered artifact (wrong bytes) and a tampered manifest (bad signature) are both rejected before replace. ## Acceptance `apply()` refuses to replace the running binary unless both the manifest signature and the artifact hash verify. Covered by tests. Out of scope: TUF-style full update framework; this is the minimum sign-and-verify. Anchored to LC-210 (desktop self-updater). Severity: security (highest of the post-arc sweep), though exploitation requires mirror compromise or a TLS break.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat#277
No description provided.