mirror of
https://github.com/actions/checkout.git
synced 2026-06-16 13:18:41 +07:00
block checking out fork pr for some events
This commit is contained in:
@@ -118,4 +118,10 @@ export interface IGitSourceSettings {
|
||||
* User override on the GitHub Server/Host URL that hosts the repository to be cloned
|
||||
*/
|
||||
githubServerUrl: string | undefined
|
||||
|
||||
/**
|
||||
* Opt-in to allow checking out fork pull request code from a workflow
|
||||
* triggered by pull_request_target or workflow_run.
|
||||
*/
|
||||
allowUnsafePrCheckout: boolean
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as core from '@actions/core'
|
||||
import * as fsHelper from './fs-helper'
|
||||
import * as github from '@actions/github'
|
||||
import * as path from 'path'
|
||||
import * as unsafePrCheckoutHelper from './unsafe-pr-checkout-helper'
|
||||
import * as workflowContextHelper from './workflow-context-helper'
|
||||
import {IGitSourceSettings} from './git-source-settings'
|
||||
|
||||
@@ -161,5 +162,18 @@ export async function getInputs(): Promise<IGitSourceSettings> {
|
||||
result.githubServerUrl = core.getInput('github-server-url')
|
||||
core.debug(`GitHub Host URL = ${result.githubServerUrl}`)
|
||||
|
||||
// Allow unsafe PR checkout (opt-in for pull_request_target / workflow_run fork PRs)
|
||||
result.allowUnsafePrCheckout =
|
||||
(core.getInput('allow-unsafe-pr-checkout') || 'false').toUpperCase() ===
|
||||
'TRUE'
|
||||
core.debug(`allow unsafe PR checkout = ${result.allowUnsafePrCheckout}`)
|
||||
|
||||
unsafePrCheckoutHelper.assertSafePrCheckout({
|
||||
qualifiedRepository,
|
||||
ref: result.ref,
|
||||
commit: result.commit,
|
||||
allowUnsafePrCheckout: result.allowUnsafePrCheckout
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
+1
-1
@@ -292,7 +292,7 @@ export async function checkCommitInfo(
|
||||
}
|
||||
}
|
||||
|
||||
function fromPayload(path: string): any {
|
||||
export function fromPayload(path: string): any {
|
||||
return select(github.context.payload, path)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import * as github from '@actions/github'
|
||||
import {fromPayload} from './ref-helper'
|
||||
|
||||
const PR_REF_PATTERN = /^refs\/pull\/[0-9]+\/(?:head|merge)$/
|
||||
|
||||
export interface IUnsafePrCheckoutInput {
|
||||
qualifiedRepository: string
|
||||
ref: string
|
||||
commit: string
|
||||
allowUnsafePrCheckout: boolean
|
||||
}
|
||||
|
||||
export function assertSafePrCheckout(input: IUnsafePrCheckoutInput): void {
|
||||
if (input.allowUnsafePrCheckout) {
|
||||
return
|
||||
}
|
||||
|
||||
const eventName = github.context.eventName
|
||||
if (eventName !== 'pull_request_target' && eventName !== 'workflow_run') {
|
||||
return
|
||||
}
|
||||
|
||||
const baseRepoId = fromPayload('repository.id')
|
||||
if (typeof baseRepoId !== 'number') {
|
||||
return
|
||||
}
|
||||
|
||||
let prHeadRepoId: unknown
|
||||
const prShas: string[] = []
|
||||
|
||||
if (eventName === 'pull_request_target') {
|
||||
prHeadRepoId = fromPayload('pull_request.head.repo.id')
|
||||
pushIfSha(prShas, fromPayload('pull_request.head.sha'))
|
||||
pushIfSha(prShas, fromPayload('pull_request.merge_commit_sha'))
|
||||
} else {
|
||||
const wrEvent = fromPayload('workflow_run.event')
|
||||
if (typeof wrEvent !== 'string' || !wrEvent.startsWith('pull_request')) {
|
||||
return
|
||||
}
|
||||
prHeadRepoId = fromPayload('workflow_run.head_repository.id')
|
||||
pushIfSha(prShas, fromPayload('workflow_run.head_commit.id'))
|
||||
}
|
||||
|
||||
// (A) Fork PR?
|
||||
if (typeof prHeadRepoId !== 'number' || prHeadRepoId === baseRepoId) {
|
||||
return
|
||||
}
|
||||
|
||||
// (B) We cannot check for all fork PR refs so check to see
|
||||
// if the resolved input points to the fork PR sha we have in the payload
|
||||
const baseQualifiedRepository = `${github.context.repo.owner}/${github.context.repo.repo}`
|
||||
const repositoryDiffersFromBase =
|
||||
input.qualifiedRepository.toLowerCase() !==
|
||||
baseQualifiedRepository.toLowerCase()
|
||||
const refMatchesPullPattern = PR_REF_PATTERN.test(input.ref)
|
||||
const commitMatchesPrHeadSha =
|
||||
!!input.commit && prShas.includes(input.commit.toLowerCase())
|
||||
|
||||
if (
|
||||
!repositoryDiffersFromBase &&
|
||||
!refMatchesPullPattern &&
|
||||
!commitMatchesPrHeadSha
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Refusing to check out fork pull request code from a '${eventName}' workflow. ` +
|
||||
`This workflow runs with the base repository's GITHUB_TOKEN, secrets, default-branch ` +
|
||||
`cache scope, and runner access. Fetching fork's code in that trusted context is a ` +
|
||||
`"pwn request" supply-chain attack pattern. To opt in after reviewing the risk, set ` +
|
||||
`'allow-unsafe-pr-checkout: true' on the actions/checkout step.`
|
||||
)
|
||||
}
|
||||
|
||||
function pushIfSha(target: string[], value: unknown): void {
|
||||
if (typeof value === 'string' && value.length > 0) {
|
||||
target.push(value.toLowerCase())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user