Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 89 additions & 4 deletions .github/workflows/notify-landing-yank.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
name: Notify landing page (release lifecycle)
name: Release lifecycle sync

# Dispatches to bomly-landing-page whenever a release is published (so docs and
# changelog sync), or deleted/unpublished (so the version is yanked from the
# version selector and changelog). Docs are synced at publish time rather than
# at draft creation so the changelog script can find the release via the API.
# version selector and changelog). Yanked releases also attempt to remove the
# corresponding WinGet version manifest when it exists. Docs are synced at
# publish time rather than at draft creation so the changelog script can find
# the release via the API.

on:
release:
Expand All @@ -13,7 +15,7 @@ permissions:
contents: read

jobs:
notify:
landing-page:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
Expand Down Expand Up @@ -55,3 +57,86 @@ jobs:
--input - <<EOF
{"event_type":"bomly-release-yanked","client_payload":{"version":"${TAG}"}}
EOF

winget-yank:
if: github.event.action == 'deleted' || github.event.action == 'unpublished'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out WinGet package manifests
uses: actions/checkout@v5
with:
repository: microsoft/winget-pkgs
ref: master
path: winget-pkgs
sparse-checkout: manifests/b/Bomly/BomlyCLI
sparse-checkout-cone-mode: false
fetch-depth: 1
Comment on lines +66 to +74

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add persist-credentials: false and consider pinning the action to a SHA.

Static analysis correctly flags two security concerns:

  1. Missing persist-credentials: false — the default true can leak credentials via workflow artifacts.
  2. Action referenced by tag (@v5) rather than hash — pinning to a SHA (e.g., @<commit-sha>) hardens against supply chain attacks.
🛡️ Proposed fix
       - name: Check out WinGet package manifests
-        uses: actions/checkout@v5
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v5.0.2
         with:
           repository: microsoft/winget-pkgs
           ref: master
           path: winget-pkgs
           sparse-checkout: manifests/b/Bomly/BomlyCLI
           sparse-checkout-cone-mode: false
           fetch-depth: 1
+          persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 66-74: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 67-67: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/notify-landing-yank.yml around lines 66 - 74, In the
"Check out WinGet package manifests" step that uses actions/checkout, add the
persist-credentials field set to false to prevent credential leakage through
workflow artifacts. Additionally, replace the tag reference `@v5` with a pinned
commit SHA (format: actions/checkout@<commit-sha>) to mitigate supply chain
attack risks from using mutable tag references.

Source: Linters/SAST tools


- name: Remove yanked WinGet version manifest
id: winget-yank
working-directory: winget-pkgs
env:
TAG: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail

VERSION="${TAG#v}"
MANIFEST_DIR="manifests/b/Bomly/BomlyCLI/${VERSION}"
BRANCH_VERSION="${VERSION//[^0-9A-Za-z._-]/-}"
BRANCH="yank-bomly-${BRANCH_VERSION}"

{
echo "version=${VERSION}"
echo "manifest_dir=${MANIFEST_DIR}"
echo "branch=${BRANCH}"
} >> "${GITHUB_OUTPUT}"

if [[ ! -d "${MANIFEST_DIR}" ]]; then
echo "No WinGet manifest exists for Bomly.BomlyCLI ${VERSION}; skipping removal."
echo "removed=false" >> "${GITHUB_OUTPUT}"
exit 0
fi

git config user.name "bomly-release-bot"
git config user.email "release-bot@bomly.dev"
git checkout -b "${BRANCH}"
rm -rf "${MANIFEST_DIR}"
git add "manifests/b/Bomly/BomlyCLI"
git commit -m "Remove Bomly CLI ${VERSION}"

echo "removed=true" >> "${GITHUB_OUTPUT}"

- name: Push WinGet removal branch
if: steps.winget-yank.outputs.removed == 'true'
working-directory: winget-pkgs
env:
WINGET_GITHUB_TOKEN: ${{ secrets.WINGET_GITHUB_TOKEN }}
BRANCH: ${{ steps.winget-yank.outputs.branch }}
run: |
set -euo pipefail
git remote add bomly "https://x-access-token:${WINGET_GITHUB_TOKEN}@github.com/bomly-dev/winget-pkgs.git"
git fetch bomly "${BRANCH}:refs/remotes/bomly/${BRANCH}" || true
git push --force-with-lease bomly "HEAD:${BRANCH}"

- name: Open WinGet removal PR
if: steps.winget-yank.outputs.removed == 'true'
working-directory: winget-pkgs
env:
GH_TOKEN: ${{ secrets.WINGET_GITHUB_TOKEN }}
VERSION: ${{ steps.winget-yank.outputs.version }}
BRANCH: ${{ steps.winget-yank.outputs.branch }}
run: |
set -euo pipefail

if gh pr view --repo microsoft/winget-pkgs --head "bomly-dev:${BRANCH}" >/dev/null 2>&1; then
echo "WinGet removal PR already exists for Bomly.BomlyCLI ${VERSION}."
exit 0
fi

gh pr create \
--repo microsoft/winget-pkgs \
--base master \
--head "bomly-dev:${BRANCH}" \
--title "Remove Bomly.BomlyCLI version ${VERSION}" \
--body "Remove the Bomly.BomlyCLI WinGet manifest for yanked release ${VERSION}."
10 changes: 9 additions & 1 deletion docs/development/CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,18 @@ Release packaging is driven by `.goreleaser.yaml`. The release workflow uses GoR
6. GoReleaser generates `SHA256SUMS` and Linux packages.
7. GoReleaser publishes the GitHub Release, using the configured GoReleaser header plus GitHub-native generated release notes, and uploads archives, packages, and checksums.
8. GoReleaser opens or updates package-manager manifest PRs for Homebrew, Scoop, and WinGet.
9. After the release is published, the `Notify landing page (release lifecycle)` workflow dispatches the landing-page docs and changelog sync with the published timestamp.
9. After the release is published, the `Release lifecycle sync` workflow dispatches the landing-page docs and changelog sync with the published timestamp.

The manual approval point for a release is the `Auto Version` workflow that creates the release tag. The GitHub Release is intentionally published automatically after validation so package-manager manifest PRs can reference public release assets and checksums.

## Yanking Releases

Deleting or unpublishing a GitHub Release automatically starts the yanking path in the `Release lifecycle sync` workflow. The workflow dispatches a landing-page removal event so the yanked version is removed from the version selector and changelog.

Package-manager cleanup depends on where the manifest lives. Homebrew and Scoop are maintained as current manifests in `bomly-dev/homebrew-tap` and `bomly-dev/scoop-bucket`, so closing stale package-manager PRs and publishing a replacement release is normally sufficient. WinGet stores versioned manifests in `microsoft/winget-pkgs`, so the workflow also checks for `Bomly.BomlyCLI` under `manifests/b/Bomly/BomlyCLI/<version>`. When that version directory exists, it pushes a deletion branch to `bomly-dev/winget-pkgs` and opens a PR against `microsoft/winget-pkgs:master`.

GoReleaser continues to own normal package-manager publication for new releases. WinGet yanking is handled by the release lifecycle workflow because it is a deletion PR, not a release publication step.

Version bump rules are chosen explicitly when running `Auto Version`:

| Selected bump | Result |
Expand Down
4 changes: 3 additions & 1 deletion docs/development/RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@ scoop install bomly

- Merge package-manager PRs after their generated manifests pass review.
- Confirm the landing-page docs sync PR opened.
- If a release must be pulled, mark the GitHub release as a prerelease or delete it, close package-manager PRs that reference the bad tag, and tag a replacement patch release when appropriate.
- If a release must be pulled, delete or unpublish the GitHub Release to trigger the automatic yanking workflow.
- Close Homebrew and Scoop package-manager PRs that reference the bad tag, then tag a replacement patch release when appropriate.
- For WinGet, confirm the release lifecycle workflow either skipped cleanup because no version manifest existed or opened a removal PR against `microsoft/winget-pkgs`.