mirror of
https://github.com/actions/checkout.git
synced 2026-05-13 16:38:07 +00:00
feat: warn on non-default checkout during pull_request_target
Signed-off-by: Kengo TODA <skypencil@gmail.com>
This commit is contained in:
parent
900f2210b1
commit
5a3004714a
@ -160,6 +160,13 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
|
|||||||
# running from unless specified. Example URLs are https://github.com or
|
# running from unless specified. Example URLs are https://github.com or
|
||||||
# https://my-ghes-server.example.com
|
# https://my-ghes-server.example.com
|
||||||
github-server-url: ''
|
github-server-url: ''
|
||||||
|
|
||||||
|
# Suppress the warning when pull_request_target checks out a non-default branch
|
||||||
|
# from the workflow repository. Only set this to true when you understand the
|
||||||
|
# security risk of running untrusted pull request code in a privileged context.
|
||||||
|
# https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
|
||||||
|
# Default: false
|
||||||
|
dangerously-checkout-non-default-branch: ''
|
||||||
```
|
```
|
||||||
<!-- end usage -->
|
<!-- end usage -->
|
||||||
|
|
||||||
|
|||||||
@ -55,6 +55,15 @@ describe('input-helper tests', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Reset inputs
|
// Reset inputs
|
||||||
inputs = {}
|
inputs = {}
|
||||||
|
github.context.eventName = 'push'
|
||||||
|
github.context.ref = 'refs/heads/some-ref'
|
||||||
|
github.context.sha = '1234567890123456789012345678901234567890'
|
||||||
|
github.context.payload = {
|
||||||
|
repository: {
|
||||||
|
default_branch: 'main'
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
@ -65,6 +74,8 @@ describe('input-helper tests', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restore @actions/github context
|
// Restore @actions/github context
|
||||||
|
github.context.eventName = originalContext.eventName
|
||||||
|
github.context.payload = originalContext.payload
|
||||||
github.context.ref = originalContext.ref
|
github.context.ref = originalContext.ref
|
||||||
github.context.sha = originalContext.sha
|
github.context.sha = originalContext.sha
|
||||||
|
|
||||||
@ -150,6 +161,75 @@ describe('input-helper tests', () => {
|
|||||||
expect(settings.commit).toBeFalsy()
|
expect(settings.commit).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('warns when pull_request_target checks out a non-default branch', async () => {
|
||||||
|
github.context.eventName = 'pull_request_target'
|
||||||
|
inputs.ref = 'some-other-ref'
|
||||||
|
|
||||||
|
await inputHelper.getInputs()
|
||||||
|
|
||||||
|
expect(core.warning).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining(
|
||||||
|
'Checking out a non-default branch from pull_request_target'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not warn when pull_request_target checks out the default branch name', async () => {
|
||||||
|
github.context.eventName = 'pull_request_target'
|
||||||
|
inputs.ref = 'main'
|
||||||
|
|
||||||
|
await inputHelper.getInputs()
|
||||||
|
|
||||||
|
expect(core.warning).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not warn when pull_request_target checks out the fully qualified default branch', async () => {
|
||||||
|
github.context.eventName = 'pull_request_target'
|
||||||
|
inputs.ref = 'refs/heads/main'
|
||||||
|
|
||||||
|
await inputHelper.getInputs()
|
||||||
|
|
||||||
|
expect(core.warning).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not warn when pull_request_target checks out the default branch sha', async () => {
|
||||||
|
github.context.eventName = 'pull_request_target'
|
||||||
|
inputs.ref = '1234567890123456789012345678901234567890'
|
||||||
|
|
||||||
|
await inputHelper.getInputs()
|
||||||
|
|
||||||
|
expect(core.warning).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not warn when dangerously-checkout-non-default-branch suppresses the warning', async () => {
|
||||||
|
github.context.eventName = 'pull_request_target'
|
||||||
|
inputs.ref = 'some-other-ref'
|
||||||
|
inputs['dangerously-checkout-non-default-branch'] = 'true'
|
||||||
|
|
||||||
|
await inputHelper.getInputs()
|
||||||
|
|
||||||
|
expect(core.warning).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not warn when pull_request checks out a non-default branch', async () => {
|
||||||
|
github.context.eventName = 'pull_request'
|
||||||
|
inputs.ref = 'some-other-ref'
|
||||||
|
|
||||||
|
await inputHelper.getInputs()
|
||||||
|
|
||||||
|
expect(core.warning).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not warn when pull_request_target checks out a different repository', async () => {
|
||||||
|
github.context.eventName = 'pull_request_target'
|
||||||
|
inputs.repository = 'some-owner/some-other-repo'
|
||||||
|
inputs.ref = 'some-other-ref'
|
||||||
|
|
||||||
|
await inputHelper.getInputs()
|
||||||
|
|
||||||
|
expect(core.warning).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
it('sets workflow organization ID', async () => {
|
it('sets workflow organization ID', async () => {
|
||||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||||
expect(settings.workflowOrganizationId).toBe(123456)
|
expect(settings.workflowOrganizationId).toBe(123456)
|
||||||
|
|||||||
@ -98,6 +98,14 @@ inputs:
|
|||||||
github-server-url:
|
github-server-url:
|
||||||
description: The base URL for the GitHub instance that you are trying to clone from, will use environment defaults to fetch from the same instance that the workflow is running from unless specified. Example URLs are https://github.com or https://my-ghes-server.example.com
|
description: The base URL for the GitHub instance that you are trying to clone from, will use environment defaults to fetch from the same instance that the workflow is running from unless specified. Example URLs are https://github.com or https://my-ghes-server.example.com
|
||||||
required: false
|
required: false
|
||||||
|
dangerously-checkout-non-default-branch:
|
||||||
|
description: >
|
||||||
|
Suppress the warning when pull_request_target checks out a non-default
|
||||||
|
branch from the workflow repository. Only set this to true when you
|
||||||
|
understand the security risk of running untrusted pull request code in a
|
||||||
|
privileged context.
|
||||||
|
https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
|
||||||
|
default: false
|
||||||
outputs:
|
outputs:
|
||||||
ref:
|
ref:
|
||||||
description: 'The branch, tag or SHA that was checked out'
|
description: 'The branch, tag or SHA that was checked out'
|
||||||
|
|||||||
22
dist/index.js
vendored
22
dist/index.js
vendored
@ -2008,7 +2008,8 @@ function getInputs() {
|
|||||||
const isWorkflowRepository = qualifiedRepository.toUpperCase() ===
|
const isWorkflowRepository = qualifiedRepository.toUpperCase() ===
|
||||||
`${github.context.repo.owner}/${github.context.repo.repo}`.toUpperCase();
|
`${github.context.repo.owner}/${github.context.repo.repo}`.toUpperCase();
|
||||||
// Source branch, source version
|
// Source branch, source version
|
||||||
result.ref = core.getInput('ref');
|
const inputRef = core.getInput('ref');
|
||||||
|
result.ref = inputRef;
|
||||||
if (!result.ref) {
|
if (!result.ref) {
|
||||||
if (isWorkflowRepository) {
|
if (isWorkflowRepository) {
|
||||||
result.ref = github.context.ref;
|
result.ref = github.context.ref;
|
||||||
@ -2027,6 +2028,16 @@ function getInputs() {
|
|||||||
}
|
}
|
||||||
core.debug(`ref = '${result.ref}'`);
|
core.debug(`ref = '${result.ref}'`);
|
||||||
core.debug(`commit = '${result.commit}'`);
|
core.debug(`commit = '${result.commit}'`);
|
||||||
|
// Warn when pull_request_target checks out non-default code from the workflow repository.
|
||||||
|
// This event runs in the base repository context, so checking out PR-controlled code can be risky.
|
||||||
|
const suppressNonDefaultBranchWarning = (core.getInput('dangerously-checkout-non-default-branch') || 'false').toUpperCase() === 'TRUE';
|
||||||
|
if (github.context.eventName === 'pull_request_target' &&
|
||||||
|
isWorkflowRepository &&
|
||||||
|
inputRef &&
|
||||||
|
!suppressNonDefaultBranchWarning &&
|
||||||
|
!isDefaultBranchRef(inputRef)) {
|
||||||
|
core.warning('Checking out a non-default branch from pull_request_target can put untrusted pull request code in a privileged context. If this is intentional, set dangerously-checkout-non-default-branch: true. Consider using pull_request or pull_request plus workflow_run instead. See https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/');
|
||||||
|
}
|
||||||
// Clean
|
// Clean
|
||||||
result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE';
|
result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE';
|
||||||
core.debug(`clean = ${result.clean}`);
|
core.debug(`clean = ${result.clean}`);
|
||||||
@ -2098,6 +2109,15 @@ function getInputs() {
|
|||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function isDefaultBranchRef(ref) {
|
||||||
|
var _a;
|
||||||
|
const defaultBranch = (_a = github.context.payload.repository) === null || _a === void 0 ? void 0 : _a.default_branch;
|
||||||
|
if (defaultBranch &&
|
||||||
|
(ref === defaultBranch || ref === `refs/heads/${defaultBranch}`)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return ref.toUpperCase() === github.context.sha.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|||||||
@ -57,7 +57,8 @@ export async function getInputs(): Promise<IGitSourceSettings> {
|
|||||||
`${github.context.repo.owner}/${github.context.repo.repo}`.toUpperCase()
|
`${github.context.repo.owner}/${github.context.repo.repo}`.toUpperCase()
|
||||||
|
|
||||||
// Source branch, source version
|
// Source branch, source version
|
||||||
result.ref = core.getInput('ref')
|
const inputRef = core.getInput('ref')
|
||||||
|
result.ref = inputRef
|
||||||
if (!result.ref) {
|
if (!result.ref) {
|
||||||
if (isWorkflowRepository) {
|
if (isWorkflowRepository) {
|
||||||
result.ref = github.context.ref
|
result.ref = github.context.ref
|
||||||
@ -78,6 +79,24 @@ export async function getInputs(): Promise<IGitSourceSettings> {
|
|||||||
core.debug(`ref = '${result.ref}'`)
|
core.debug(`ref = '${result.ref}'`)
|
||||||
core.debug(`commit = '${result.commit}'`)
|
core.debug(`commit = '${result.commit}'`)
|
||||||
|
|
||||||
|
// Warn when pull_request_target checks out non-default code from the workflow repository.
|
||||||
|
// This event runs in the base repository context, so checking out PR-controlled code can be risky.
|
||||||
|
const suppressNonDefaultBranchWarning =
|
||||||
|
(
|
||||||
|
core.getInput('dangerously-checkout-non-default-branch') || 'false'
|
||||||
|
).toUpperCase() === 'TRUE'
|
||||||
|
if (
|
||||||
|
github.context.eventName === 'pull_request_target' &&
|
||||||
|
isWorkflowRepository &&
|
||||||
|
inputRef &&
|
||||||
|
!suppressNonDefaultBranchWarning &&
|
||||||
|
!isDefaultBranchRef(inputRef)
|
||||||
|
) {
|
||||||
|
core.warning(
|
||||||
|
'Checking out a non-default branch from pull_request_target can put untrusted pull request code in a privileged context. If this is intentional, set dangerously-checkout-non-default-branch: true. Consider using pull_request or pull_request plus workflow_run instead. See https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Clean
|
// Clean
|
||||||
result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE'
|
result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE'
|
||||||
core.debug(`clean = ${result.clean}`)
|
core.debug(`clean = ${result.clean}`)
|
||||||
@ -163,3 +182,16 @@ export async function getInputs(): Promise<IGitSourceSettings> {
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDefaultBranchRef(ref: string): boolean {
|
||||||
|
const defaultBranch = (github.context.payload.repository as any)
|
||||||
|
?.default_branch
|
||||||
|
if (
|
||||||
|
defaultBranch &&
|
||||||
|
(ref === defaultBranch || ref === `refs/heads/${defaultBranch}`)
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref.toUpperCase() === github.context.sha.toUpperCase()
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user