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:
Neil
2026-08-16 22:18:50 -07:00
committed by GitHub
parent 8ca4ed945e
commit 08bf209e40
2 changed files with 42 additions and 15 deletions
+34 -13
View File
@@ -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')
})
})