Problem
The release job's winget step authenticates with a maintainer-supplied personal access token: .github/workflows/release.yml:180-186 runs the winget-releaser action with token: ${{ secrets.WINGET_TOKEN }}. This is the only secret in the release pipeline that is not the ephemeral, repo-scoped GITHUB_TOKEN (the other secret usages are secrets.GITHUB_TOKEN in auto-release.yml:37 and :122, and secrets.CODECOV_TOKEN in ci.yml:83 and :91) - it must carry write access to a fork of microsoft/winget-pkgs in order to open the upstream manifest PR. It is therefore the highest-value credential in the distribution chain, and the winget manifest is the one channel where the binary URL and hash users receive are asserted outside this repo. Yet SECURITY.md's supply-chain section (lines 143-150) does not mention it, and the threat model scopes "Update channel attacks (spoofed releases, signature bypass)" as in-scope (SECURITY.md:161) while describing only two distribution paths - the GitHub Releases API (SECURITY.md:109-116) and the portable/winget package (SECURITY.md:117-124) - without acknowledging the credential that publishes the latter.
Proposed solution
Add a bullet to SECURITY.md:143-150 recording the winget publishing posture: that release automation uses a separate token whose only purpose is opening a manifest PR against the maintainer's winget-pkgs fork, that it is scoped as narrowly as GitHub permits (a fine-grained PAT limited to that single fork with contents write, rather than a classic full-repo token), and that it carries an expiry and is rotated. Verify the live token actually matches that description before documenting it, and set a calendar expiry so rotation is forced rather than remembered. Nothing about the workflow needs to change if the token is already fine-grained - the gap being closed is that the posture is undocumented and therefore unverifiable by a reader.
Rationale
SECURITY.md invites vulnerability reports and explicitly puts update-channel attacks in scope, so it should account for every credential that can influence what a user installs. A compromised or over-scoped WINGET_TOKEN is the one path by which a third party could get a manifest pointing at a binary of their choosing in front of users who install via the headline winget install laurentiu021.SysManager command (README.md:29) - a path that the app's own SHA256 self-update check cannot see, because it only ever validates the GitHub Releases asset. Documenting the scoping and rotation makes the strongest link in the chain auditable; if the audit shows the token is a classic full-scope PAT, narrowing it is a five-minute fix with real risk reduction.
Evidence
Read .github/workflows/release.yml:180-186 in full - - name: Update winget package / uses: vedantmgoyal9/winget-releaser@4ffc... / identifier: laurentiu021.SysManager / token: ${{ secrets.WINGET_TOKEN }}. Enumerated every secret in the pipeline: grep -rn "secrets\." .github/workflows/ -> auto-release.yml:37 and :122 (GITHUB_TOKEN), ci.yml:83 and :91 (CODECOV_TOKEN), release.yml:186 (WINGET_TOKEN) - WINGET_TOKEN is the only non-ephemeral, cross-repo one. Read SECURITY.md:143-150 (supply chain: three bullets, no token mention) and SECURITY.md:152-161 (Scope: "Update channel attacks (spoofed releases, signature bypass)" in scope). Grepped SECURITY.md for token/secret/PAT/fork -> only line 159 "Credential or token exposure (shouldn't apply - the app stores neither)", which is about the app's runtime, not the release pipeline. Confirmed README.md:29 makes winget the headline install path.
Risk / trade-off
Documenting the existence of a high-value token slightly advertises a target, though the workflow file is already public and the token reference is visible in it - so the disclosure is nil and the transparency is a net gain. The real work is the audit of the live token's actual scope, which cannot be verified from the snapshot; if it turns out to be a classic full-repo PAT, narrowing it risks breaking the winget step on the next release and should be validated with a manual workflow_dispatch rather than discovered during an automated release.
Affected area
Release
Effort: S | priority: value 3/5, fit 4/5
Identified during the trust, distribution and reach audit audit.
Problem
The release job's winget step authenticates with a maintainer-supplied personal access token: .github/workflows/release.yml:180-186 runs the winget-releaser action with
token: ${{ secrets.WINGET_TOKEN }}. This is the only secret in the release pipeline that is not the ephemeral, repo-scoped GITHUB_TOKEN (the other secret usages aresecrets.GITHUB_TOKENin auto-release.yml:37 and :122, andsecrets.CODECOV_TOKENin ci.yml:83 and :91) - it must carry write access to a fork of microsoft/winget-pkgs in order to open the upstream manifest PR. It is therefore the highest-value credential in the distribution chain, and the winget manifest is the one channel where the binary URL and hash users receive are asserted outside this repo. Yet SECURITY.md's supply-chain section (lines 143-150) does not mention it, and the threat model scopes "Update channel attacks (spoofed releases, signature bypass)" as in-scope (SECURITY.md:161) while describing only two distribution paths - the GitHub Releases API (SECURITY.md:109-116) and the portable/winget package (SECURITY.md:117-124) - without acknowledging the credential that publishes the latter.Proposed solution
Add a bullet to SECURITY.md:143-150 recording the winget publishing posture: that release automation uses a separate token whose only purpose is opening a manifest PR against the maintainer's winget-pkgs fork, that it is scoped as narrowly as GitHub permits (a fine-grained PAT limited to that single fork with contents write, rather than a classic full-
repotoken), and that it carries an expiry and is rotated. Verify the live token actually matches that description before documenting it, and set a calendar expiry so rotation is forced rather than remembered. Nothing about the workflow needs to change if the token is already fine-grained - the gap being closed is that the posture is undocumented and therefore unverifiable by a reader.Rationale
SECURITY.md invites vulnerability reports and explicitly puts update-channel attacks in scope, so it should account for every credential that can influence what a user installs. A compromised or over-scoped WINGET_TOKEN is the one path by which a third party could get a manifest pointing at a binary of their choosing in front of users who install via the headline
winget install laurentiu021.SysManagercommand (README.md:29) - a path that the app's own SHA256 self-update check cannot see, because it only ever validates the GitHub Releases asset. Documenting the scoping and rotation makes the strongest link in the chain auditable; if the audit shows the token is a classic full-scope PAT, narrowing it is a five-minute fix with real risk reduction.Evidence
Read .github/workflows/release.yml:180-186 in full -
- name: Update winget package/uses: vedantmgoyal9/winget-releaser@4ffc.../identifier: laurentiu021.SysManager/token: ${{ secrets.WINGET_TOKEN }}. Enumerated every secret in the pipeline:grep -rn "secrets\." .github/workflows/-> auto-release.yml:37 and :122 (GITHUB_TOKEN), ci.yml:83 and :91 (CODECOV_TOKEN), release.yml:186 (WINGET_TOKEN) - WINGET_TOKEN is the only non-ephemeral, cross-repo one. Read SECURITY.md:143-150 (supply chain: three bullets, no token mention) and SECURITY.md:152-161 (Scope: "Update channel attacks (spoofed releases, signature bypass)" in scope). Grepped SECURITY.md for token/secret/PAT/fork -> only line 159 "Credential or token exposure (shouldn't apply - the app stores neither)", which is about the app's runtime, not the release pipeline. Confirmed README.md:29 makes winget the headline install path.Risk / trade-off
Documenting the existence of a high-value token slightly advertises a target, though the workflow file is already public and the token reference is visible in it - so the disclosure is nil and the transparency is a net gain. The real work is the audit of the live token's actual scope, which cannot be verified from the snapshot; if it turns out to be a classic full-
repoPAT, narrowing it risks breaking the winget step on the next release and should be validated with a manual workflow_dispatch rather than discovered during an automated release.Affected area
Release
Effort: S | priority: value 3/5, fit 4/5
Identified during the trust, distribution and reach audit audit.