diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c45397a..8fd13d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,6 +64,14 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + # CodeRabbit catch (CWE-250, task_1788457898992): the default + # persisted credential would stay live through npm ci/build/test + # below, so a compromised dependency's lifecycle script could + # misuse it to push. Each git network call downstream instead + # authenticates individually via an inline `-c http.extraheader` + # (never written to .git/config — see the second CodeRabbit catch, + # CWE-522, at "Determine mode" below). + persist-credentials: false - name: Setup Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 @@ -89,6 +97,24 @@ jobs: # steady state, and never retries the artifacts that actually failed). # This makes a rerun after a partial failure resume exactly the # missing steps instead of silently skipping them. + # + # Git auth note (CodeRabbit, CWE-250 then CWE-522, task_1788457898992): + # persist-credentials is false on checkout above, and this step's own + # `git fetch --tags` is the first git network call after the untrusted + # npm lifecycle. A first pass re-authenticated via `git remote + # set-url`, but that WRITES the token into .git/config where any later + # process in the job could read it back off disk. Using a `-c + # http.extraheader` on the git invocation itself instead scopes the + # credential to that one command's process environment -- nothing + # persists to a file. Every git network call in this workflow uses + # this same inline pattern; none set the remote URL. + # + # Also (CodeRabbit, CWE-319): every such call targets an explicit + # https://github.com/... URL rather than the `origin` remote name -- + # if something upstream of this point ever rewrote origin's URL to an + # http:// scheme, using the remote name would silently send this + # Basic-auth header in cleartext. An explicit https:// URL can't be + # redirected that way. - name: Determine mode (publish vs prepare) id: mode env: @@ -96,7 +122,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail - git fetch --tags + AUTH_HEADER="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')" + echo "::add-mask::$AUTH_HEADER" + git -c http.extraheader="$AUTH_HEADER" fetch "https://github.com/${{ github.repository }}.git" --tags VERSION=$(jq -r '.version' package.json) PKG_NAME=$(jq -r '.name' package.json) @@ -125,11 +153,15 @@ jobs: - name: "Publish: tag" if: steps.mode.outputs.mode == 'publish' && steps.mode.outputs.tag_exists == 'false' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail VERSION="${{ steps.mode.outputs.version }}" + AUTH_HEADER="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 | tr -d '\n')" + echo "::add-mask::$AUTH_HEADER" git tag "v${VERSION}" - git push origin "v${VERSION}" + git -c http.extraheader="$AUTH_HEADER" push "https://github.com/${{ github.repository }}.git" "v${VERSION}" - name: "Publish: npm publish" if: steps.mode.outputs.mode == 'publish' && steps.mode.outputs.npm_published == 'false' @@ -173,8 +205,9 @@ jobs: # subject to that restriction (verified live the same day: an # App-token `gh pr create` against this exact repo succeeded, PR # authored by app/wyre-agent-fleet). Used only for the `gh pr` calls - # below — `git push` keeps using actions/checkout's default credential, - # which was never the blocked operation. + # below — `git push` authenticates separately with the default + # GITHUB_TOKEN via an inline http.extraheader (see PUSH_TOKEN below), + # since push was never the blocked operation. - name: "Prepare: mint App token for PR creation" if: steps.mode.outputs.mode == 'prepare' && steps.prepare.outputs.release_needed == 'true' id: app-token @@ -205,6 +238,7 @@ jobs: if: steps.mode.outputs.mode == 'prepare' && steps.prepare.outputs.release_needed == 'true' env: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + PUSH_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION: ${{ steps.prepare.outputs.version }} run: | set -euo pipefail @@ -220,7 +254,9 @@ jobs: merge method) triggers this workflow's PUBLISH mode, which tags, publishes to npm, and creates the GitHub release — nothing publishes until this merges." - git push --force origin release/next + AUTH_HEADER="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" + echo "::add-mask::$AUTH_HEADER" + git -c http.extraheader="$AUTH_HEADER" push --force "https://github.com/${{ github.repository }}.git" release/next if gh pr view release/next --json state --jq .state 2>/dev/null | grep -q OPEN; then gh pr edit release/next --title "chore(release): ${VERSION}"