From 56cd2482cd91eac0e0d1ed8031d6602ce43aa3aa Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 13 Nov 2026 10:28:33 -0500 Subject: [PATCH 1/2] Pass ref and tag names to the shell through the environment publish.yml's gh release steps, fusion-docs.yml's asset upload and code-check.yml's base-commit logic interpolated tag and branch names straight into their scripts. Ref names may contain $(), backticks and ';', so a crafted tag or branch would run as shell code -- in publish's case, in the job that holds id-token: write. Pass them in environment variables and quote them, as code-check already does for HEAD_REF. Found by review bots on sqlalchemy-singlestoredb #23, which had copied the publish steps from here. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/code-check.yml | 8 +++++--- .github/workflows/fusion-docs.yml | 5 ++++- .github/workflows/publish.yml | 9 +++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/code-check.yml b/.github/workflows/code-check.yml index e1f4b9455..93f63064e 100644 --- a/.github/workflows/code-check.yml +++ b/.github/workflows/code-check.yml @@ -71,16 +71,18 @@ jobs: HEAD_REF: ${{ github.head_ref }} HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} REPO: ${{ github.repository }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + REF_NAME: ${{ github.ref_name }} run: | # Define directories to monitor (space-separated) MONITORED_DIRS="singlestoredb/management singlestoredb/fusion" # Determine the base commit to compare against - if [ "${{ github.event_name }}" == "pull_request" ]; then + if [ "$EVENT_NAME" == "pull_request" ]; then # For PRs, compare against the target branch (usually master/main) - BASE_COMMIT="origin/${{ github.event.pull_request.base.ref }}" + BASE_COMMIT="origin/$BASE_REF" echo "Pull Request: Comparing against $BASE_COMMIT" - elif [ "${{ github.ref_name }}" == "main" ] || [ "${{ github.ref_name }}" == "master" ]; then + elif [ "$REF_NAME" == "main" ] || [ "$REF_NAME" == "master" ]; then # For pushes to main/master, compare against the push's own lower # bound. The ruleset only allows squash merges, so that is normally # HEAD~1 anyway; this keeps a multi-commit push checked whole if diff --git a/.github/workflows/fusion-docs.yml b/.github/workflows/fusion-docs.yml index c7b8ac5c8..e73cceeeb 100644 --- a/.github/workflows/fusion-docs.yml +++ b/.github/workflows/fusion-docs.yml @@ -35,8 +35,11 @@ jobs: python resources/gen_fusion_handlers_doc.py --dir fusion-docs zip -r fusion-docs.zip fusion-docs + # The tag goes through the environment, not into the script, so the + # shell never runs characters in it; see publish.yml. - name: Upload release asset run: | - gh release upload ${{ github.event.release.tag_name }} fusion-docs.zip + gh release upload "$TAG" fusion-docs.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.event.release.tag_name }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 21d3d06ee..fe5deac8c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -257,19 +257,24 @@ jobs: with: subject-path: dist/* + # The tag reaches the shell through the environment rather than being + # interpolated into the script: tag names may contain $(), backticks + # and ';', which the shell would otherwise run. - name: Create GitHub Release if: ${{ github.event_name == 'push' }} run: | - gh release create ${{ github.ref_name }} dist/* --prerelease --title "${{ github.ref_name }}" + gh release create "$TAG" dist/* --prerelease --title "$TAG" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.ref_name }} - name: Upload release assets if: ${{ github.event_name == 'release' }} run: | - gh release upload ${{ github.event.release.tag_name }} dist/* --clobber + gh release upload "$TAG" dist/* --clobber env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.event.release.tag_name }} - name: Publish to PyPI if: ${{ github.event_name == 'release' || github.event.inputs.publish_pypi == 'true' }} From 5e39a5b91b457a6524eda28003714876d575fe25 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 13 Nov 2026 10:32:42 -0500 Subject: [PATCH 2/2] Pass the cluster secrets to the shell through the environment The create_test_cluster.py calls in publish.yml and smoke-test.yml, and publish.yml's shutdown curl, interpolated CLUSTER_PASSWORD and CLUSTER_API_KEY into their commands, so a quote, $ or backtick in either would break or alter the command. Pass them in environment variables, as the drop steps and smoke-test.yml's shutdown already do. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 11 +++++++++-- .github/workflows/smoke-test.yml | 8 +++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fe5deac8c..cd0d1742a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -78,10 +78,16 @@ jobs: # to it, so the script resets it to CLUSTER_PASSWORD over SQL once the # cluster is up. A generated password could not reach the other jobs # anyway: the runner refuses to write a masked value as a job output. + # + # The secrets reach the script through the environment rather than + # being interpolated into the command, so the shell never sees their + # characters. run: | - python resources/create_test_cluster.py --password="${{ secrets.CLUSTER_PASSWORD }}" --token="${{ secrets.CLUSTER_API_KEY }}" --project="Standard Project" --init-sql singlestoredb/tests/test.sql --output=github --expires=2h "python - $GITHUB_WORKFLOW - $GITHUB_RUN_NUMBER" + python resources/create_test_cluster.py --password="$CLUSTER_PASSWORD" --token="$CLUSTER_API_KEY" --project="Standard Project" --init-sql singlestoredb/tests/test.sql --output=github --expires=2h "python - $GITHUB_WORKFLOW - $GITHUB_RUN_NUMBER" env: PYTHONPATH: ${{ github.workspace }} + CLUSTER_PASSWORD: ${{ secrets.CLUSTER_PASSWORD }} + CLUSTER_API_KEY: ${{ secrets.CLUSTER_API_KEY }} outputs: cluster-id: ${{ steps.initialize-database.outputs.cluster-id }} @@ -330,6 +336,7 @@ jobs: echo "::error::No cluster ID from setup-database; the cluster (if any) must be terminated by hand" exit 1 fi - curl --fail-with-body -H "Accept: application/json" -H "Authorization: Bearer ${{ secrets.CLUSTER_API_KEY }}" -X DELETE "https://api.singlestore.com/v2/clusters/$CLUSTER_ID?force=true" + curl --fail-with-body -H "Accept: application/json" -H "Authorization: Bearer $CLUSTER_API_KEY" -X DELETE "https://api.singlestore.com/v2/clusters/$CLUSTER_ID?force=true" env: CLUSTER_ID: ${{ needs.setup-database.outputs.cluster-id }} + CLUSTER_API_KEY: ${{ secrets.CLUSTER_API_KEY }} diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index 8a8ec0f5e..eab922a87 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -60,10 +60,16 @@ jobs: # to it, so the script resets it to CLUSTER_PASSWORD over SQL once the # cluster is up. A generated password could not reach the other jobs # anyway: the runner refuses to write a masked value as a job output. + # + # The secrets reach the script through the environment rather than + # being interpolated into the command, so the shell never sees their + # characters. run: | - python resources/create_test_cluster.py --password="${{ secrets.CLUSTER_PASSWORD }}" --token="${{ secrets.CLUSTER_API_KEY }}" --project="Standard Project" --init-sql singlestoredb/tests/test.sql --output=github --expires=2h "python - $GITHUB_WORKFLOW - $GITHUB_RUN_NUMBER" + python resources/create_test_cluster.py --password="$CLUSTER_PASSWORD" --token="$CLUSTER_API_KEY" --project="Standard Project" --init-sql singlestoredb/tests/test.sql --output=github --expires=2h "python - $GITHUB_WORKFLOW - $GITHUB_RUN_NUMBER" env: PYTHONPATH: ${{ github.workspace }} + CLUSTER_PASSWORD: ${{ secrets.CLUSTER_PASSWORD }} + CLUSTER_API_KEY: ${{ secrets.CLUSTER_API_KEY }} outputs: cluster-id: ${{ steps.initialize-database.outputs.cluster-id }}