Fix: Resolve workflow vulnerabilities (#154)
Some checks failed
Python CI / build (push) Has been cancelled

* resolve workflow vulnerabilities

* Set persist-credentials: false on checkouts before untrusted steps.

Prevents GITHUB_TOKEN from being written to git config before pip/pytest
in CI and before build steps in release. add-tag checkout keeps default
credentials for GitHub release creation.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin Han 2026-06-03 09:47:58 +01:00 committed by GitHub
parent fa5af77369
commit a2aaa9f059
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 113 additions and 22 deletions

View File

@ -1,3 +1,7 @@
# Runs on pushes to main and on pull requests whose head branch lives in this
# repository (contributors with push access). Fork PRs are skipped: GitHub still
# starts the workflow, but the job does not run, so untrusted code is not installed
# or executed via pip/pytest.
name: Python CI name: Python CI
on: on:
@ -6,36 +10,66 @@ on:
pull_request: pull_request:
branches: [ "main" ] branches: [ "main" ]
permissions:
contents: read
jobs: jobs:
build: build:
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5 - uses: actions/setup-python@v5
with: with:
python-version: '3.11' python-version: '3.11'
cache: 'pip' cache: 'pip'
- id: packages
name: Discover packages
run: |
shopt -s nullglob
packages=()
for dir in ./thousandeyes-sdk-*/; do
name="${dir#./}"
name="${name%/}"
if ! printf '%s' "$name" | grep -Eq '^thousandeyes-sdk-[a-z0-9-]+$'; then
echo "Invalid package directory name: ${name}" >&2
exit 1
fi
packages+=("$name")
done
if [ "${#packages[@]}" -eq 0 ]; then
echo "No thousandeyes-sdk-* packages found" >&2
exit 1
fi
FOLDERS_JSON=$(printf '%s\n' "${packages[@]}" | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "packages=${FOLDERS_JSON}" >> "$GITHUB_OUTPUT"
- name: Install core module - name: Install core module
run: pip install -e thousandeyes-sdk-core run: pip install -e thousandeyes-sdk-core
- name: Install and test modules - name: Install and test modules
env:
PACKAGES_JSON: ${{ steps.packages.outputs.packages }}
run: | run: |
pip install pytest pip install pytest
pip install coverage pip install coverage
# Initialize coverage data file
coverage erase coverage erase
for module in $(find . -maxdepth 1 -type d -name "thousandeyes-sdk-*" | cut -c 3-); do mapfile -t modules < <(jq -r '.[]' <<< "$PACKAGES_JSON")
pip install -e $module for module in "${modules[@]}"; do
coverage run --source=$module -m pytest $module pip install -e "./${module}"
# Move the .coverage file to a unique name coverage run --source="./${module}" -m pytest "./${module}"
mv .coverage .coverage.$module mv .coverage ".coverage.${module}"
done done
# Combine all .coverage files
coverage combine .coverage.* coverage combine .coverage.*
coverage report coverage report
coverage xml coverage xml

View File

@ -1,3 +1,5 @@
# Manual release only. GitHub allows workflow_dispatch only for users with write
# access to this repository. PyPI publish is further gated by the "release" environment.
name: Release name: Release
on: on:
workflow_dispatch: workflow_dispatch:
@ -7,59 +9,114 @@ on:
required: true required: true
type: string type: string
permissions:
contents: read
jobs: jobs:
validate-release:
runs-on: ubuntu-latest
outputs:
release_version: ${{ steps.validate.outputs.release_version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- id: validate
env:
RELEASE_VERSION: ${{ inputs.releaseVersion }}
run: |
if ! printf '%s' "$RELEASE_VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$'; then
echo "Invalid releaseVersion: must match X.Y.Z or X.Y.ZrcN (e.g. 2.26.0 or 2.0.0rc1)" >&2
exit 1
fi
if git rev-parse "refs/tags/${RELEASE_VERSION}" >/dev/null 2>&1; then
echo "Tag ${RELEASE_VERSION} already exists" >&2
exit 1
fi
echo "release_version=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT"
set-package-matrix: set-package-matrix:
needs: validate-release
# This action returns all sub-packages to be published. # This action returns all sub-packages to be published.
# It thens exports the variable to `matrix`, so that the deployment job is run individually for each sub-package # It then exports the variable to `matrix`, so that the deployment job is run individually for each sub-package
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
packages: ${{ steps.packages.outputs.packages }} packages: ${{ steps.packages.outputs.packages }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
persist-credentials: false
- id: packages - id: packages
run: | run: |
FOLDERS_JSON=$(find . -maxdepth 1 -type d -name "thousandeyes-sdk-*" | cut -c 3- | jq -R -s -c 'split("\n")[:-1]') shopt -s nullglob
echo "packages=$FOLDERS_JSON" >> "$GITHUB_OUTPUT" packages=()
for dir in ./thousandeyes-sdk-*/; do
name="${dir#./}"
name="${name%/}"
if ! printf '%s' "$name" | grep -Eq '^thousandeyes-sdk-[a-z0-9-]+$'; then
echo "Invalid package directory name: ${name}" >&2
exit 1
fi
packages+=("$name")
done
if [ "${#packages[@]}" -eq 0 ]; then
echo "No thousandeyes-sdk-* packages found" >&2
exit 1
fi
FOLDERS_JSON=$(printf '%s\n' "${packages[@]}" | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "packages=${FOLDERS_JSON}" >> "$GITHUB_OUTPUT"
deployment: deployment:
needs: set-package-matrix needs: [validate-release, set-package-matrix]
strategy: strategy:
matrix: matrix:
package-name: ${{ fromJSON(needs.set-package-matrix.outputs.packages) }} package-name: ${{ fromJSON(needs.set-package-matrix.outputs.packages) }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read
id-token: write id-token: write
environment: environment:
name: release name: release
url: https://pypi.org/p/${{ matrix.package-name }} url: https://pypi.org/p/${{ matrix.package-name }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
ref: main ref: main
persist-credentials: false
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
cache: pip cache: pip
cache-dependency-path: '**/pyproject.toml' cache-dependency-path: '**/pyproject.toml'
- name: Install dependencies - name: Install dependencies
run: | run: pip install setuptools wheel build
pip install setuptools wheel build
- name: Build - name: Build
env:
RELEASE_VERSION: ${{ needs.validate-release.outputs.release_version }}
PACKAGE_NAME: ${{ matrix.package-name }}
run: | run: |
echo ${{ inputs.releaseVersion }} >> ${{ matrix.package-name }}/.version printf '%s\n' "$RELEASE_VERSION" >> "${PACKAGE_NAME}/.version"
cp LICENSE NOTICE ${{ matrix.package-name }}/ cp LICENSE NOTICE "${PACKAGE_NAME}/"
python -m build ${{ matrix.package-name }} --outdir dist/ python -m build "${PACKAGE_NAME}" --outdir dist/
- name: Publish - name: Publish
uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@release/v1
with: with:
skip-existing: true skip-existing: true
add-tag: add-tag:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: deployment needs: [validate-release, deployment]
permissions:
contents: write
steps: steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Create GitHub Release - name: Create GitHub Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
tag_name: ${{ inputs.releaseVersion }} tag_name: ${{ needs.validate-release.outputs.release_version }}
prerelease: false prerelease: false
draft: false draft: false
generate_release_notes: true generate_release_notes: true