Skip to content

Add support for signing and verifying appcast feeds#2822

Merged
zorgiepoo merged 1 commit into2.xfrom
appcast-signing
Jan 1, 2026
Merged

Add support for signing and verifying appcast feeds#2822
zorgiepoo merged 1 commit into2.xfrom
appcast-signing

Conversation

@zorgiepoo
Copy link
Copy Markdown
Member

@zorgiepoo zorgiepoo commented Dec 31, 2025

Add support for signing and verifying appcast feeds

This provides enhanced opt-in security preventing informational attacks that for example suggest the user the auto-updater is broken and an update needs to be installed from a compromised website (assuming the developer's website is hijacked).

To opt in, developers need to add SURequireSignedFeed Info.plist key to their bundle.

generate_appcast and sign_update have been updated to support signing appcast feeds and release note files (both need to be signed). By default, they embed a HTML comment warning in the files that further modifications will invalidate previously signed signatures. Signed appcasts include a signing block comment at the end of the XML file describing the signature of the content preceding it. Signed release notes will have their signatures annotated in the sparkle:releaseNotesLink element with a sparkle:edSignature attribute (and optionally a sparkle:length attribute for diagnostic purposes).

A signingValidationStatus property is also now added to SUAppcast and SUAppcastItem indicating the signing status (skipped, succeeded, failed).

The standard user driver's release notes HTML web view rejects external file references when appcasts are signed. A bug has also been fixed where the update alert would not provide errors when loading external release note files fails. These changes also introduce a showing new error / localizable strings when the release notes is not signed correctly or fails to load.

As a hardening policy, we also enforce SUVerifyUpdateBeforeExtraction when opting into signed feeds, so key rotation is only possible with Developer ID code signed dmg archives.

A fallback feed signing failure expiration policy has also been added. When a signed feed has failed validation for more than a configurable period of time (by default SUSignedFeedFailureExpirationInterval is set to 20 days), Sparkle will allow appcast items to be shown to the user. However, these items operate in a safe mode that strip abusable information like release notes / descriptions / links, sanitize the version strings, and they cannot be marked critical or informational, nor be installed automatically. After this policy expires, this allows users installing updates as long as the updates are signed correctly, or be aware that the updater has not been functioning correctly.

Fixes #971.

Misc Checklist

  • My change requires a documentation update on Sparkle's website repository
  • My change requires changes to generate_appcast, generate_keys, or sign_update

Testing

I tested and verified my change by using one or multiple of these methods:

  • Sparkle Test App
  • Unit Tests
  • My own app
  • Other (please specify)
  • Enabled signed feeds in Sparkle Test App and added unit tests to sign/verify/embed warnings in appcast feeds / release notes.
  • Tested sign_update and generate_appcast for signing appcast feeds and release note files. Tested sign_update and generate_appcast can handle symlinked release note files. Tested hidden --disable-embedded-sign-warning and -p option for both tools for signing feeds and release notes.
  • Tested failure signing expiration policy for same-app (sparkle test app) and external updater (sparkle-cli) successfully, and validated release notes are stripped. Verified when there's no new update, a feed signing error is still shown under this policy, and updates aren't installed automatically.
  • Tested that older Sparkle versions understand newly signed feeds and release notes still.
  • Tested content blocking filter on older OS systems (10.14)

macOS version tested:
26.2 (25C56)
12.7 VM
10.14.6 VM

@zorgiepoo zorgiepoo added this to the 2.9.0 milestone Dec 31, 2025
@zorgiepoo zorgiepoo force-pushed the appcast-signing branch 4 times, most recently from a8f8ad4 to 46940b6 Compare January 1, 2026 04:34
This provides enhanced opt-in security preventing informational attacks that for example suggest the user the auto-updater is broken and an update needs to be installed from a compromised website (assuming the developer's website is hijacked).

To opt in, developers need to add SURequireSignedFeed Info.plist key to their bundle.

generate_appcast and sign_update have been updated to support signing appcast feeds and release note files (both need to be signed). By default, they embed a HTML comment warning in the files that further modifications will invalidate previously signed signatures. Signed appcasts include a signing block comment at the end of the XML file describing the signature of the content preceding it. Signed release notes will have their signatures annotated in the <sparkle:releaseNotesLink> element with a sparkle:edSignature attribute (and optionally a sparkle:length attribute for diagnostic purposes).

A signingValidationStatus property is also now added to SUAppcast and SUAppcastItem indicating the signing status (skipped, succeeded, failed).

The standard user driver's release notes HTML web view rejects external file references when appcasts are signed. A bug has also been fixed where the update alert would not provide errors when loading external release note files fails. These changes also introduce a showing new error / localizable strings when the release notes is not signed correctly or fails to load.

As a hardening policy, we also enforce SUVerifyUpdateBeforeExtraction when opting into signed feeds, so key rotation is only possible with Developer ID code signed dmg archives.

A fallback feed signing failure expiration policy has also been added. When a signed feed has failed validation for more than a configurable period of time (by default SUSignedFeedFailureExpirationInterval is set to 20 days), Sparkle will allow appcast items to be shown to the user. However, these items operate in a safe mode that strip abusable information like release notes / descriptions / links, sanitize the version strings, and they cannot be marked critical or informational, nor be installed automatically. After this policy expires, this allows users installing updates as long as the updates are signed correctly, or be aware that the updater has not been functioning correctly.

Fixes #971.
@zorgiepoo zorgiepoo merged commit 7761e65 into 2.x Jan 1, 2026
2 checks passed
@zorgiepoo zorgiepoo deleted the appcast-signing branch January 1, 2026 19:33
@ychin
Copy link
Copy Markdown

ychin commented Jan 11, 2026

Hi, I just tried this out. I noticed that when I run sign_update myappcast.xml, it reformats the entire XML file. Is that a byproduct of the tool reading and writing the XML file? I would have imagined that it either simply prepend/append a chunk without modifying the contents in file, or just give me a block of text (aka the signature) that I manually include in well-specified region in my XML file.

Just for context, for various workflow and legacy reasons, I currently generate my appcast XML using a script and combine them dynamically using Jekyll (which GitHub Pages will generate the final output for me) instead of using the generate_appcast script. With the way sign_update works, I would have to manually commit a new version of it since the signature would be signed against the reformatted file.

I guess a compromise would be that I could just split out my release notes into a separate file and only sign that, and that can be done cleanly with the signature specified in the appcast (via releaseNotesLink). Actually I don't think that would work since SURequireSignedFeed would mean I have to sign the whole feed.

@zorgiepoo
Copy link
Copy Markdown
Member Author

zorgiepoo commented Jan 11, 2026

Can you try with passing --disable-embedded-sign-warning to sign_update ? The option is currently hidden but I'll probably make it public, and maybe rename it.

The XML reading/writing is done to add a sign warning about making further modifications to the file (unfortunately with XML parsing because XML doesn't allow comments in the very beginning), but the actual signing is just appending a block of comment text to the end of the file (with no XML parsing).

However signing the XML file still has to update the file.. otherwise we would have to serve/download a new/different file just for the signature, which I thought would be the worse alternative than embedding the signature inside the appcast.

@ychin
Copy link
Copy Markdown

ychin commented Jan 11, 2026

Can you try with passing --disable-embedded-sign-warning to sign_update ? The option is currently hidden but I'll probably make it public, and maybe rename it.

Thanks. The flag did work. It appended the signature to the end of file without reformatting of the XML file.

However signing the XML file still has to update the file.. otherwise we would have to serve/download a new/different file just for the signature, which I thought would be the worse alternative than embedding the signature inside the appcast.

Sure, this makes sense. I can see the tradeoffs with this specific design and I think it's fine to embed the signature in the same file. I was just surprised when I saw that the entire file was reformatted. I would vote for exposing this command-line flag (in maybe a more user-friendly name), since I think it should be up to each maintainer to decide whether they want that warning to exist or not as it highly depends on each project's workflow. Just a simple warning may not work anyway if the appcast is generated and someone somehow mistakenly re-generates the appcast without re-signing. Projects that opt in to this will need their own way of making sure the steps are followed.

Actually, I think if this flag is added I think it might be better to just print out the string rather than modifying the file in-place, IMO. It's more flexible for the user who could then add that to their own file in their own way, and it's more consistent with how the sign_update tool works with other files as it usually just prints out a string that you can embed into elsewhere.

In fact, playing around with this, it seems like I can already do this in a hacky version by just renaming myappcast.xml to myappcast.txt, call sign_update on it, then parse the results and manually extract the signature to the form that the appcast signature wants… 😅

@zorgiepoo
Copy link
Copy Markdown
Member Author

zorgiepoo commented Jan 11, 2026

Sure, this makes sense. I can see the tradeoffs with this specific design and I think it's fine to embed the signature in the same file. I was just surprised when I saw that the entire file was reformatted.

Me too. Maybe I can get it to preserve formatting more, but not sure. Need to look into that.

I would vote for exposing this command-line flag (in maybe a more user-friendly name)

Indeed but I'm still thinking it should be on by default(?).

Actually, I think if this flag is added I think it might be better to just print out the string rather than modifying the file in-place, IMO. It's more flexible for the user who could then add that to their own file in their own way

This allows it to be possible for the developer to add/update the signing block incorrectly (e.g add extra newline or whatever). This block is more stringent than other parts of the file and isn't as forgiving to place with adjustment. In this case, there is no other file to embed the signature into; it's the same file. There should be no downside for sign_update just updating it for you in the byte-exact way it's intended.

@zorgiepoo
Copy link
Copy Markdown
Member Author

For this discussion, #2840 was made so the file should not be entirely reformatted when adding the signing warning, and #2841 exposes --disable-signing-warning to sign_update and generate_appcast

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.

Signing appcasts

2 participants