From bfa5285b8d164a0999f4f0073e5d37823c4c0301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Str=C3=BCbe?= Date: Fri, 16 Oct 2026 15:38:14 +0200 Subject: [PATCH 1/4] ci: harden GitHub Actions workflows (FB-4904) - Upgrade and SHA-pin actions (pinact, 7-day min age); repin action-python-release to current main. - Default every workflow to `permissions: {}` and grant per-job permissions, including on reusable-workflow caller jobs. - Set `persist-credentials: false` on checkouts that don't push; the release job keeps `true` for the version bump and tag push. - Drop the pip cache from the docs deploy job (cache poisoning) and move template expressions in `run:` into `env:`; quote shell variables. - Add actionlint + zizmor CI workflow and an actionlint config that ignores the intentional `cache-mode` job key. Co-Authored-By: Claude Opus 5.5 --- .github/actionlint.yaml | 5 ++ .github/workflows/build-documentation.yml | 34 ++++++------ .github/workflows/check-pr-formatting.yml | 3 +- .github/workflows/code-check.yml | 7 ++- .github/workflows/integration-tests-core.yml | 8 +-- .github/workflows/integration-tests-v2.yml | 8 +-- .github/workflows/integration-tests.yml | 7 ++- .github/workflows/lint-and-scan-actions.yml | 57 ++++++++++++++++++++ .github/workflows/nightly-v2.yml | 17 ++++-- .github/workflows/pull-request.yml | 7 ++- .github/workflows/release.yml | 7 ++- .github/workflows/security-scan.yml | 11 ++-- .github/workflows/unit-tests.yml | 19 ++++--- 13 files changed, 130 insertions(+), 60 deletions(-) create mode 100644 .github/actionlint.yaml create mode 100644 .github/workflows/lint-and-scan-actions.yml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 00000000000..7c00cd72e56 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,5 @@ +# `cache-mode` is a GitHub job key that actionlint doesn't know yet (see #541). +paths: + .github/workflows/code-check.yml: + ignore: + - 'unexpected key "cache-mode" for "job" section' diff --git a/.github/workflows/build-documentation.yml b/.github/workflows/build-documentation.yml index bc7db2e9bcd..c67d4a1a8af 100644 --- a/.github/workflows/build-documentation.yml +++ b/.github/workflows/build-documentation.yml @@ -11,8 +11,7 @@ on: types: - created -permissions: - contents: read +permissions: {} jobs: build: @@ -21,20 +20,15 @@ jobs: contents: write steps: - name: Checkout code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: 3.x - - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.cfg') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Install dependencies shell: bash run: | @@ -55,15 +49,15 @@ jobs: GITHUB_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} run: | if [[ -n "${CUSTOM_PATH}" ]]; then - echo "path=${CUSTOM_PATH}" >> $GITHUB_OUTPUT + echo "path=${CUSTOM_PATH}" >> "$GITHUB_OUTPUT" # 0.x releases deploy to 0.x path, all other releases deploy to latest path - elif [[ "${{ github.event_name }}" == "release" && "${GITHUB_RELEASE_TAG_NAME}" == 0.* ]]; then - echo "path=0.x" >> $GITHUB_OUTPUT + elif [[ "${GITHUB_EVENT_NAME}" == "release" && "${GITHUB_RELEASE_TAG_NAME}" == 0.* ]]; then + echo "path=0.x" >> "$GITHUB_OUTPUT" # for manual builds deploy to the branch name path - elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - echo "path=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT + elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + echo "path=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" else - echo "path=latest" >> $GITHUB_OUTPUT + echo "path=latest" >> "$GITHUB_OUTPUT" fi - name: Deploy to GitHub Pages @@ -76,6 +70,8 @@ jobs: - name: Add URL to the Job Summary shell: bash + env: + DOCS_PATH: ${{ steps.resolve_url_path.outputs.path }} run: | - url="https://python.docs.firebolt.io/sdk_documentation/${{ steps.resolve_url_path.outputs.path }}" - echo "[Documentation]($url)" >> $GITHUB_STEP_SUMMARY + url="https://python.docs.firebolt.io/sdk_documentation/${DOCS_PATH}" + echo "[Documentation]($url)" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/check-pr-formatting.yml b/.github/workflows/check-pr-formatting.yml index 623989f425f..e9468556da7 100644 --- a/.github/workflows/check-pr-formatting.yml +++ b/.github/workflows/check-pr-formatting.yml @@ -5,8 +5,7 @@ on: branches: [ main, 0.x ] types: [opened, synchronize, edited] -permissions: - contents: read +permissions: {} jobs: pr-formatting: diff --git a/.github/workflows/code-check.yml b/.github/workflows/code-check.yml index 91cca23e493..3d5b96c9d9c 100644 --- a/.github/workflows/code-check.yml +++ b/.github/workflows/code-check.yml @@ -5,8 +5,7 @@ on: push: branches: [ main, 0.x ] -permissions: - contents: read +permissions: {} jobs: check-code: @@ -16,12 +15,12 @@ jobs: cache-mode: none steps: - name: Check out code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Set up Python 3.10 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.10' diff --git a/.github/workflows/integration-tests-core.yml b/.github/workflows/integration-tests-core.yml index 8de91c87b62..890b2920eb3 100644 --- a/.github/workflows/integration-tests-core.yml +++ b/.github/workflows/integration-tests-core.yml @@ -42,8 +42,7 @@ on: SLACK_BOT_TOKEN: required: false -permissions: - contents: read +permissions: {} jobs: run-core-integration-tests: @@ -54,12 +53,13 @@ jobs: CORE_IMAGE_TAG: ${{ inputs.tag_version || vars.DEFAULT_CORE_IMAGE_TAG }} steps: - name: Check out code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + persist-credentials: false repository: 'firebolt-db/firebolt-python-sdk' - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ inputs.python_version }} diff --git a/.github/workflows/integration-tests-v2.yml b/.github/workflows/integration-tests-v2.yml index 4771952762b..27db80fde86 100644 --- a/.github/workflows/integration-tests-v2.yml +++ b/.github/workflows/integration-tests-v2.yml @@ -18,8 +18,7 @@ on: type: string default: '' -permissions: - contents: read +permissions: {} jobs: tests: @@ -28,12 +27,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + persist-credentials: false repository: 'firebolt-db/firebolt-python-sdk' - name: Set up Python 3.10 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.10' diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 12a2a1146e3..5663f2f2fc0 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -12,11 +12,12 @@ on: ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_SECRET_STG: required: true -permissions: - contents: read +permissions: {} jobs: integration-test-v2: + permissions: + contents: read uses: ./.github/workflows/integration-tests-v2.yml secrets: FIREBOLT_CLIENT_ID_STG_NEW_IDN: ${{ secrets.FIREBOLT_CLIENT_ID_STG_NEW_IDN }} @@ -24,6 +25,8 @@ jobs: ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_ID_STG: ${{ secrets.ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_ID_STG }} ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_SECRET_STG: ${{ secrets.ECOSYSTEM_FIREBOLT_SA_NO_USER_CLIENT_SECRET_STG }} integration-test-core: + permissions: + contents: read uses: ./.github/workflows/integration-tests-core.yml report-test-results: permissions: diff --git a/.github/workflows/lint-and-scan-actions.yml b/.github/workflows/lint-and-scan-actions.yml new file mode 100644 index 00000000000..0b6c849742d --- /dev/null +++ b/.github/workflows/lint-and-scan-actions.yml @@ -0,0 +1,57 @@ +# Lint and security-scan GitHub Actions workflow files. +# +# Runs actionlint (structural/logic checks) and zizmor (security audit) +# as parallel jobs whenever files under .github/ are modified. +# +# zizmor results appear in the repository's Security tab (GitHub Advanced +# Security). The workflow does not fail on findings; use GitHub rulesets +# to block merges on unresolved alerts. + +name: Lint & Scan GitHub Actions + +on: + push: + branches: ["main"] + paths: [".github/**"] + pull_request: + branches: ["**"] + paths: [".github/**"] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + actionlint: + runs-on: ubuntu-latest + permissions: + contents: read + checks: write # needed for github-pr-check reporter + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Run actionlint + uses: reviewdog/action-actionlint@dbe5299849118fd6f099ba563d263d770955a64a # v1.73.2 + with: + reporter: github-pr-check + fail_level: error + + zizmor: + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read # needed for private repos + actions: read # needed for private repos + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@6599ee8b7a49aef6a770f63d261d214911a7ce02 # v0.6.0 diff --git a/.github/workflows/nightly-v2.yml b/.github/workflows/nightly-v2.yml index 8be0dfc3c92..114be154189 100644 --- a/.github/workflows/nightly-v2.yml +++ b/.github/workflows/nightly-v2.yml @@ -4,13 +4,16 @@ on: schedule: - cron: '0 3 * * *' # 3 am UTC every day -permissions: - contents: read +permissions: {} jobs: code-check: + permissions: + contents: read uses: ./.github/workflows/code-check.yml unit-tests: + permissions: + contents: read uses: ./.github/workflows/unit-tests.yml secrets: GIST_PAT: ${{ secrets.GIST_PAT }} @@ -24,6 +27,8 @@ jobs: FOSSA_TOKEN: ${{ secrets.FOSSA_TOKEN }} SONARCLOUD_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }} core: + permissions: + contents: read strategy: fail-fast: false # finish all jobs even if one fails matrix: @@ -37,6 +42,8 @@ jobs: python_version: ${{ matrix.python-version }} sendSlackNotifications: true tests: + permissions: + contents: read runs-on: ${{ matrix.os }} strategy: fail-fast: false # finish all jobs even if one fails @@ -46,10 +53,12 @@ jobs: python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - name: Check out code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index bef4c0e432f..4f2661c66b2 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -4,13 +4,16 @@ on: pull_request: branches: [ main, 0.x ] -permissions: - contents: read +permissions: {} jobs: code-checkers: + permissions: + contents: read uses: ./.github/workflows/code-check.yml unit-tests: + permissions: + contents: read uses: ./.github/workflows/unit-tests.yml secrets: GIST_PAT: ${{ secrets.GIST_PAT }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ac505e4ad8..9b92038ce96 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,8 +10,7 @@ on: required: false description: 'Trigger a major release (optional). Leave empty for regular release.' -permissions: - contents: read +permissions: {} jobs: integration-tests: @@ -40,14 +39,14 @@ jobs: permission-contents: write - name: Check out code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} persist-credentials: true # The release action pushes the version commit and tag. - name: 'Publish action' - uses: firebolt-db/action-python-release@0cb5692fd39a782bce366e9120055d999bc8824b # main + uses: firebolt-db/action-python-release@535d72bf4fb7ff9ff7f87bb0454e232afe1cef91 # main with: pre-release-tag: ${{ inputs.pre-release-tag }} major-release: ${{ inputs.major-release }} diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 9e1b5a16eee..8ce4ea1dcff 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -9,21 +9,22 @@ on: SONARCLOUD_TOKEN: required: true -permissions: - contents: read - pull-requests: read +permissions: {} jobs: security-scan: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read steps: - name: "Checkout Code" - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: "Download coverage report" - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: pytest-coverage-report diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 797b3fd25a0..18335f08dae 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -11,8 +11,7 @@ on: push: branches: [ main, 0.x ] -permissions: - contents: read +permissions: {} jobs: unit-tests: @@ -21,12 +20,12 @@ jobs: contents: read steps: - name: Check out code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Set up Python 3.10 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.10' @@ -40,7 +39,7 @@ jobs: pytest --cov=src/ tests/unit --cov-report=xml - name: Upload coverage report - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 continue-on-error: true with: name: pytest-coverage-report @@ -53,13 +52,13 @@ jobs: run: | fraction=$(sed -n 2p coverage.xml | sed 's/.*line-rate=\"\([0-9.]*\)\".*$/\1/') percentage=$(echo "scale=1; $fraction * 100" | bc -l) - percentage_whole=$(echo "${percentage%.*}") - colour=$(if [ $percentage_whole -ge 80 ]; then echo "green"; else echo "orange"; fi) - echo "colour=$colour" >> $GITHUB_OUTPUT - echo "covered=$percentage_whole" >> $GITHUB_OUTPUT + percentage_whole="${percentage%.*}" + colour=$(if [ "$percentage_whole" -ge 80 ]; then echo "green"; else echo "orange"; fi) + echo "colour=$colour" >> "$GITHUB_OUTPUT" + echo "covered=$percentage_whole" >> "$GITHUB_OUTPUT" - name: Create Coverage Badge - uses: schneegans/dynamic-badges-action@0e50b8bad39e7e1afd3e4e9c2b7dd145fad07501 # v1.8.0 + uses: schneegans/dynamic-badges-action@28b0fa8bdeb46170ac397105ece0c1fe58f68910 # v1.9.0 if: github.event_name == 'push' continue-on-error: true with: From 8fb08dbde04c52e5e4b0884139acd957ccdd9575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Str=C3=BCbe?= Date: Fri, 16 Oct 2026 16:06:35 +0200 Subject: [PATCH 2/4] ci: never let pinact move integration-testing-setup (FB-4904) pinact -u ignores Dependabot's semver-major ignore and moves v1 callers of integration-testing-setup to v2, a different product with different inputs. Exclude the action from pinact; Dependabot keeps it updated within its major. Co-Authored-By: Claude Opus 5.5 --- .github/pinact.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/pinact.yaml diff --git a/.github/pinact.yaml b/.github/pinact.yaml new file mode 100644 index 00000000000..b90a7ab17f0 --- /dev/null +++ b/.github/pinact.yaml @@ -0,0 +1,7 @@ +# pinact configuration: https://github.com/suzuki-shunsuke/pinact/blob/main/docs/config.md +version: 3 +ignore_actions: + # The major version selects the product (v1 = FB 1.0, v2 = FB 2.0), so `pinact run -u` + # must never move it. Dependabot (with its semver-major ignore) keeps it up to date. + - name: firebolt-db/integration-testing-setup + ref: .* From 4ae3fa622fb69c459c6ec38a85386b6fd285e322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Str=C3=BCbe?= Date: Fri, 16 Oct 2026 16:19:37 +0200 Subject: [PATCH 3/4] ci: skip PR title check by Dependabot author, not branch name (FB-4904) startsWith(github.head_ref, 'dependabot') matches any branch named dependabot*, so anyone could skip the check by naming their branch so. Check the PR author instead. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/check-pr-formatting.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-pr-formatting.yml b/.github/workflows/check-pr-formatting.yml index e9468556da7..e6c234b805d 100644 --- a/.github/workflows/check-pr-formatting.yml +++ b/.github/workflows/check-pr-formatting.yml @@ -16,7 +16,8 @@ jobs: - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: ${{ !startsWith(github.head_ref, 'dependabot') }} + # Skip Dependabot PRs. Check the PR author, not the branch name, which anyone can choose. + if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }} with: # Ensures the subject doesn't start with an uppercase character. subjectPattern: ^(?![A-Z]).+$ From 2a989ef6cd6434920d7584539ebdb9967c8d472a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Str=C3=BCbe?= Date: Fri, 16 Oct 2026 16:19:37 +0200 Subject: [PATCH 4/4] ci: add zizmor config (FB-4904) Disable the self-repository audit: actionlint does not support the suggested `uses: $/...` syntax yet. Co-Authored-By: Claude Opus 5.5 --- .github/zizmor.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/zizmor.yml diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 00000000000..9dcb63c97e6 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,6 @@ +rules: + # zizmor suggests the new `uses: $/...` self-repository syntax for local + # reusable workflows, but actionlint does not support it yet and fails on it. + # Keep the workspace-relative `./.github/workflows/...` form until it does. + self-repository: + disable: true