From 790b53fc3adb581b1e6dde91b56a5999a9d26410 Mon Sep 17 00:00:00 2001 From: Peter Siska <63866+peschee@users.noreply.github.com> Date: Mon, 15 Jun 2026 15:47:58 +0200 Subject: [PATCH 1/2] fix(security): harden run-steps against script injection; fix release gating Move every github.*, inputs.*, secrets.* and vars.* value that was interpolated directly into a `run:` shell body into a step-level `env:` block, referenced as a quoted shell variable. This closes script-injection holes across all reusable workflows and composite actions and keeps secrets (Nexus, macOS keychain, skopeo, Jira) off command lines. Also: - add-helm-repositories: drop `eval`, build a bash argument array instead - setup-npm-nexus-access: stop printing the generated .npmrc auth token - release announce job: add always() so it is not skipped when optional upstream jobs are skipped; suppress only on failure/cancellation - release run summary: reference the correct lowercase parse outputs - CHANGELOG: document the above under [Unreleased] Verified with actionlint (clean) and an injection scan over all run: bodies. --- .../actions/add-helm-repositories/action.yml | 17 ++++-- .../base/add-helm-repositories/action.yml | 17 ++++-- .../base/check-write-permission/action.yml | 15 +++-- .github/actions/base/parse-version/action.yml | 10 +++- .../base/publish-checkstyle-report/action.yml | 4 +- .../base/publish-spotbugs-report/action.yml | 4 +- .../base/set-maven-project-version/action.yml | 18 ++++-- .../base/setup-npm-nexus-access/action.yml | 4 +- .github/actions/copy-docker-images/action.yml | 11 +++- .github/actions/ensure-branch/action.yml | 14 +++-- .github/actions/install-mvnd/action.yaml | 15 ++++- .github/actions/parse-version/action.yml | 10 +++- .../actions/release-docker-images/action.yml | 21 ++++--- .github/actions/release-jira/action.yml | 5 +- .../send-rocket-chat-message/action.yml | 6 +- .../set-maven-project-version/action.yml | 18 ++++-- .../actions/setup-npm-nexus-access/action.yml | 4 +- .github/workflows/changelog-update.yml | 10 +++- .github/workflows/copy-docker-image.yml | 12 +++- .github/workflows/deploy-dev.yml | 21 +++++-- .github/workflows/git-patch-branch-create.yml | 34 +++++++---- .github/workflows/git-tag.yml | 20 +++++-- .github/workflows/jira-release.yml | 5 +- .github/workflows/main-maven-build.yml | 11 +++- .github/workflows/move-nexus-artifacts.yml | 28 ++++++--- .github/workflows/nexus-artifacts-move.yml | 15 +++-- .github/workflows/nexus-tag-associate.yml | 9 ++- .github/workflows/nexus-tag-create.yml | 9 ++- .github/workflows/nexus-tag-search.yml | 7 ++- .github/workflows/nexus-tag.yml | 58 ++++++++++++------- .github/workflows/release.yml | 37 ++++++++---- .github/workflows/shared-deploy-dev.yml | 21 +++++-- .github/workflows/shared-maven-build.yml | 11 +++- .../workflows/shared-nexus-tag-artifacts.yml | 34 +++++++---- .../workflows/shared-nexus-tag-associate.yml | 15 +++-- .github/workflows/shared-nexus-tag-create.yml | 17 ++++-- .github/workflows/shared-update-archetype.yml | 12 +++- .github/workflows/tag-nexus-artifacts.yml | 56 +++++++++++------- .github/workflows/update-archetype.yml | 12 +++- .github/workflows/version-bump.yml | 48 ++++++++++----- CHANGELOG.md | 9 +++ 41 files changed, 485 insertions(+), 219 deletions(-) diff --git a/.github/actions/add-helm-repositories/action.yml b/.github/actions/add-helm-repositories/action.yml index 3b738c3..0ce3ba6 100644 --- a/.github/actions/add-helm-repositories/action.yml +++ b/.github/actions/add-helm-repositories/action.yml @@ -30,16 +30,21 @@ runs: - name: Add Helm repositories shell: bash if: ${{ inputs.repositories != '[]' }} + env: + HELM_USER: ${{ inputs.username }} + HELM_PASS: ${{ inputs.password }} + FORCE_UPDATE: ${{ inputs.force-update }} + REPOSITORIES: ${{ inputs.repositories }} run: | - echo '${{ inputs.repositories }}' | jq -c '.[]' | while IFS= read -r repo_json; do + echo "$REPOSITORIES" | jq -c '.[]' | while IFS= read -r repo_json; do repo_name=$(echo "$repo_json" | jq -r .name) repo_url=$(echo "$repo_json" | jq -r .url) - # Build the helm command with conditional --force-update flag - helm_cmd="helm repo add \"$repo_name\" \"$repo_url\" --username \"${{ inputs.username }}\" --password \"${{ inputs.password }}\"" - if [[ "${{ inputs.force-update }}" == "true" ]]; then - helm_cmd="$helm_cmd --force-update" + # Build the helm arguments with conditional --force-update flag + helm_args=(repo add "$repo_name" "$repo_url" --username "$HELM_USER" --password "$HELM_PASS") + if [[ "$FORCE_UPDATE" == "true" ]]; then + helm_args+=(--force-update) fi - eval "$helm_cmd" + helm "${helm_args[@]}" done diff --git a/.github/actions/base/add-helm-repositories/action.yml b/.github/actions/base/add-helm-repositories/action.yml index 3b738c3..0ce3ba6 100644 --- a/.github/actions/base/add-helm-repositories/action.yml +++ b/.github/actions/base/add-helm-repositories/action.yml @@ -30,16 +30,21 @@ runs: - name: Add Helm repositories shell: bash if: ${{ inputs.repositories != '[]' }} + env: + HELM_USER: ${{ inputs.username }} + HELM_PASS: ${{ inputs.password }} + FORCE_UPDATE: ${{ inputs.force-update }} + REPOSITORIES: ${{ inputs.repositories }} run: | - echo '${{ inputs.repositories }}' | jq -c '.[]' | while IFS= read -r repo_json; do + echo "$REPOSITORIES" | jq -c '.[]' | while IFS= read -r repo_json; do repo_name=$(echo "$repo_json" | jq -r .name) repo_url=$(echo "$repo_json" | jq -r .url) - # Build the helm command with conditional --force-update flag - helm_cmd="helm repo add \"$repo_name\" \"$repo_url\" --username \"${{ inputs.username }}\" --password \"${{ inputs.password }}\"" - if [[ "${{ inputs.force-update }}" == "true" ]]; then - helm_cmd="$helm_cmd --force-update" + # Build the helm arguments with conditional --force-update flag + helm_args=(repo add "$repo_name" "$repo_url" --username "$HELM_USER" --password "$HELM_PASS") + if [[ "$FORCE_UPDATE" == "true" ]]; then + helm_args+=(--force-update) fi - eval "$helm_cmd" + helm "${helm_args[@]}" done diff --git a/.github/actions/base/check-write-permission/action.yml b/.github/actions/base/check-write-permission/action.yml index 9fa120b..ad19ea1 100644 --- a/.github/actions/base/check-write-permission/action.yml +++ b/.github/actions/base/check-write-permission/action.yml @@ -12,17 +12,22 @@ runs: - name: Check checks:write permission id: check shell: bash + env: + REPOSITORY: ${{ github.repository }} + EVENT_NAME: ${{ github.event_name }} + ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ github.token }} run: | - echo "::debug::Checking checks:write permission for repo: ${{ github.repository }}" - echo "::debug::Event name: ${{ github.event_name }}" - echo "::debug::Actor: ${{ github.actor }}" + echo "::debug::Checking checks:write permission for repo: $REPOSITORY" + echo "::debug::Event name: $EVENT_NAME" + echo "::debug::Actor: $ACTOR" # Test if we have checks:write permission by attempting to create a check run. # 422 = permission granted (invalid request body), 403 = no permission RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ - -H "Authorization: Bearer ${{ github.token }}" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/${{ github.repository }}/check-runs" \ + "https://api.github.com/repos/$REPOSITORY/check-runs" \ -d '{}') HTTP_CODE=$(echo "$RESPONSE" | tail -n1) diff --git a/.github/actions/base/parse-version/action.yml b/.github/actions/base/parse-version/action.yml index cfb789f..3ef8bef 100644 --- a/.github/actions/base/parse-version/action.yml +++ b/.github/actions/base/parse-version/action.yml @@ -35,10 +35,12 @@ runs: - name: Parse version id: version shell: bash + env: + VERSION: ${{ inputs.version }} run: | - IFS=- read SEM_VER BUILD_DATE RUN_ID COMMIT_SHA <<< "${{ inputs.version }}" + IFS=- read SEM_VER BUILD_DATE RUN_ID COMMIT_SHA <<< "$VERSION" - cat >> $GITHUB_OUTPUT <> $GITHUB_OUTPUT <> $GITHUB_OUTPUT <> '${{ inputs.auth-file }}' + cat << EOF >> "$AUTH_FILE" //$NEXUS_URL/repository/$NPM_REPOSITORY/:_authToken=$NPM_TOKEN EOF - cat '${{ inputs.auth-file }}' env: NPM_TOKEN: ${{ inputs.token }} NPM_REPOSITORY: ${{ inputs.repository }} NEXUS_URL: ${{ inputs.nexus-url }} + AUTH_FILE: ${{ inputs.auth-file }} diff --git a/.github/actions/copy-docker-images/action.yml b/.github/actions/copy-docker-images/action.yml index d6aef35..e29cfac 100644 --- a/.github/actions/copy-docker-images/action.yml +++ b/.github/actions/copy-docker-images/action.yml @@ -20,9 +20,14 @@ runs: steps: - name: Copy multi-arch docker image shell: bash + env: + SOURCE_CREDENTIALS: ${{ inputs.source_credentials }} + DESTINATION_CREDENTIALS: ${{ inputs.destination_credentials }} + SOURCE_IMAGE: ${{ inputs.source_image }} + DESTINATION_IMAGE: ${{ inputs.destination_image }} run: | docker run --rm quay.io/skopeo/stable:v1 copy \ --all \ - --src-creds ${{ inputs.source_credentials }} \ - --dest-creds ${{ inputs.destination_credentials }} \ - docker://${{ inputs.source_image }} docker://${{ inputs.destination_image }} + --src-creds "$SOURCE_CREDENTIALS" \ + --dest-creds "$DESTINATION_CREDENTIALS" \ + "docker://$SOURCE_IMAGE" "docker://$DESTINATION_IMAGE" diff --git a/.github/actions/ensure-branch/action.yml b/.github/actions/ensure-branch/action.yml index 0294c77..9d38aaf 100644 --- a/.github/actions/ensure-branch/action.yml +++ b/.github/actions/ensure-branch/action.yml @@ -17,23 +17,29 @@ runs: - name: Check if branch exists id: branch_check + env: + BRANCH: ${{ inputs.branch }} run: | - if [[ -n $(git ls-remote --heads origin ${{ inputs.branch }}) ]]; then + if [[ -n $(git ls-remote --heads origin "$BRANCH") ]]; then echo "exists=true" >> $GITHUB_OUTPUT else - echo "exists=false" >> $GITHUB_OUTPUT + echo "exists=false" >> $GITHUB_OUTPUT fi shell: bash - name: Branch exists if: steps.branch_check.outputs.exists == 'true' shell: bash + env: + BRANCH: ${{ inputs.branch }} run: | - echo "Branch '${{ inputs.branch }}' exists!" + echo "Branch '$BRANCH' exists!" - name: Branch does NOT exists if: steps.branch_check.outputs.exists == 'false' shell: bash + env: + BRANCH: ${{ inputs.branch }} run: | - echo "Branch '${{ inputs.branch }}' does NOT exist. Aborting..." + echo "Branch '$BRANCH' does NOT exist. Aborting..." exit 1 diff --git a/.github/actions/install-mvnd/action.yaml b/.github/actions/install-mvnd/action.yaml index 7d09ff2..582a551 100644 --- a/.github/actions/install-mvnd/action.yaml +++ b/.github/actions/install-mvnd/action.yaml @@ -39,10 +39,16 @@ outputs: runs: using: 'composite' steps: - - run: curl -fsSL -o mvnd.zip https://downloads.apache.org/maven/mvnd/${{ inputs.version }}/maven-mvnd-${{ inputs.version }}-${{ inputs.distribution }}.zip + - env: + VERSION: ${{ inputs.version }} + DISTRIBUTION: ${{ inputs.distribution }} + run: curl -fsSL -o mvnd.zip "https://downloads.apache.org/maven/mvnd/$VERSION/maven-mvnd-$VERSION-$DISTRIBUTION.zip" if: inputs.dry-run == 'false' shell: bash - - run: curl -fsSL -o mvnd.zip.sha256 https://downloads.apache.org/maven/mvnd/${{ inputs.version }}/maven-mvnd-${{ inputs.version }}-${{ inputs.distribution }}.zip.sha256 + - env: + VERSION: ${{ inputs.version }} + DISTRIBUTION: ${{ inputs.distribution }} + run: curl -fsSL -o mvnd.zip.sha256 "https://downloads.apache.org/maven/mvnd/$VERSION/maven-mvnd-$VERSION-$DISTRIBUTION.zip.sha256" if: inputs.dry-run == 'false' shell: bash - id: integrity-check @@ -53,7 +59,10 @@ runs: if: inputs.dry-run == 'false' shell: bash - id: mvnd-location - run: echo "mvnd-dir=/tmp/maven-mvnd-${{ inputs.version }}-${{ inputs.distribution }}/bin" >> $GITHUB_OUTPUT + env: + VERSION: ${{ inputs.version }} + DISTRIBUTION: ${{ inputs.distribution }} + run: echo "mvnd-dir=/tmp/maven-mvnd-$VERSION-$DISTRIBUTION/bin" >> $GITHUB_OUTPUT shell: bash # - id: mvnd-opts # run: echo "MVND_OPTS=-P apache-snapshots -V -e -ntp -Dmvnd.threads=2 -Daether.connector.http.connectionMaxTtl=120 -Daether.connector.requestTimeout=300000 -Daether.dependencyCollector.impl=bf -Dmaven.artifact.threads=25 -Dci.env.name=github.com -Dsurefire.rerunFailingTestsCount=2 -Dfailsafe.rerunFailingTestsCount=2" >> $GITHUB_ENV diff --git a/.github/actions/parse-version/action.yml b/.github/actions/parse-version/action.yml index cfb789f..3ef8bef 100644 --- a/.github/actions/parse-version/action.yml +++ b/.github/actions/parse-version/action.yml @@ -35,10 +35,12 @@ runs: - name: Parse version id: version shell: bash + env: + VERSION: ${{ inputs.version }} run: | - IFS=- read SEM_VER BUILD_DATE RUN_ID COMMIT_SHA <<< "${{ inputs.version }}" + IFS=- read SEM_VER BUILD_DATE RUN_ID COMMIT_SHA <<< "$VERSION" - cat >> $GITHUB_OUTPUT <> $GITHUB_OUTPUT <> $GITHUB_OUTPUT <> '${{ inputs.auth-file }}' + cat << EOF >> "$AUTH_FILE" //$NEXUS_URL/repository/$NPM_REPOSITORY/:_authToken=$NPM_TOKEN EOF - cat '${{ inputs.auth-file }}' env: NPM_TOKEN: ${{ inputs.token }} NPM_REPOSITORY: ${{ inputs.repository }} NEXUS_URL: ${{ inputs.nexus-url }} + AUTH_FILE: ${{ inputs.auth-file }} diff --git a/.github/workflows/changelog-update.yml b/.github/workflows/changelog-update.yml index 68af7fd..dc20419 100644 --- a/.github/workflows/changelog-update.yml +++ b/.github/workflows/changelog-update.yml @@ -45,9 +45,11 @@ jobs: ref: ${{ inputs.branch_name }} - name: Configure Git user + env: + ACTOR: ${{ github.actor }} run: | - git config user.name "${{ github.actor }}" - git config user.email "${{ github.actor }}@users.noreply.github.com" + git config user.name "$ACTOR" + git config user.email "$ACTOR@users.noreply.github.com" - name: Update Changelog uses: uniport/workflows/.github/actions/update-changelog@main @@ -62,5 +64,7 @@ jobs: --message "Update changelog with new version and prepare next unreleased version" - name: Push tag + env: + BRANCH_NAME: ${{ inputs.branch_name }} run: | - git push origin ${{ inputs.branch_name }} + git push origin "$BRANCH_NAME" diff --git a/.github/workflows/copy-docker-image.yml b/.github/workflows/copy-docker-image.yml index 894d87b..152a60e 100644 --- a/.github/workflows/copy-docker-image.yml +++ b/.github/workflows/copy-docker-image.yml @@ -46,10 +46,16 @@ jobs: - name: Summary shell: bash + env: + SOURCE_REGISTRY: ${{ inputs.source_registry }} + SOURCE_IMAGE: ${{ inputs.source_image }} + DESTINATION_REGISTRY: ${{ inputs.destination_registry }} + DESTINATION_IMAGE: ${{ inputs.destination_image }} + VERSION: ${{ inputs.version }} run: | - cat >> $GITHUB_STEP_SUMMARY <> "$GITHUB_STEP_SUMMARY" <> $GITHUB_STEP_SUMMARY - echo "- **version**: \`${{ inputs.version }}\`" >> $GITHUB_STEP_SUMMARY - echo "- **branch**: \`${{ inputs.branch }}\`" >> $GITHUB_STEP_SUMMARY + echo "- **version**: \`$VERSION\`" >> $GITHUB_STEP_SUMMARY + echo "- **branch**: \`$BRANCH\`" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/git-patch-branch-create.yml b/.github/workflows/git-patch-branch-create.yml index ed5633d..d9bc0c3 100644 --- a/.github/workflows/git-patch-branch-create.yml +++ b/.github/workflows/git-patch-branch-create.yml @@ -49,8 +49,11 @@ jobs: - name: Setup id: setup shell: bash + env: + MAJOR: ${{ inputs.major }} + MINOR: ${{ inputs.minor }} run: | - PATCH_BRANCH_NAME="${{ inputs.major }}.${{ inputs.minor }}.x" + PATCH_BRANCH_NAME="$MAJOR.$MINOR.x" cat >> $GITHUB_OUTPUT <> $GITHUB_STEP_SUMMARY <> $GITHUB_STEP_SUMMARY <> $GITHUB_OUTPUT diff --git a/.github/workflows/main-maven-build.yml b/.github/workflows/main-maven-build.yml index 9c8439e..62871ae 100644 --- a/.github/workflows/main-maven-build.yml +++ b/.github/workflows/main-maven-build.yml @@ -88,8 +88,10 @@ jobs: - name: Unlock keychain (macOS runners) if: ${{ contains(runner.name, 'macduff') }} + env: + MACDUFF_KEYCHAIN_PASSWORD: ${{ secrets.MACDUFF_KEYCHAIN_PASSWORD }} run: | - security unlock-keychain -p "${{ secrets.MACDUFF_KEYCHAIN_PASSWORD }}" + security unlock-keychain -p "$MACDUFF_KEYCHAIN_PASSWORD" - name: Setup Helm repositories uses: uniport/workflows/.github/actions/base/add-helm-repositories@main @@ -164,11 +166,14 @@ jobs: lcov: frontend/src/main/web/coverage/lcov.info - name: Build summary + env: + RC_VERSION: ${{ steps.vars.outputs.RC_VERSION }} + GROUP_ID: ${{ steps.vars.outputs.GROUP_ID }} run: | cat >> $GITHUB_STEP_SUMMARY <> $GITHUB_STEP_SUMMARY <> $GITHUB_STEP_SUMMARY <> $GITHUB_OUTPUT <> $GITHUB_STEP_SUMMARY <> $GITHUB_STEP_SUMMARY <> $GITHUB_STEP_SUMMARY <> "$GITHUB_STEP_SUMMARY" <> $GITHUB_STEP_SUMMARY - echo "- **version**: \`${{ inputs.version }}\`" >> $GITHUB_STEP_SUMMARY - echo "- **branch**: \`${{ inputs.branch }}\`" >> $GITHUB_STEP_SUMMARY + echo "- **version**: \`$VERSION\`" >> $GITHUB_STEP_SUMMARY + echo "- **branch**: \`$BRANCH\`" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/shared-maven-build.yml b/.github/workflows/shared-maven-build.yml index bc6adc1..1111527 100644 --- a/.github/workflows/shared-maven-build.yml +++ b/.github/workflows/shared-maven-build.yml @@ -111,8 +111,10 @@ jobs: - name: Unlock macOS Keychain (for code signing) if: ${{ contains(runner.name, 'macduff') }} + env: + MACDUFF_KEYCHAIN_PASSWORD: ${{ secrets.MACDUFF_KEYCHAIN_PASSWORD }} run: | - security unlock-keychain -p "${{ secrets.MACDUFF_KEYCHAIN_PASSWORD }}" + security unlock-keychain -p "$MACDUFF_KEYCHAIN_PASSWORD" security list-keychains - name: Login to Uniport Docker Registry @@ -200,11 +202,14 @@ jobs: lcov: frontend/src/main/web/coverage/lcov.info - name: Generate Build Summary for GitHub UI + env: + RC_VERSION: ${{ steps.vars.outputs.RC_VERSION }} + GROUP_ID: ${{ steps.vars.outputs.GROUP_ID }} run: | cat >> $GITHUB_STEP_SUMMARY <> $GITHUB_STEP_SUMMARY <> $GITHUB_STEP_SUMMARY <> $GITHUB_OUTPUT <> $GITHUB_STEP_SUMMARY <> $GITHUB_OUTPUT <> $GITHUB_STEP_SUMMARY <> $GITHUB_STEP_SUMMARY < Date: Mon, 15 Jun 2026 17:22:32 +0200 Subject: [PATCH 2/2] fix: skip release announcement when cancelled --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b781c1..d711510 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -235,11 +235,12 @@ jobs: announce: name: Announce new release - # Policy: Only announce if all required release steps completed without failure. + # Policy: Only announce if the workflow was not cancelled and all required release + # steps completed without failure. # `always()` is required because several needed jobs (promote, git-tag, jira-release, # archetype) are conditionally skipped on a normal release; without it this job would # itself be skipped whenever any upstream job is skipped. - if: ${{ always() && inputs.send_announcement && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} + if: ${{ always() && !cancelled() && inputs.send_announcement && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} needs: - setup - promote