Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions __test__/input-helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,20 @@ const mockGithubContext: any = {
payload: {}
}

// Replicate @actions/core getInput behavior: it trims whitespace by default
// (String.prototype.trim(), which strips characters such as a leading U+FEFF BOM)
// unless trimWhitespace is explicitly set to false.
const getInputImpl = (name: string, options?: {trimWhitespace?: boolean}) => {
const val = inputs[name] ?? ''
if (options && options.trimWhitespace === false) {
return val
}
return typeof val === 'string' ? val.trim() : val
}
Comment thread
aiqiaoy marked this conversation as resolved.

// Mock @actions/core before loading input-helper
jest.unstable_mockModule('@actions/core', () => ({
getInput: jest.fn((name: string) => inputs[name]),
getInput: jest.fn(getInputImpl),
getBooleanInput: jest.fn((name: string) => inputs[name]),
getMultilineInput: jest.fn((name: string) =>
inputs[name] ? String(inputs[name]).split('\n').filter(Boolean) : []
Expand Down Expand Up @@ -76,9 +87,7 @@ describe('input-helper tests', () => {
inputs = {}
jest.clearAllMocks()
// Re-apply default mocks
;(core.getInput as jest.Mock<any>).mockImplementation(
(name: string) => inputs[name]
)
;(core.getInput as jest.Mock<any>).mockImplementation(getInputImpl as any)
mockDirectoryExistsSync.mockImplementation(
(p: string) => p === gitHubWorkspace
)
Expand Down Expand Up @@ -176,6 +185,36 @@ describe('input-helper tests', () => {
expect(settings.commit).toBeFalsy()
})

it('does not reclassify a ref as sha when a BOM is prefixed', async () => {
// A fork branch named "<U+FEFF>" + 40 hex chars. core.getInput trims the
// BOM by default, which previously collapsed this into a bare SHA and
// bypassed the unsafe fork PR checkout guard.
inputs.ref = '\uFEFF522d932fae5296da51fdf431934425ecf891c6a2'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.commit).toBeFalsy()
expect(settings.ref).toBe('522d932fae5296da51fdf431934425ecf891c6a2')
})

it('does not reclassify a sha-256 ref as sha when a BOM is prefixed', async () => {
inputs.ref =
'\uFEFF1111111111222222222233333333334444444444555555555566666666667777'
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.commit).toBeFalsy()
expect(settings.ref).toBe(
'1111111111222222222233333333334444444444555555555566666666667777'
)
})

it('treats a sha surrounded by ascii whitespace as a commit', async () => {
// ASCII whitespace can only come from the workflow author's YAML (git ref
// names cannot contain it), so trimming it and treating the value as a
// commit is safe.
inputs.ref = ' 1111111111222222222233333333334444444444 '
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.ref).toBeFalsy()
expect(settings.commit).toBe('1111111111222222222233333333334444444444')
})

it('sets workflow organization ID', async () => {
const settings: IGitSourceSettings = await inputHelper.getInputs()
expect(settings.workflowOrganizationId).toBe(123456)
Expand Down
20 changes: 18 additions & 2 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42100,6 +42100,22 @@ async function getInputs() {
`${github_context.repo.owner}/${github_context.repo.repo}`.toUpperCase();
// Source branch, source version
result.ref = getInput('ref');
// core.getInput()'s default trim strips a range of Unicode characters such as a
// leading BOM (U+FEFF) or NBSP (U+00A0). Those are valid in a git ref name, so
// a fork branch named "<BOM>" + 40 hex chars would trim down to a bare SHA and
// be silently reclassified as a commit, bypassing the unsafe fork PR checkout
// guard.
//
// The trim below strips only the ASCII whitespace characters which are all forbidden
// in a git branch name.
// \t U+0009 horizontal tab - ASCII control, forbidden in ref names
// \n U+000A line feed - ASCII control, forbidden in ref names
// \v U+000B vertical tab - ASCII control, forbidden in ref names
// \f U+000C form feed - ASCII control, forbidden in ref names
// \r U+000D carriage return - ASCII control, forbidden in ref names
// ' ' U+0020 space - forbidden in ref names
const asciiTrimmedRef = getInput('ref', { trimWhitespace: false })
.replace(/^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g, '');
if (!result.ref) {
if (isWorkflowRepository) {
result.ref = github_context.ref;
Expand All @@ -42112,8 +42128,8 @@ async function getInputs() {
}
}
// SHA?
else if (result.ref.match(/^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/)) {
result.commit = result.ref;
else if (asciiTrimmedRef.match(/^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/)) {
result.commit = asciiTrimmedRef;
result.ref = '';
}
core_debug(`ref = '${result.ref}'`);
Expand Down
21 changes: 19 additions & 2 deletions src/input-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,23 @@ export async function getInputs(): Promise<IGitSourceSettings> {

// Source branch, source version
result.ref = core.getInput('ref')
// core.getInput()'s default trim strips a range of Unicode characters such as a
// leading BOM (U+FEFF) or NBSP (U+00A0). Those are valid in a git ref name, so
// a fork branch named "<BOM>" + 40 hex chars would trim down to a bare SHA and
// be silently reclassified as a commit, bypassing the unsafe fork PR checkout
// guard.
//
// The trim below strips only the ASCII whitespace characters which are all forbidden
// in a git branch name.
// \t U+0009 horizontal tab - ASCII control, forbidden in ref names
// \n U+000A line feed - ASCII control, forbidden in ref names
// \v U+000B vertical tab - ASCII control, forbidden in ref names
// \f U+000C form feed - ASCII control, forbidden in ref names
// \r U+000D carriage return - ASCII control, forbidden in ref names
// ' ' U+0020 space - forbidden in ref names
const asciiTrimmedRef = core
.getInput('ref', {trimWhitespace: false})
.replace(/^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g, '')
Comment thread
aiqiaoy marked this conversation as resolved.
if (!result.ref) {
if (isWorkflowRepository) {
result.ref = github.context.ref
Expand All @@ -72,8 +89,8 @@ export async function getInputs(): Promise<IGitSourceSettings> {
}
}
// SHA?
else if (result.ref.match(/^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/)) {
result.commit = result.ref
else if (asciiTrimmedRef.match(/^(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})$/)) {
result.commit = asciiTrimmedRef
result.ref = ''
}
core.debug(`ref = '${result.ref}'`)
Expand Down
Loading