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
Automate Winget manifest submission on release (#1263)
* Automate Winget manifest submission on release
Add winget-publish.yml workflow that triggers on release: published
(same event as PyPI publish) and uses vedantmgoyal9/winget-releaser
to submit an updated manifest PR to microsoft/winget-pkgs automatically.
The workflow skips the rolling 'latest' tag, uses harden-runner with
egress blocking, and pins all actions to commit SHAs following the
project's existing security pattern. Requires a WINGET_TOKEN secret
(GitHub PAT with public_repo scope) to be set in repository settings.
https://claude.ai/code/session_01Sf3iaNmGSZ7UAhFrXZRYzi
* Extend supply chain threat model with Winget
Add Winget Community Repository (A-09) and WINGET_TOKEN PAT (A-10) as
new assets, with three new dataflows: DF-27 (CI manifest PR submission),
DF-28 (consumer winget install), and DF-29 (MSI download via winget).
New threats:
- DFT-34: Long-lived stored PAT enables persistent publish rights after
exfiltration (unlike OIDC's short-lived tokens used for PyPI)
- DFT-35: Compromised PAT enables malicious installer URL injection via
manifest PR (Winget distributes references to binaries, not binaries
directly, so an attacker can craft a valid-hash PR for a trojanised MSI)
New controls:
- C-041: Winget manifest PRs reviewed by microsoft/winget-pkgs maintainers
- C-042: WINGET_TOKEN scoped to dedicated 'winget' GitHub environment
Regenerated doc/explanation/threat_model_supply_chain.rst from source.
https://claude.ai/code/session_01Sf3iaNmGSZ7UAhFrXZRYzi
* Review comments
* Add setup docs
* Review comment
---------
Co-authored-by: Claude <noreply@anthropic.com>
Threat model for dfetch. Covers the pre-install lifecycle: code contribution, CI/CD, build (wheel / sdist), PyPI distribution, and consumer installation. The installed dfetch package is the handoff point to tm_usage.py.
21
+
Threat model for dfetch. Covers the pre-install lifecycle: code contribution, CI/CD, build (wheel / sdist), PyPI distribution, Winget manifest submission, and consumer installation. The installed dfetch package is the handoff point to tm_usage.py.
22
22
23
23
Assumptions
24
24
-----------
@@ -79,6 +79,9 @@ Boundaries
79
79
* - PyPI / TestPyPI
80
80
- Python Package Index and its staging registry. dfetch publishes via OIDC trusted publishing - no long-lived API token stored.
81
81
82
+
* - Winget Community Repository
83
+
- The Windows Package Manager Community Repository (https://github.com/microsoft/winget-pkgs) where dfetch's Winget manifest is hosted. Manifest PRs are submitted automatically by the CI release pipeline (winget-publish.yml) using the stored WINGET_TOKEN PAT (A-10). Consumer installations resolve manifests from this repository; winget downloads the MSI installer from the URL declared in the manifest (pointing to GitHub Releases, A-01) and verifies its SHA256 hash.
externalentity_AWingetCommunityRepositorymicrosoftwingetpkgs_7113ed0f48 -> actor_ConsumerEndUser_f8af758679: DF-29: Consumer downloads MSI via winget
433
480
@enduml
434
481
435
482
.. raw:: html
@@ -505,7 +552,7 @@ Asset Identification
505
552
- Data
506
553
- High / High / High
507
554
* - A-06: GitHub Actions Workflow
508
-
- CI/CD pipelines: test, build (wheel/msi/deb/rpm), lint, CodeQL, Scorecard, dependency-review, docs, release. All actions pinned by commit SHA. harden-runner used in every workflow that executes steps on a runner (egress: block with endpoint allowlist); ci.yml is a dispatcher-only workflow with no runner steps and does not include harden-runner.
555
+
- CI/CD pipelines: test, build (wheel/msi/deb/rpm), lint, CodeQL, Scorecard, dependency-review, docs, release, winget-publish. All actions pinned by commit SHA. harden-runner used in every workflow that executes steps on a runner (egress: block with endpoint allowlist); ci.yml is a dispatcher-only workflow with no runner steps and does not include harden-runner. winget-publish.yml uses a stored PAT (WINGET_TOKEN, A-10) to submit manifest PRs to the Winget Community Repository (A-09).
509
556
- Process
510
557
- Medium / Medium / Medium
511
558
* - A-07: dfetch Build / Dev Dependencies
@@ -520,6 +567,14 @@ Asset Identification
520
567
- GitHub Actions cache entries written and restored across pipeline runs. Used to speed up dependency installation (pip, gem) and incremental builds. Cache-poisoning from forked PRs (DFT-28, SLSA E6: poison the build cache) is mitigated by ref-scoped cache keys: build.yml includes ``${{ github.ref_name }}`` in both ``key`` and ``restore-keys`` (C-033), which isolates PR and release caches per branch so a fork cannot write into the release cache namespace.
521
568
- Datastore
522
569
- High / High / —
570
+
* - A-09: Winget Community Repository (microsoft/winget-pkgs)
571
+
- The Windows Package Manager Community Repository where the dfetch ``DFetch-org.DFetch`` manifest is hosted (https://github.com/microsoft/winget-pkgs). CI submits manifest update PRs via ``vedantmgoyal9/winget-releaser`` using a stored PAT (A-10); PRs are reviewed by ``microsoft/winget-pkgs`` maintainers before merging (C-041). Manifests contain SHA256 hashes of the installer binary; winget verifies the hash before installation. A compromised PAT or a fraudulent PR that passes review could redirect consumers to a malicious installer (DFT-35).
572
+
- ExternalEntity
573
+
- High / High / —
574
+
* - A-10: WINGET_TOKEN PAT
575
+
- Long-lived GitHub Personal Access Token with ``public_repo`` scope, stored as a GitHub Actions environment secret in the ``winget`` environment. Used by ``winget-publish.yml`` to fork ``microsoft/winget-pkgs`` and submit manifest update PRs. Unlike the PyPI OIDC token (A-05) which is short-lived and not stored, this PAT persists indefinitely until rotated. If exfiltrated from the CI environment, an attacker could submit fraudulent manifest PRs from outside the project's pipeline.
576
+
- Data
577
+
- High / High / —
523
578
524
579
525
580
@@ -618,6 +673,21 @@ Dataflows
618
673
- Consumer / End User
619
674
- HTTPS
620
675
676
+
* - DF-27: Winget manifest PR submission
677
+
- A-02: GitHub Actions Infrastructure
678
+
- A-09: Winget Community Repository (microsoft/winget-pkgs)
679
+
- HTTPS
680
+
681
+
* - DF-28: winget install dfetch
682
+
- Consumer / End User
683
+
- A-09: Winget Community Repository (microsoft/winget-pkgs)
684
+
- HTTPS
685
+
686
+
* - DF-29: Consumer downloads MSI via winget
687
+
- A-09: Winget Community Repository (microsoft/winget-pkgs)
- A-09: Winget Community Repository (microsoft/winget-pkgs)
860
+
- | **Sev:** 🟠H
861
+
|**Risk:** 🟠H
862
+
|**STRIDE:** T S
863
+
|**Status:** Mitigate
864
+
- C-041
787
865
788
866
789
867
Controls
@@ -870,3 +948,11 @@ Controls
870
948
- Test result attestation on source archive
871
949
- DFT-31
872
950
- The CI test workflow (``test.yml``) generates an in-toto test result attestation (predicate type ``https://in-toto.io/attestation/test-result/v0.1``) for every release and main-branch commit. The attestation proves the full CI test suite ran against the exact source archive and every check passed, before any binary was produced from that source. Consumers can verify it using ``gh attestation verify dfetch-source.tar.gz`` with ``--predicate-type https://in-toto.io/attestation/test-result/v0.1`` and ``--cert-identity`` pinned to ``test.yml`` at the release tag ref. This provides an additional layer of assurance beyond build provenance: not only was the artifact produced from the official commit, but the test suite demonstrably passed on that exact source before any binary was built. ``.github/workflows/test.yml``
951
+
* - C-041
952
+
- Winget manifest PRs reviewed by community maintainers
953
+
- DFT-35
954
+
- Manifest update PRs submitted to ``microsoft/winget-pkgs`` by ``winget-publish.yml`` go through the standard Winget community review process before merging. ``microsoft/winget-pkgs`` maintainers verify the publisher identity and inspect manifest changes including installer URLs and hashes. This provides a manual review gate between a fraudulent PR submission and consumer exposure. Residual risk: a reviewer who approves without independently verifying the installer URL origin could merge a fraudulent manifest. ``.github/workflows/winget-publish.yml``
955
+
* - C-042
956
+
- WINGET_TOKEN scoped to dedicated Winget environment
957
+
- DFT-34
958
+
- ``WINGET_TOKEN`` is stored in the ``winget`` GitHub Actions deployment environment, limiting its exposure: the PAT is only injected into workflows that explicitly reference that environment. Only ``winget-publish.yml`` references the ``winget`` environment, so the PAT is not available to other workflows. Residual risk: unlike PyPI which uses OIDC (A-05, no stored long-lived token), Winget does not support OIDC trusted publishing; the PAT must be stored and rotated manually (DFT-34). ``.github/workflows/winget-publish.yml``
Copy file name to clipboardExpand all lines: doc/howto/contributing.rst
+5-1Lines changed: 5 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -375,7 +375,11 @@ Releasing
375
375
git push --tags
376
376
377
377
- The ``ci.yml`` job will automatically create a draft release in `GitHub Releases <https://github.com/dfetch-org/dfetch/releases/>`_ with all artifacts.
378
-
- Once the release is published, a new package is automatically pushed to `PyPi <https://pypi.org/project/dfetch/>`_.
378
+
- Once the release is published, a new package is automatically pushed to `PyPi <https://pypi.org/project/dfetch/>`_
379
+
and a manifest PR is automatically submitted to the `Winget Community Repository <https://github.com/microsoft/winget-pkgs>`_.
380
+
The Winget submission requires the ``WINGET_TOKEN`` secret (a GitHub PAT with ``public_repo`` scope) to be configured
381
+
as an environment secret in the ``winget`` environment settings (not at the repository level),
382
+
so it is only accessible to workflows that deploy to that environment.
379
383
380
384
- After release, add new header to ``CHANGELOG.rst``:
0 commit comments