You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Update integrity rests on the GitHub release alone unless a publisher policy is set; the Windows publisher check skips non-executable files #181
Related: #102 (signed releases), #103 (updates under administrator control).
Default path.DesktopUpdater.Create uses Velopack's GithubSource (src/SemanticRouter.Desktop/DesktopUpdater.cs:74-76). Velopack checks each package against the hash in releases.<channel>.json, but that file comes from the same release. Verify returns true when the policy names no publisher (DesktopUpdater.cs:316).
So anyone who can change a release's assets (a stolen maintainer token, a compromised release job) can ship an update that installs silently and is applied when the app quits.
On Linux no publisher check is possible at all (UpdatePublisher.cs:69-72).
The Windows publisher check covers only executables. With a publisher policy, only zip entries under lib/app/ that start with MZ are checked (src/SemanticRouter.Desktop/UpdatePublisher.cs:103).
Data files such as SemanticRouter.runtimeconfig.json and SemanticRouter.deps.json are neither signed nor checked.
The app is published self-contained but not single-file (deploy/publish-desktop.sh:18-21), so these loose files decide what the .NET host loads and how (probing paths, runtime properties).
On macOS, codesign --deep --strict seals every file, so there is no such gap there.
Feed override in release builds.SEMANTICROUTER_UPDATE_SOURCE (DesktopUpdater.cs:107) is described as "for testing", but every build honours it.
It accepts a local folder or a UNC path.
The only thing refused is plain http to a host that is not loopback (line 108).
Evidence
The code is as cited. The UpdatePublisher and DesktopUpdater tests pass.
Impact
A compromised release or feed can deliver code. So can a process that can set the user's environment, but that crosses no privilege boundary for a process running as the same user. The main risk is the release itself.
Suggested fix
Once signing lands (Signed, verifiable releases: signatures, provenance, SBOM and checksums #102), verify the publisher by default: bake the project's own certificate or team ID into the app instead of relying on a machine policy. Until then, say in docs/desktop.md that updates are trusted on GitHub's word.
On Windows, also refuse a package whose non-executable files differ from a manifest signed with the release, or check a detached signature over the whole .nupkg.
Honour SEMANTICROUTER_UPDATE_SOURCE only in debug and test builds, or only when the policy allows it. Require HTTPS for feeds that are not local, instead of only refusing plain HTTP.
Done when
Once the certificates exist, release builds verify the publisher of every update by default.
The Windows check covers every file in lib/app/, tested with a package whose runtimeconfig.json was changed.
The environment override is gone from release builds or gated by policy, with a test of DesktopUpdater.Feed.
Draft PR #319 covers the second "Done when": under Updates:Publisher on Windows, every file of an update must now match a list of SHA-256 hashes (SemanticRouter.Files.dll, written at publish and signed by vpk with the release). It's tested with a package whose runtimeconfig.json was changed after signing. The environment-override item was already on main (Updates:AllowCustomFeed). The default path (no policy) and the first "Done when" (which waits for the certificates, #102) are still open.
Draft PR #323 (stacked on #319) covers the default path, without a publisher policy. An update from GitHub must now be attested by release.yml at its version's tag: the app asks GitHub's attestations API for the package's SHA-256 and verifies the Sigstore bundle itself, against the trust root it carries. That covers the certificate chain and identity, the DSSE signature, and the Rekor entry with its inclusion proof and checkpoint. A missing or failing attestation refuses the update. When the check can't run (offline, rate-limited, an unreadable log entry), the update is applied with a warning. Such updates are downloaded whole, since Velopack re-zips a package it rebuilds from deltas. The first "Done when" (verifying the publisher by default) still waits for the certificates in #102.
Activity
Tighten desktop update feeds and the Windows publisher check
siebe41 commentedon Oct 21, 2026
Draft PR #319 covers the second "Done when": under
Updates:Publisheron Windows, every file of an update must now match a list of SHA-256 hashes (SemanticRouter.Files.dll, written at publish and signed by vpk with the release). It's tested with a package whoseruntimeconfig.jsonwas changed after signing. The environment-override item was already on main (Updates:AllowCustomFeed). The default path (no policy) and the first "Done when" (which waits for the certificates, #102) are still open.siebe41 commentedon Oct 21, 2026
Draft PR #323 (stacked on #319) covers the default path, without a publisher policy. An update from GitHub must now be attested by
release.ymlat its version's tag: the app asks GitHub's attestations API for the package's SHA-256 and verifies the Sigstore bundle itself, against the trust root it carries. That covers the certificate chain and identity, the DSSE signature, and the Rekor entry with its inclusion proof and checkpoint. A missing or failing attestation refuses the update. When the check can't run (offline, rate-limited, an unreadable log entry), the update is applied with a warning. Such updates are downloaded whole, since Velopack re-zips a package it rebuilds from deltas. The first "Done when" (verifying the publisher by default) still waits for the certificates in #102.