Repository navigation
Sign msix packages, and fail the build when signing does not happen - #22
Merged
Merged
Conversation
… does not happen TownSuite/TownSuite.IssueTracker#15101 Uploads were stored under a generated id with no extension, so signtool did not recognize an msix and failed the whole batch, leaving the msi and exe unsigned as well. The failure was then invisible: batch errors were written under the batch id but polled for under the file id, and the client treated its own timeout as success, so builds published unsigned installers. - Client sends the original extension as X-FileExtension on upload and poll, and the service keeps it on the working file so signtool recognizes msix and appx. An absent header keeps the previous behaviour. - Validate that extension against a short alphanumeric pattern before it reaches a signtool command line. - GET /sign/batch checks the batch level error file as well as the per-file one, so a batch failure returns 500 instead of 425 until the client gives up. - DownloadSignedFiles reports files still outstanding when the batch timeout expires, so a timeout exits non-zero. - Add coverage, including an end to end msix sign through the batch endpoint using real makeappx and signtool.
…an msix TownSuite/TownSuite.IssueTracker#15101 Recovering the extension from the X-FileExtension header alone meant nothing improved until a new client was released, pinned in codesigning.ps1 and rolled out to the build agents. An msix is a zip carrying an AppxManifest, so the service can identify one on its own and the client needs no change at all. - Fall back to content detection when no extension header is sent. The header still wins when present. - Locate the finished file by id prefix instead of rebuilding its name from the header, so the download no longer depends on the caller sending anything. ISigner.GetFileName is replaced by FindResultFile. - Drop the extension header from the poll request, which no longer needs it. - Cover appx packages and bundles, plain zips that must not be mistaken for one, and non-zip and truncated input.
majorsilence
approved these changes
Aug 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
TL/DR: Fixed silent publishing of unsigned msix files. Uploads now keep their extension (via
X-FileExtensionheader or zip-content detection), and batch signing errors/timeouts are surfaced as failures instead of the client timing out and shipping unsigned artifacts.Uploads were stored under a generated id with no file extension.
signtoolpicks the handler for container formats such as msix by file name, so it refused the package and exited non-zero for the whole batch, leaving the msi and exe unsigned as well.Two further defects hid that failure. A batch level signing error was written under the batch id but polled for under the per-file id, so every poll returned 425 until the client gave up 1200 seconds later. The client then reported no failures and exited 0, so the pipeline generated checksums over unsigned artifacts and published them to Box.
The service now keeps the extension on the working file, taking it from the
X-FileExtensionheader when the client sends one and otherwise detecting an appx package from its content, so the currently deployed client signs anmsixcorrectly with no client-side change. Batch errors are surfaced to pollers, and an expired batch timeout is reported as a failure instead of success.Covered end to end by packing a real msix with makeappx and signing it through the batch endpoint with no header at all, asserting the returned package carries an AppxSignature.p7x.
Example logs:
Motivation and Context
https://github.com/TownSuite/TownSuite.IssueTracker/issues/15101
Types of changes
Checklist: