Read-only mirror — the public repositories of the Forgejo instance, refreshed every 10 minutes. The live instance (fg.dieschoenewolke.de) is reachable only over the VPN; clone here with https://git.dieschoenewolke.de/git/<owner>/<repo>.git.
summaryrefslogtreecommitdiff
path: root/__test__
diff options
context:
space:
mode:
authorAiqiao Yan <55104035+aiqiaoy@users.noreply.github.com>2026-07-07 16:34:55 -0400
committerGitHub <noreply@github.com>2026-07-07 16:34:55 -0400
commit93e494ef84ba8e3de98da2927beaf09ecfa946d7 (patch)
treeb4ab56a121f9ee246f6c56289431268452a7f909 /__test__
parentf97ee346754f209980bd9c4d3ff3f84e84381a9d (diff)
backport `allow-unsafe-pr-checkout` to v5 (#2501)
* block checking out fork pr for pull_request_target and workflow_run (#2454) * block checking out fork pr for some events * address copilot and reviewer feedback * run prettier formatting * build * update urls * update readme * update description and url again * edit url one more time * update error wording (#2467)
Diffstat (limited to '__test__')
-rw-r--r--__test__/git-auth-helper.test.ts3
-rw-r--r--__test__/input-helper.test.ts1
-rw-r--r--__test__/unsafe-pr-checkout-helper.test.ts267
3 files changed, 270 insertions, 1 deletions
diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts
index 9acba54..92a61fe 100644
--- a/__test__/git-auth-helper.test.ts
+++ b/__test__/git-auth-helper.test.ts
@@ -1113,7 +1113,8 @@ async function setup(testName: string): Promise<void> {
sshUser: '',
workflowOrganizationId: 123456,
setSafeDirectory: true,
- githubServerUrl: githubServerUrl
+ githubServerUrl: githubServerUrl,
+ allowUnsafePrCheckout: false
}
}
diff --git a/__test__/input-helper.test.ts b/__test__/input-helper.test.ts
index 9514cb4..2b7e918 100644
--- a/__test__/input-helper.test.ts
+++ b/__test__/input-helper.test.ts
@@ -91,6 +91,7 @@ describe('input-helper tests', () => {
expect(settings.repositoryOwner).toBe('some-owner')
expect(settings.repositoryPath).toBe(gitHubWorkspace)
expect(settings.setSafeDirectory).toBe(true)
+ expect(settings.allowUnsafePrCheckout).toBe(false)
})
it('qualifies ref', async () => {
diff --git a/__test__/unsafe-pr-checkout-helper.test.ts b/__test__/unsafe-pr-checkout-helper.test.ts
new file mode 100644
index 0000000..9efa246
--- /dev/null
+++ b/__test__/unsafe-pr-checkout-helper.test.ts
@@ -0,0 +1,267 @@
+import * as github from '@actions/github'
+import {assertSafePrCheckout} from '../lib/unsafe-pr-checkout-helper'
+
+// Shallow clone original @actions/github context
+const originalContext = {...github.context}
+const originalEventName = github.context.eventName
+const originalPayload = github.context.payload
+
+const BASE_REPO_ID = 100
+const FORK_REPO_ID = 200
+const PR_HEAD_SHA = '1111111111111111111111111111111111111111'
+const PR_MERGE_SHA = '2222222222222222222222222222222222222222'
+const SAFE_BASE_SHA = '3333333333333333333333333333333333333333'
+const WORKFLOW_RUN_HEAD_COMMIT_SHA = '4444444444444444444444444444444444444444'
+const BASE_QUALIFIED_REPO = 'some-owner/some-repo'
+const FORK_QUALIFIED_REPO = 'another-repo/fork'
+
+function setContext(eventName: string, payload: object): void {
+ ;(github.context as {eventName: string}).eventName = eventName
+ ;(github.context as {payload: object}).payload = payload
+}
+
+function forkPullRequestTargetPayload(): object {
+ return {
+ repository: {id: BASE_REPO_ID},
+ pull_request: {
+ head: {
+ sha: PR_HEAD_SHA,
+ repo: {id: FORK_REPO_ID, full_name: FORK_QUALIFIED_REPO}
+ },
+ merge_commit_sha: PR_MERGE_SHA
+ }
+ }
+}
+
+function sameRepoPullRequestTargetPayload(): object {
+ return {
+ repository: {id: BASE_REPO_ID},
+ pull_request: {
+ head: {
+ sha: PR_HEAD_SHA,
+ repo: {id: BASE_REPO_ID, full_name: BASE_QUALIFIED_REPO}
+ },
+ merge_commit_sha: PR_MERGE_SHA
+ }
+ }
+}
+
+function forkWorkflowRunPayload(): object {
+ return {
+ repository: {id: BASE_REPO_ID},
+ workflow_run: {
+ event: 'pull_request',
+ head_commit: {id: WORKFLOW_RUN_HEAD_COMMIT_SHA},
+ head_repository: {id: FORK_REPO_ID, full_name: FORK_QUALIFIED_REPO}
+ }
+ }
+}
+
+describe('unsafe-pr-checkout-helper', () => {
+ beforeAll(() => {
+ jest.spyOn(github.context, 'repo', 'get').mockReturnValue({
+ owner: 'some-owner',
+ repo: 'some-repo'
+ })
+ })
+
+ afterEach(() => {
+ ;(github.context as {eventName: string}).eventName = originalEventName
+ ;(github.context as {payload: object}).payload = originalPayload
+ })
+
+ afterAll(() => {
+ ;(github.context as {eventName: string}).eventName =
+ originalContext.eventName
+ ;(github.context as {payload: object}).payload = originalContext.payload
+ jest.restoreAllMocks()
+ })
+
+ it('allows pull_request events untouched', () => {
+ setContext('pull_request', forkPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: 'attacker/fork',
+ ref: 'refs/pull/1/merge',
+ commit: '',
+ allowUnsafePrCheckout: false
+ })
+ ).not.toThrow()
+ })
+
+ it('allows pull_request_target default checkout (base branch)', () => {
+ setContext('pull_request_target', forkPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: BASE_QUALIFIED_REPO,
+ ref: 'refs/heads/main',
+ commit: SAFE_BASE_SHA,
+ allowUnsafePrCheckout: false
+ })
+ ).not.toThrow()
+ })
+
+ it('allows same-repo pull_request_target checkout of PR head', () => {
+ setContext('pull_request_target', sameRepoPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: BASE_QUALIFIED_REPO,
+ ref: '',
+ commit: PR_HEAD_SHA,
+ allowUnsafePrCheckout: false
+ })
+ ).not.toThrow()
+ })
+
+ it('refuses pull_request_target fork PR head SHA checkout', () => {
+ setContext('pull_request_target', forkPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: BASE_QUALIFIED_REPO,
+ ref: '',
+ commit: PR_HEAD_SHA,
+ allowUnsafePrCheckout: false
+ })
+ ).toThrow(/Refusing to check out fork pull request code/)
+ })
+
+ it('refuses pull_request_target fork PR merge_commit_sha checkout', () => {
+ setContext('pull_request_target', forkPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: BASE_QUALIFIED_REPO,
+ ref: '',
+ commit: PR_MERGE_SHA,
+ allowUnsafePrCheckout: false
+ })
+ ).toThrow(/allow-unsafe-pr-checkout/)
+ })
+
+ it('refuses pull_request_target fork PR ref pattern (head)', () => {
+ setContext('pull_request_target', forkPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: BASE_QUALIFIED_REPO,
+ ref: 'refs/pull/42/head',
+ commit: '',
+ allowUnsafePrCheckout: false
+ })
+ ).toThrow()
+ })
+
+ it('refuses pull_request_target fork PR ref pattern (merge)', () => {
+ setContext('pull_request_target', forkPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: BASE_QUALIFIED_REPO,
+ ref: 'refs/pull/42/merge',
+ commit: '',
+ allowUnsafePrCheckout: false
+ })
+ ).toThrow()
+ })
+
+ it('refuses pull_request_target when repository points at the fork', () => {
+ setContext('pull_request_target', forkPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: FORK_QUALIFIED_REPO,
+ ref: 'refs/heads/main',
+ commit: '',
+ allowUnsafePrCheckout: false
+ })
+ ).toThrow()
+ })
+
+ it('allows pull_request_target checkout of an unrelated third-party repo', () => {
+ setContext('pull_request_target', forkPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: 'some-other/unrelated',
+ ref: 'refs/heads/main',
+ commit: '',
+ allowUnsafePrCheckout: false
+ })
+ ).not.toThrow()
+ })
+
+ it('refuses pull_request_target ignoring repository case differences', () => {
+ setContext('pull_request_target', forkPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: FORK_QUALIFIED_REPO.toUpperCase(),
+ ref: '',
+ commit: '',
+ allowUnsafePrCheckout: false
+ })
+ ).toThrow()
+ })
+
+ it('refuses pull_request_target ignoring commit SHA case differences', () => {
+ setContext('pull_request_target', forkPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: BASE_QUALIFIED_REPO,
+ ref: '',
+ commit: PR_HEAD_SHA.toUpperCase(),
+ allowUnsafePrCheckout: false
+ })
+ ).toThrow()
+ })
+
+ it('allows pull_request_target fork PR checkout when opted in', () => {
+ setContext('pull_request_target', forkPullRequestTargetPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: BASE_QUALIFIED_REPO,
+ ref: 'refs/pull/42/merge',
+ commit: '',
+ allowUnsafePrCheckout: true
+ })
+ ).not.toThrow()
+ })
+
+ it('refuses workflow_run fork PR head_commit.id checkout', () => {
+ setContext('workflow_run', forkWorkflowRunPayload())
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: BASE_QUALIFIED_REPO,
+ ref: '',
+ commit: WORKFLOW_RUN_HEAD_COMMIT_SHA,
+ allowUnsafePrCheckout: false
+ })
+ ).toThrow()
+ })
+
+ it('refuses workflow_run with pull_request_target underlying event', () => {
+ const payload = forkWorkflowRunPayload() as {
+ workflow_run: {event: string}
+ }
+ payload.workflow_run.event = 'pull_request_target'
+ setContext('workflow_run', payload)
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: BASE_QUALIFIED_REPO,
+ ref: '',
+ commit: WORKFLOW_RUN_HEAD_COMMIT_SHA,
+ allowUnsafePrCheckout: false
+ })
+ ).toThrow()
+ })
+
+ it('allows workflow_run same-repo PR (head_repository.id matches base)', () => {
+ const payload = forkWorkflowRunPayload() as {
+ workflow_run: {head_repository: {id: number}}
+ }
+ payload.workflow_run.head_repository.id = BASE_REPO_ID
+ setContext('workflow_run', payload)
+ expect(() =>
+ assertSafePrCheckout({
+ qualifiedRepository: BASE_QUALIFIED_REPO,
+ ref: '',
+ commit: WORKFLOW_RUN_HEAD_COMMIT_SHA,
+ allowUnsafePrCheckout: false
+ })
+ ).not.toThrow()
+ })
+})