diff --git a/.github/workflows/benchmarks_report.yml b/.github/workflows/benchmarks_report.yml index 4a45da3704..07e3b11e4c 100644 --- a/.github/workflows/benchmarks_report.yml +++ b/.github/workflows/benchmarks_report.yml @@ -2,17 +2,32 @@ # Separated for security: # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ +# Reference +# - https://github.com/actions/github-script +# - https://github.com/actions/upload-artifact +# - https://github.com/actions/checkout +# - https://github.com/actions/download-artifact +# - https://github.com/actions/setup-python + name: benchmarks-report run-name: Report benchmark results on: - workflow_run: + # Security: it is impossible to fully avoid this exposure, so long as we want results + # from pull request CI to be posted as a comment. `permissions`, and `bm_runner.py` + # are as locked-down as possible, and maintainers must manually approve workflow + # runs from external authors, to mitigate the risk. The remaining vulnerability + # is spam comments. + workflow_run: # zizmor: ignore[dangerous-triggers] workflows: [benchmarks-run] types: - completed jobs: download: + permissions: + actions: read + contents: read runs-on: ubuntu-latest outputs: reports_exist: ${{ steps.unzip.outputs.reports_exist }} @@ -20,7 +35,7 @@ jobs: - name: Download artifact id: download-artifact # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow - uses: actions/github-script@v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 with: script: | let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ @@ -54,28 +69,33 @@ jobs: echo "reports_exist=$reports_exist" >> "$GITHUB_OUTPUT" - name: Store artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: benchmark_reports path: benchmark_reports post_reports: + permissions: + issues: write + pull-requests: write runs-on: ubuntu-latest needs: download if: needs.download.outputs.reports_exist == 1 steps: - name: Checkout repo - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false - name: Download artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: name: benchmark_reports path: .github/workflows/benchmark_reports - name: Set up Python # benchmarks/bm_runner.py only needs builtins to run. - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 - name: Post reports env: diff --git a/.github/workflows/benchmarks_run.yml b/.github/workflows/benchmarks_run.yml index bfbf5a81f0..fe90c8f7a5 100644 --- a/.github/workflows/benchmarks_run.yml +++ b/.github/workflows/benchmarks_run.yml @@ -2,6 +2,12 @@ # - In the last 24 hours' commits. # - Introduced by this pull request. +# Reference +# - https://github.com/actions/checkout +# - https://github.com/MarceloPrado/has-changed-path +# - https://github.com/actions/cache +# - https://github.com/actions/upload-artifact + name: benchmarks-run run-name: Run benchmarks @@ -19,6 +25,8 @@ on: # Add the `labeled` type to the default list. types: [labeled, opened, synchronize, reopened] +permissions: {} + jobs: pre-checks: # This workflow supports two different scenarios (overnight and branch). @@ -29,9 +37,10 @@ jobs: overnight: ${{ steps.overnight.outputs.check }} branch: ${{ steps.branch.outputs.check }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: fetch-depth: 2 + persist-credentials: false - id: files-changed uses: marceloprado/has-changed-path@df1b7a3161b8fb9fd8c90403c66a9e66dfde50cb with: @@ -72,16 +81,17 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout repo - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: fetch-depth: 0 + persist-credentials: false - name: Install run dependencies run: pip install asv nox!=2025.05.01 - name: Cache environment directories id: cache-env-dir - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae with: path: | .nox @@ -91,7 +101,7 @@ jobs: - name: Cache test data directory id: cache-test-data - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae with: path: | ${{ env.IRIS_TEST_DATA_PATH }} @@ -115,10 +125,11 @@ jobs: # the proposed merge with the base branch. if: needs.pre-checks.outputs.branch == 'true' env: + BASE_REF: ${{ github.base_ref }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.number }} run: | - nox -s benchmarks -- branch origin/${{ github.base_ref }} + nox -s benchmarks -- branch origin/${BASE_REF} - name: Run overnight benchmarks # If the 'overnight' condition(s) are met: use the bm_runner to compare @@ -126,16 +137,17 @@ jobs: id: overnight if: needs.pre-checks.outputs.overnight == 'true' env: + FIRST_COMMIT: ${{ inputs.first_commit }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # The first_commit argument allows a custom starting point - useful # for manual re-running. run: | - first_commit=${{ inputs.first_commit }} + first_commit=${FIRST_COMMIT} if [ "$first_commit" == "" ] then first_commit=$(git log --after="$(date -d "1 day ago" +"%Y-%m-%d") 23:00:00" --pretty=format:"%h" | tail -n 1) fi - + if [ "$first_commit" != "" ] then nox -s benchmarks -- overnight $first_commit @@ -157,7 +169,7 @@ jobs: - name: Upload any benchmark reports # Uploading enables more downstream processing e.g. posting a PR comment. if: success() || steps.overnight.outcome == 'failure' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: benchmark_reports path: .github/workflows/benchmark_reports @@ -165,7 +177,7 @@ jobs: - name: Archive asv results # Store the raw ASV database(s) to help manual investigations. if: ${{ always() }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: asv-raw-results path: benchmarks/.asv/results diff --git a/.github/workflows/benchmarks_validate.yml b/.github/workflows/benchmarks_validate.yml index 48f49bd0c4..9c49567c9c 100644 --- a/.github/workflows/benchmarks_validate.yml +++ b/.github/workflows/benchmarks_validate.yml @@ -1,3 +1,7 @@ +# Reference +# - https://github.com/actions/checkout +# - https://github.com/actions/cache + name: benchmarks-validate run-name: Validate the benchmarking setup @@ -17,6 +21,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: {} + jobs: validate: runs-on: ubuntu-latest @@ -27,16 +33,17 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: fetch-depth: 0 + persist-credentials: false - name: Install run dependencies run: pip install asv nox!=2025.05.01 - - name: Cache environment directories - id: cache-env-dir - uses: actions/cache@v5 + - name: Restore environment cache + id: cache-restore + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae with: path: | .nox @@ -46,3 +53,15 @@ jobs: - name: Validate setup run: nox -s benchmarks -- validate + + - name: Save environment cache + # Security: PRs are potentially malformed/malicious, so only allow runs on trunk + # branches to update the cache, to avoid cache poisoning. + if: github.event_name == 'push' + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae + with: + path: | + .nox + benchmarks/.asv/env + $CONDA/pkgs + key: ${{ steps.cache-restore.outputs.cache-primary-key }} diff --git a/.github/workflows/ci-citation.yml b/.github/workflows/ci-citation.yml index 26945289a9..c76b77ed11 100644 --- a/.github/workflows/ci-citation.yml +++ b/.github/workflows/ci-citation.yml @@ -1,3 +1,7 @@ +# Reference: +# - https://github.com/actions/checkout +# - https://github.com/citation-file-format/cffconvert-github-action + name: ci-citation on: @@ -15,14 +19,17 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: {} + jobs: validate: name: "validate" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: fetch-depth: 0 + persist-credentials: false - name: "check CITATION.cff" uses: citation-file-format/cffconvert-github-action@4cf11baa70a673bfdf9dad0acc7ee33b3f4b6084 diff --git a/.github/workflows/ci-linkchecks.yml b/.github/workflows/ci-linkchecks.yml index 6ffdd3df93..3d60593753 100644 --- a/.github/workflows/ci-linkchecks.yml +++ b/.github/workflows/ci-linkchecks.yml @@ -1,4 +1,9 @@ -name: Linkcheck +# References: +# - https://github.com/actions/checkout +# - https://github.com/lycheeverse/lychee-action +# - https://github.com/peter-evans/create-issue-from-file + +name: ci-linkchecks on: workflow_dispatch: @@ -16,7 +21,7 @@ jobs: issues: write # required for peter-evans/create-issue-from-file steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/ci-manifest.yml b/.github/workflows/ci-manifest.yml index cde7c20f52..fe51d85456 100644 --- a/.github/workflows/ci-manifest.yml +++ b/.github/workflows/ci-manifest.yml @@ -1,5 +1,6 @@ # Reference # - https://github.com/actions/checkout +# - https://github.com/SciTools/workflows name: ci-manifest @@ -20,7 +21,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: {} + jobs: manifest: name: "check-manifest" - uses: scitools/workflows/.github/workflows/ci-manifest.yml@2026.05.0 + uses: scitools/workflows/.github/workflows/ci-manifest.yml@1f2141422a63321a32575ddd186e53acff12550c diff --git a/.github/workflows/ci-template-check.yml b/.github/workflows/ci-template-check.yml index 3475c14d25..300328cf56 100644 --- a/.github/workflows/ci-template-check.yml +++ b/.github/workflows/ci-template-check.yml @@ -4,13 +4,17 @@ name: ci-template-check on: - pull_request_target: + pull_request_target: # zizmor: ignore[dangerous-triggers] branches: - main +permissions: {} + jobs: prompt-share: - uses: scitools/workflows/.github/workflows/ci-template-check.yml@2026.05.0 - secrets: inherit + uses: scitools/workflows/.github/workflows/ci-template-check.yml@1f2141422a63321a32575ddd186e53acff12550c + secrets: + AUTH_APP_ID: ${{ secrets.AUTH_APP_ID }} + AUTH_APP_PRIVATE_KEY: ${{ secrets.AUTH_APP_PRIVATE_KEY }} with: pr_number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 92899840d9..2988f53743 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -1,6 +1,7 @@ # reference: # - https://github.com/actions/cache # - https://github.com/actions/checkout +# - https://github.com/codecov/codecov-action # - https://github.com/marketplace/actions/setup-miniconda name: ci-tests @@ -21,6 +22,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: {} + jobs: tests: name: "${{ matrix.session }} (py${{ matrix.python-version }} ${{ matrix.os }})" @@ -56,9 +59,10 @@ jobs: steps: - name: "checkout" - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: fetch-depth: 0 + persist-credentials: false - name: "environment configure" env: @@ -83,7 +87,7 @@ jobs: env_name: ${{ env.ENV_NAME }} - name: "conda install" - uses: conda-incubator/setup-miniconda@v4 + uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 with: miniforge-version: latest channels: conda-forge @@ -145,6 +149,6 @@ jobs: - name: "upload coverage report" if: ${{ matrix.coverage }} - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 with: - token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/ci-wheels.yml b/.github/workflows/ci-wheels.yml index 9da409f1f6..80df3a1a19 100644 --- a/.github/workflows/ci-wheels.yml +++ b/.github/workflows/ci-wheels.yml @@ -1,5 +1,6 @@ # Reference: # - https://github.com/actions/checkout +# - https://github.com/conda-incubator/setup-miniconda # - https://github.com/actions/download-artifact # - https://github.com/actions/upload-artifact # - https://github.com/pypa/build @@ -23,21 +24,24 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: {} + jobs: build: name: "build sdist & wheel" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: fetch-depth: 0 + persist-credentials: false - name: "building" shell: bash run: | pipx run build - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: pypi-artifacts path: ${{ github.workspace }}/dist/* @@ -57,11 +61,12 @@ jobs: env: ENV_NAME: "ci-wheels" steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: fetch-depth: 0 + persist-credentials: false - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: name: pypi-artifacts path: ${{ github.workspace }}/dist @@ -82,7 +87,7 @@ jobs: env_name: ${{ env.ENV_NAME }} - name: "conda install" - uses: conda-incubator/setup-miniconda@v4 + uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 with: miniforge-version: latest channels: conda-forge,defaults @@ -116,7 +121,7 @@ jobs: name: "show artifacts" runs-on: ubuntu-latest steps: - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: name: pypi-artifacts path: ${{ github.workspace }}/dist @@ -137,7 +142,7 @@ jobs: # and check for the SciTools repo if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && github.repository_owner == 'SciTools' steps: - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: name: pypi-artifacts path: ${{ github.workspace }}/dist @@ -159,7 +164,7 @@ jobs: # upload to PyPI for every tag starting with 'v' if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') && github.repository_owner == 'SciTools' steps: - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: name: pypi-artifacts path: ${{ github.workspace }}/dist diff --git a/.github/workflows/composite/cartopy-cache/action.yml b/.github/workflows/composite/cartopy-cache/action.yml index 800a19162b..3a7d2e7b99 100644 --- a/.github/workflows/composite/cartopy-cache/action.yml +++ b/.github/workflows/composite/cartopy-cache/action.yml @@ -20,7 +20,7 @@ inputs: runs: using: "composite" steps: - - uses: actions/cache@v5 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae id: cartopy-cache with: path: ~/.local/share/cartopy @@ -30,11 +30,13 @@ runs: env: CARTOPY_SHARE_DIR: ~/.local/share/cartopy CARTOPY_FEATURE: https://raw.githubusercontent.com/SciTools/cartopy/v0.20.0/tools/cartopy_feature_download.py + CONDA_BASEDIR: ${{ env.CONDA }} + INPUTS_ENV_NAME: ${{ inputs.env_name }} shell: bash run: | # Require to explicitly activate the environment within the composite action. - source ${{ env.CONDA }}/etc/profile.d/conda.sh >/dev/null 2>&1 - conda activate ${{ inputs.env_name }} + source ${CONDA_BASEDIR}/etc/profile.d/conda.sh >/dev/null 2>&1 + conda activate ${INPUTS_ENV_NAME} wget --quiet ${CARTOPY_FEATURE} mkdir -p ${CARTOPY_SHARE_DIR} # Requires a pre-installed version of cartopy within the environment. diff --git a/.github/workflows/composite/conda-env-cache/action.yml b/.github/workflows/composite/conda-env-cache/action.yml index 8be4e63f88..762ed966b9 100644 --- a/.github/workflows/composite/conda-env-cache/action.yml +++ b/.github/workflows/composite/conda-env-cache/action.yml @@ -23,13 +23,16 @@ inputs: runs: using: "composite" steps: - - uses: actions/cache@v5 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae id: conda-env-cache with: path: ${{ env.CONDA }}/envs/${{ inputs.env_name }} key: ${{ runner.os }}-conda-env-${{ inputs.env_name }}-p${{ inputs.cache_period }}-b${{ inputs.cache_build }} - if: steps.conda-env-cache.outputs.cache-hit != 'true' + env: + INPUTS_ENV_NAME: ${{ inputs.env_name }} + INPUTS_INSTALL_PACKAGES: ${{ inputs.install_packages }} shell: bash run: | - conda install --quiet --name ${{ inputs.env_name }} ${{ inputs.install_packages }} + conda install --quiet --name ${INPUTS_ENV_NAME} ${INPUTS_INSTALL_PACKAGES} diff --git a/.github/workflows/composite/conda-pkg-cache/action.yml b/.github/workflows/composite/conda-pkg-cache/action.yml index a6fd89762b..c66eeb0770 100644 --- a/.github/workflows/composite/conda-pkg-cache/action.yml +++ b/.github/workflows/composite/conda-pkg-cache/action.yml @@ -16,7 +16,7 @@ inputs: runs: using: "composite" steps: - - uses: actions/cache@v5 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae with: path: ~/conda_pkgs_dir key: ${{ runner.os }}-conda-pkgs-${{ inputs.env_name }}-p${{ inputs.cache_period }}-b${{ inputs.cache_build }} diff --git a/.github/workflows/composite/iris-data-cache/action.yml b/.github/workflows/composite/iris-data-cache/action.yml index 6d12a768fd..fe6fbd7ac6 100644 --- a/.github/workflows/composite/iris-data-cache/action.yml +++ b/.github/workflows/composite/iris-data-cache/action.yml @@ -16,15 +16,17 @@ inputs: runs: using: "composite" steps: - - uses: actions/cache@v5 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae id: data-cache with: path: ~/iris-test-data key: ${{ runner.os }}-iris-test-data-${{ inputs.env_name }}-v${{ inputs.version }}-b${{ inputs.cache_build }} - if: steps.data-cache.outputs.cache-hit != 'true' + env: + INPUTS_VERSION: ${{ inputs.version }} shell: bash run: | - wget --quiet https://github.com/SciTools/iris-test-data/archive/v${{ inputs.version }}.zip -O iris-test-data.zip + wget --quiet https://github.com/SciTools/iris-test-data/archive/v${INPUTS_VERSION}.zip -O iris-test-data.zip unzip -q iris-test-data.zip - mv iris-test-data-${{ inputs.version }} ~/iris-test-data + mv iris-test-data-${INPUTS_VERSION} ~/iris-test-data diff --git a/.github/workflows/composite/nox-cache/action.yml b/.github/workflows/composite/nox-cache/action.yml index f8249033df..876b6a2a84 100644 --- a/.github/workflows/composite/nox-cache/action.yml +++ b/.github/workflows/composite/nox-cache/action.yml @@ -16,7 +16,7 @@ inputs: runs: using: "composite" steps: - - uses: actions/cache@v5 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae with: path: ${{ github.workspace }}/.nox key: ${{ runner.os }}-nox-${{ inputs.env_name }}-s${{ matrix.session }}-py${{ matrix.python-version }}-b${{ inputs.cache_build }}-${{ hashFiles(inputs.lock_file) }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index b25ce8691b..e01f391a59 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -12,4 +12,4 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v6 \ No newline at end of file + - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index df6929bdab..cc6d38df9d 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -7,12 +7,16 @@ name: Refresh Lockfiles on: workflow_dispatch: schedule: - # Run once a week on a Saturday night + # Run once a week on a Saturday night # N.B. "should" be quoted, according to # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onschedule - cron: "1 0 * * 6" +permissions: {} + jobs: refresh_lockfiles: - uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@2026.05.0 - secrets: inherit + uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@1f2141422a63321a32575ddd186e53acff12550c + secrets: + AUTH_APP_ID: ${{ secrets.AUTH_APP_ID }} + AUTH_APP_PRIVATE_KEY: ${{ secrets.AUTH_APP_PRIVATE_KEY }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 94bfa8e0a8..1949ba976c 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,4 +1,5 @@ -# See https://github.com/actions/stale +# Reference: +# - https://github.com/actions/stale name: Stale issues and pull-requests @@ -9,12 +10,23 @@ on: # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onschedule - cron: "0 0 * * *" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + actions: write + issues: write + pull-requests: write + jobs: stale: if: "github.repository == 'SciTools/iris'" runs-on: ubuntu-latest steps: - - uses: actions/stale@v10 + - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ea2792a221..fb2347dd00 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,7 @@ ci: # Alphabetised, for lack of a better order. files: | (?x)( + .github\/workflows\/.*| benchmarks\/.+\.py| docs\/.+\.py| lib\/.+\.py| @@ -113,3 +114,8 @@ repos: rev: "v0.24.1" hooks: - id: validate-pyproject + +- repo: https://github.com/zizmorcore/zizmor-pre-commit + rev: v1.25.2 + hooks: + - id: zizmor diff --git a/README.md b/README.md index 19e0fbbdae..696ec574f4 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ | 💬 Community | [![Contributor Covenant](https://img.shields.io/badge/contributor%20covenant-2.1-4baaaa.svg)](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) [![GH Discussions](https://img.shields.io/badge/github-discussions%20%F0%9F%92%AC-yellow?logo=github&logoColor=lightgrey)](https://github.com/SciTools/iris/discussions) [![bluesky](https://img.shields.io/badge/scitools-0285FF?label=bluesky&logo=bluesky&logoColor=0285FF)](https://bsky.app/profile/scitools.bsky.social) | | 📖 Documentation | [![rtd](https://readthedocs.org/projects/scitools-iris/badge/?version=latest)](https://scitools-iris.readthedocs.io/en/latest/?badge=latest) [![Check Links](https://github.com/SciTools/iris/actions/workflows/ci-linkchecks.yml/badge.svg)](https://github.com/SciTools/iris/actions/workflows/ci-linkchecks.yml) | | 📈 Health | [![codecov](https://codecov.io/gh/SciTools/iris/branch/main/graph/badge.svg?token=0GeICSIF3g)](https://codecov.io/gh/SciTools/iris) | -| ✨ Meta | [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![NEP29](https://raster.shields.io/badge/follows-NEP29-orange.png)](https://numpy.org/neps/nep-0029-deprecation_policy.html) [![license - bds-3-clause](https://img.shields.io/github/license/SciTools/iris)](https://github.com/SciTools/iris/blob/main/LICENSE) [![conda platform](https://img.shields.io/conda/pn/conda-forge/iris.svg)](https://anaconda.org/conda-forge/iris) | +| ✨ Meta | [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![NEP29](https://raster.shields.io/badge/follows-NEP29-orange.png)](https://numpy.org/neps/nep-0029-deprecation_policy.html) [![license - bds-3-clause](https://img.shields.io/github/license/SciTools/iris)](https://github.com/SciTools/iris/blob/main/LICENSE) [![conda platform](https://img.shields.io/conda/pn/conda-forge/iris.svg)](https://anaconda.org/conda-forge/iris) [![zizmor](https://img.shields.io/badge/%F0%9F%8C%88-zizmor-white?labelColor=white)](https://zizmor.sh/) | | 📦 Package | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.595182.svg)](https://doi.org/10.5281/zenodo.595182) [![conda-forge](https://img.shields.io/conda/vn/conda-forge/iris?color=orange&label=conda-forge&logo=conda-forge&logoColor=white)](https://anaconda.org/conda-forge/iris) [![pypi](https://img.shields.io/pypi/v/scitools-iris?color=orange&label=pypi&logo=python&logoColor=white)](https://pypi.org/project/scitools-iris/) [![pypi - python version](https://img.shields.io/pypi/pyversions/scitools-iris.svg?color=orange&logo=python&label=python&logoColor=white)](https://pypi.org/project/scitools-iris/) | | 🧰 Repo | [![commits-since](https://img.shields.io/github/commits-since/SciTools/iris/latest.svg)](https://github.com/SciTools/iris/commits/main) [![contributors](https://img.shields.io/github/contributors/SciTools/iris)](https://github.com/SciTools/iris/graphs/contributors) [![release](https://img.shields.io/github/v/release/scitools/iris)](https://github.com/SciTools/iris/releases) | | | diff --git a/benchmarks/bm_runner.py b/benchmarks/bm_runner.py index 79cb0798fb..23c88f4b4e 100755 --- a/benchmarks/bm_runner.py +++ b/benchmarks/bm_runner.py @@ -173,6 +173,46 @@ def _asv_compare( raise RuntimeError(message) +def _read_gh_report_command(command_path: Path, commit_dir: Path) -> list[str]: + body_file = commit_dir / "body.txt" + command = command_path.read_text().strip().split("\t") + if len(command) == 3 and command[0] == "pr_comment": + _, pr_number, repo = command + return [ + "gh", + "pr", + "comment", + pr_number, + "--body-file", + str(body_file), + "--repo", + repo, + ] + if len(command) == 4 and command[0] == "issue_create": + _, repo, title, assignee = command + command = [ + "gh", + "issue", + "create", + "--title", + title, + "--body-file", + str(body_file), + "--label", + "Bot", + "--label", + "Type: Performance", + "--repo", + repo, + ] + if assignee: + command.extend(["--assignee", assignee]) + return command + + message = f"Unexpected report command format: {command_path}" + raise ValueError(message) + + def _gh_create_reports(commit_sha: str, results_full: str, results_shifts: str) -> None: """If running under GitHub Actions: record the results in report(s). @@ -230,17 +270,12 @@ def _gh_create_reports(commit_sha: str, results_full: str, results_shifts: str) ) if on_pull_request: - # Command to post the report as a comment on the active PR. + # Strict command format to post report as a comment on the active PR. body_path.write_text(performance_report) - command = ( - f"gh pr comment {pr_number} " - f"--body-file {body_path.absolute()} " - f"--repo {repo}" - ) - command_path.write_text(command) + command_path.write_text(f"pr_comment\t{pr_number}\t{repo}") else: - # Command to post the report as new issue. + # Strict command format to post the report as a new issue. commit_msg = _subprocess_runner_capture( f"git log {commit_sha}^! --oneline".split(" ") ) @@ -291,18 +326,7 @@ def _gh_create_reports(commit_sha: str, results_full: str, results_shifts: str) ) body += performance_report body_path.write_text(body) - - command = ( - "gh issue create " - f'--title "{title}" ' - f"--body-file {body_path.absolute()} " - '--label "Bot" ' - '--label "Type: Performance" ' - f"--repo {repo}" - ) - if assignee: - command += f" --assignee {assignee}" - command_path.write_text(command) + command_path.write_text(f"issue_create\t{repo}\t{title}\t{assignee}") def _gh_post_reports() -> None: @@ -318,12 +342,8 @@ def _gh_post_reports() -> None: commit_dirs = [x for x in GH_REPORT_DIR.iterdir() if x.is_dir()] for commit_dir in commit_dirs: command_path = commit_dir / "command.txt" - command = command_path.read_text() - - # Security: only accept certain commands to run. - assert command.startswith(("gh issue create", "gh pr comment")) - - _subprocess_runner(shlex.split(command)) + command = _read_gh_report_command(command_path, commit_dir) + _subprocess_runner(command) class _SubParserGenerator(ABC): diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 8a0fe92159..737af1347e 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -132,6 +132,10 @@ This document explains the changes made to Iris for this release #. `@SgtVarmint`_ migrated codebase from ``os.path`` to ``pathlib.Path`` where possible (:issue:`4523`, :pull:`7087`) + +#. `@bjlittle`_ and `@trexfeathers`_ added the `Zizmor`_ pre-commit hook to monitor for + security vulnerabilities in Iris' GitHub Actions workflows, and then actioned + Zizmor's recommendations to harden the workflows. (:pull:`7138`) .. comment Whatsnew author names (@github name) in alphabetical order. Note that, @@ -143,3 +147,4 @@ This document explains the changes made to Iris for this release .. comment Whatsnew resources in alphabetical order: .. _cf-checker: https://github.com/cedadev/cf-checker +.. _Zizmor: https://github.com/zizmorcore/zizmor