Harden release workflows against tag injection and credential leakage#45
Open
Harden release workflows against tag injection and credential leakage#45
Conversation
Made-with: Cursor
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.
Summary
Security audit of the release-related workflows (
create-major-tag.yml,required-labels.yml) revealed several gaps compared to the recently hardeneddocs-buildanddocs-deployworkflows. This PR brings them up to the same security standard.Changes
create-major-tag.yml— 4 fixes1. Strict semver tag validation (HIGH)
The workflow previously extracted a "major version" from
GITHUB_REFviaawkwith no format validation. Sincerelease: publishedfires for any release (including manually-created ones), an attacker with release-create permission could publish a release with an arbitrary tag name. The extracted value would then be force-pushed as a tag, potentially overwriting legitimate major version tags likev1that downstream consumers depend on.Now rejects any tag not matching
^[0-9]+\.[0-9]+\.[0-9]+$before performing any git operations.2.
persist-credentials: falseon checkout (HIGH)The checkout step was storing
contents: write-scoped credentials in.git/config, making them available to all subsequentrun:steps. If any step were compromised or the workflow extended carelessly, those credentials could push arbitrary code to any branch. Now uses explicit token-based remote URL (set and immediately cleared) only in the step that needs it — matching the pattern used inchangelog/submit.3. Least-privilege permissions (MEDIUM)
contents: writewas set at the workflow level, meaning any future job added to this file would silently inherit write access. Moved tocontents: readat workflow level withcontents: writegranted only to the job that needs it.4. Concurrency guard (MEDIUM)
Two releases published in quick succession (e.g.
2.0.0and2.1.0) would both extractMAJOR_VERSION=2and race ongit push -ffor thev2tag. Added a serializing concurrency group withcancel-in-progress: falseso tag updates are applied in order.required-labels.yml— 2 fixes5. Hardened
pull_request_targetcheckout (MEDIUM)This workflow runs on
pull_request_target(base-repo context with elevated trust). While the default checkout is the base branch (safe today), the lack of explicit constraints made it a footgun — a future edit addingref: ${{ github.event.pull_request.head.sha }}would immediately become a critical code-execution vulnerability from fork PRs.Now uses
sparse-checkoutlimited to the single file it needs (.github/release-drafter.yml) andpersist-credentials: falseto eliminate credential exposure.6. Delimiter-based
GITHUB_OUTPUT(LOW-MEDIUM)The
yqoutput was written toGITHUB_OUTPUTusing inlinekey=valueformat. If the parsed YAML ever produced output containing newlines, it could inject additional output variables. Now uses a random hex delimiter boundary, which is the recommended pattern for multi-line or untrusted output values.Test plan
required-labelscheck should still pass and correctly extract labels fromrelease-drafter.yml99.0.0) →create-major-tagshould createv99foo-bar) is rejected by the validation stepMade with Cursor