Repository navigation
fix(ci): require write access and in-repo branch for /test-staging #1920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,35 +19,69 @@ on: | |
| required: false | ||
| default: '' | ||
|
|
||
| # Default-deny; the job opts in to what it needs. | ||
| permissions: {} | ||
|
|
||
| concurrency: | ||
| group: staging-tests | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| staging-tests: | ||
| name: Staging tests | ||
| # Cheap pre-filter only; the real authorization is the first step below. | ||
| # author_association is not used as the gate because private org members | ||
| # show up as CONTRIBUTOR, which would lock out legitimate maintainers. | ||
| # The label trigger is safe as is: pull_request runs from forks get no | ||
| # secrets, and adding a label requires triage access. | ||
| if: >- | ||
| github.event_name == 'workflow_dispatch' || | ||
| (github.event_name == 'pull_request' && github.event.label.name == 'test-staging') || | ||
| (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '/test-staging')) | ||
| (github.event_name == 'issue_comment' && github.event.issue.pull_request && github.event.issue.state == 'open' && contains(github.event.comment.body, '/test-staging')) | ||
| runs-on: | ||
| group: infra1-runners-arc | ||
| labels: runners-small | ||
| permissions: | ||
| contents: read | ||
| pull-requests: read | ||
| steps: | ||
| - name: Get PR head SHA (comment trigger) | ||
| # SECURITY: anyone can comment on a PR in a public repo, and this job runs | ||
| # PR code with PYTHON_SDK_STG_API_KEY on self-hosted runners. Before any | ||
| # checkout, require (1) write access for the commenter and (2) a PR head | ||
| # branch in this repository, not a fork. (2) closes the race where an | ||
| # outsider pushes new commits to their fork after a maintainer comments: | ||
| # only users with write access can push to in-repo branches. | ||
| - name: Authorize comment trigger | ||
| if: github.event_name == 'issue_comment' | ||
| id: pr | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| REPO: ${{ github.repository }} | ||
| PR_NUMBER: ${{ github.event.issue.number }} | ||
| COMMENTER: ${{ github.event.comment.user.login }} | ||
| run: | | ||
| PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}) | ||
| PERMISSION=$(gh api "repos/$REPO/collaborators/$COMMENTER/permission" --jq .permission 2>/dev/null || echo none) | ||
| case "$PERMISSION" in | ||
| admin|maintain|write) ;; | ||
| *) | ||
| echo "::error::User '$COMMENTER' has '$PERMISSION' permission; write access is required to run staging tests." | ||
| exit 1 | ||
| ;; | ||
| esac | ||
|
|
||
| PR_DATA=$(gh api "repos/$REPO/pulls/$PR_NUMBER") | ||
| HEAD_REPO=$(echo "$PR_DATA" | jq -r .head.repo.full_name) | ||
| if [ "$HEAD_REPO" != "$REPO" ]; then | ||
| echo "::error::PR head is in '$HEAD_REPO'. Staging tests only run on branches of $REPO; push the branch there to test it." | ||
| exit 1 | ||
| fi | ||
| echo "sha=$(echo "$PR_DATA" | jq -r .head.sha)" >> "$GITHUB_OUTPUT" | ||
| echo "ref=$(echo "$PR_DATA" | jq -r .head.ref)" >> "$GITHUB_OUTPUT" | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Checkout | ||
| uses: actions/checkout@v5 | ||
| with: | ||
| ref: ${{ steps.pr.outputs.sha || github.event.pull_request.head.sha || github.sha }} | ||
| persist-credentials: false | ||
|
|
||
| - name: Set up Python | ||
| uses: astral-sh/setup-uv@v10.0.1 | ||
|
|
@@ -72,9 +106,11 @@ jobs: | |
| TOKEN: ${{ secrets.PYTHON_SDK_STG_API_KEY }} | ||
|
|
||
| - name: Run staging tests | ||
| # Inputs are passed through env, not interpolated into the script, so | ||
| # they cannot inject shell commands. | ||
| run: | | ||
| make test-staging \ | ||
| TEST_ENVS=${{ github.event.inputs.test_envs || 'py314' }} \ | ||
| ADD_ARGS="${{ github.event.inputs.test_filter && format('-k {0}', github.event.inputs.test_filter) || '' }}" | ||
| make test-staging TEST_ENVS="$TEST_ENVS" ADD_ARGS="${TEST_FILTER:+-k $TEST_FILTER}" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| env: | ||
| TOKEN: ${{ secrets.PYTHON_SDK_STG_API_KEY }} | ||
| TEST_ENVS: ${{ github.event.inputs.test_envs || 'py314' }} | ||
| TEST_FILTER: ${{ github.event.inputs.test_filter }} | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
sed -n '1,130p' .github/workflows/staging-tests.yaml git diff 8bca570b6adb70b923651a3fa10df911ed74445a HEAD -- .github/workflows/staging-tests.yamlRepository: gooddata/gooddata-python-sdk
Length of output: 9024
Script executed:
Repository: gooddata/gooddata-python-sdk
Length of output: 9504
Protect the self-hosted runner from fork code on the labeled path.
Adding
test-stagingis an authorization signal, but it is not a head-repository check or proof of runner isolation. The labeled path checks out the fork head and runs it oninfra1-runners-arc/runners-small. Fork pull-request runs do not receive repository secrets, so this path does not establishPYTHON_SDK_STG_API_KEYexposure, but it still executes untrusted code on self-hosted infrastructure. Reject fork heads on this path, or route labeled fork requests to a runner isolated from trusted infrastructure.