mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
fix(ci): run PR LoC scripts from the default branch, not PR head (#15016)
The PR test LoC job fetched .github/scripts/pr-test-loc-*.mjs from pull/<n>/head and ran them with node while holding a GITHUB_TOKEN scoped pull-requests: write, so PR-authored code executed under a write token. Pin the fetch to the repository default branch. base.sha is not enough: for stacked PRs it is an unreviewed feature-branch commit any collaborator can push to, while main is gated by branch protection. Also pass event data via env instead of shell interpolation, and add set -euo pipefail so a failed download cannot leave a truncated script.
This commit is contained in:
@@ -23,14 +23,20 @@ jobs:
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
# Why no checkout: the Files API already has per-file additions/deletions.
|
||||
# Why the default branch and never pull/<n>/head: this job holds a write-scoped
|
||||
# GITHUB_TOKEN, so it may only execute reviewed code. A PR that edits these
|
||||
# scripts takes effect once merged.
|
||||
- name: Count test vs non-test LoC
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
TRUSTED_REF: ${{ github.event.repository.default_branch }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for script in pr-test-loc-table.mjs pr-test-loc-summary.mjs; do
|
||||
gh api "repos/${GITHUB_REPOSITORY}/contents/.github/scripts/${script}?ref=pull/${{ github.event.pull_request.number }}/head" \
|
||||
gh api "repos/${GITHUB_REPOSITORY}/contents/.github/scripts/${script}?ref=${TRUSTED_REF}" \
|
||||
--jq .content | base64 --decode > "$RUNNER_TEMP/${script}"
|
||||
done
|
||||
node "$RUNNER_TEMP/pr-test-loc-summary.mjs" --update-pr "${{ github.event.pull_request.number }}"
|
||||
node "$RUNNER_TEMP/pr-test-loc-summary.mjs" --update-pr "$PR_NUMBER"
|
||||
|
||||
@@ -15,6 +15,11 @@ import {
|
||||
|
||||
const projectDir = resolve(import.meta.dirname, '../..')
|
||||
const locScript = join(projectDir, '.github/scripts/pr-test-loc-summary.mjs')
|
||||
const locWorkflow = parse(
|
||||
readFileSync(join(projectDir, '.github/workflows/pr-test-loc.yml'), 'utf8')
|
||||
)
|
||||
const locJob = locWorkflow.jobs.loc
|
||||
const locStep = locJob.steps[0]
|
||||
const tempDirs = []
|
||||
|
||||
function runLoc(args, { env } = {}) {
|
||||
@@ -178,20 +183,36 @@ describe('PR test LoC summary', () => {
|
||||
})
|
||||
|
||||
it('is a no-checkout GitHub-hosted PR workflow', () => {
|
||||
const workflow = parse(
|
||||
readFileSync(join(projectDir, '.github/workflows/pr-test-loc.yml'), 'utf8')
|
||||
)
|
||||
const locJob = workflow.jobs.loc
|
||||
const serialized = JSON.stringify(workflow)
|
||||
|
||||
expect(locJob['runs-on']).toBe('ubuntu-latest')
|
||||
expect(locJob.steps).toHaveLength(1)
|
||||
expect(locJob.steps[0].run).toContain('gh api')
|
||||
expect(locJob.steps[0].run).toContain('pr-test-loc-table.mjs')
|
||||
expect(locJob.steps[0].run).toContain('pr-test-loc-summary.mjs')
|
||||
expect(locJob.steps[0].run).toContain('--update-pr')
|
||||
expect(workflow.permissions['pull-requests']).toBe('write')
|
||||
expect(serialized).not.toContain('actions/checkout')
|
||||
expect(serialized).not.toContain('self-hosted')
|
||||
expect(locStep.run).toContain('gh api')
|
||||
expect(locStep.run).toContain('pr-test-loc-table.mjs')
|
||||
expect(locStep.run).toContain('pr-test-loc-summary.mjs')
|
||||
expect(locStep.run).toContain('--update-pr')
|
||||
expect(locWorkflow.permissions['pull-requests']).toBe('write')
|
||||
expect(JSON.stringify(locWorkflow)).not.toContain('actions/checkout')
|
||||
expect(JSON.stringify(locWorkflow)).not.toContain('self-hosted')
|
||||
})
|
||||
|
||||
// The write-scoped GITHUB_TOKEN makes any PR-authored code a privilege escalation.
|
||||
it('executes only default-branch script code, never pull-request head code', () => {
|
||||
const serialized = JSON.stringify(locWorkflow)
|
||||
|
||||
expect(locStep.env.TRUSTED_REF).toBe('${{ github.event.repository.default_branch }}')
|
||||
expect(locStep.run).toContain('?ref=${TRUSTED_REF}')
|
||||
expect(serialized).not.toContain('pull_request_target')
|
||||
expect(serialized).not.toContain('pull/')
|
||||
expect(serialized).not.toContain('pull_request.head')
|
||||
expect(serialized).not.toContain('/merge')
|
||||
})
|
||||
|
||||
it('passes event data through env instead of interpolating it into the shell', () => {
|
||||
expect(locStep.env.PR_NUMBER).toBe('${{ github.event.pull_request.number }}')
|
||||
expect(locStep.run).toContain('--update-pr "$PR_NUMBER"')
|
||||
expect(locStep.run).not.toContain('${{')
|
||||
})
|
||||
|
||||
it('fails the step when a script download fails instead of running a truncated file', () => {
|
||||
expect(locStep.run).toContain('set -euo pipefail')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user