Переглянути джерело

skip running unsafe pr check if input is default (backport #2518 to releases/v3)

copilot-swe-agent[bot] 1 місяць тому
батько
коміт
9892aae41f
3 змінених файлів з 75 додано та 14 видалено
  1. 46 0
      __test__/input-helper.test.ts
  2. 13 6
      dist/index.js
  3. 16 8
      src/input-helper.ts

+ 46 - 0
__test__/input-helper.test.ts

@@ -143,4 +143,50 @@ describe('input-helper tests', () => {
     const settings: IGitSourceSettings = await inputHelper.getInputs()
     expect(settings.workflowOrganizationId).toBe(123456)
   })
+
+  describe('unsafe PR checkout guard', () => {
+    const forkPayload = {
+      repository: {id: 100},
+      pull_request: {
+        head: {
+          sha: '1234567890123456789012345678901234567890',
+          repo: {id: 200, full_name: 'attacker/fork'}
+        },
+        merge_commit_sha: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+      }
+    }
+
+    it('allows the default self-checkout on a fork pull_request_target', async () => {
+      const originalEvent = github.context.eventName
+      const originalPayload = github.context.payload
+      try {
+        github.context.eventName = 'pull_request_target'
+        github.context.payload = forkPayload as any
+        // Simulate a rebase/fast-forward merge where the base tip (event SHA)
+        // equals the PR head SHA. The default self-checkout must still succeed.
+        github.context.sha = '1234567890123456789012345678901234567890'
+        const settings: IGitSourceSettings = await inputHelper.getInputs()
+        expect(settings.commit).toBe('1234567890123456789012345678901234567890')
+      } finally {
+        github.context.eventName = originalEvent
+        github.context.payload = originalPayload
+      }
+    })
+
+    it('refuses an explicit fork repository on pull_request_target', async () => {
+      const originalEvent = github.context.eventName
+      const originalPayload = github.context.payload
+      try {
+        github.context.eventName = 'pull_request_target'
+        github.context.payload = forkPayload as any
+        inputs.repository = 'attacker/fork'
+        await expect(inputHelper.getInputs()).rejects.toThrow(
+          /Refusing to check out fork pull request code/
+        )
+      } finally {
+        github.context.eventName = originalEvent
+        github.context.payload = originalPayload
+      }
+    })
+  })
 })

+ 13 - 6
dist/index.js

@@ -1779,12 +1779,19 @@ function getInputs() {
             (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
-        });
+        // The default self-checkout (this repository with no explicit ref) always
+        // resolves to the trusted ref/commit GitHub set for the triggering event, so
+        // the fork-checkout guard only needs to run when the caller customized the
+        // repository or ref.
+        const isDefaultCheckout = isWorkflowRepository && !core.getInput('ref');
+        if (!isDefaultCheckout) {
+            unsafePrCheckoutHelper.assertSafePrCheckout({
+                qualifiedRepository,
+                ref: result.ref,
+                commit: result.commit,
+                allowUnsafePrCheckout: result.allowUnsafePrCheckout
+            });
+        }
         return result;
     });
 }

+ 16 - 8
src/input-helper.ts

@@ -7,7 +7,7 @@ import * as workflowContextHelper from './workflow-context-helper'
 import {IGitSourceSettings} from './git-source-settings'
 
 export async function getInputs(): Promise<IGitSourceSettings> {
-  const result = ({} as unknown) as IGitSourceSettings
+  const result = {} as unknown as IGitSourceSettings
 
   // GitHub workspace
   let githubWorkspacePath = process.env['GITHUB_WORKSPACE']
@@ -137,7 +137,8 @@ export async function getInputs(): Promise<IGitSourceSettings> {
     (core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE'
 
   // Workflow organization ID
-  result.workflowOrganizationId = await workflowContextHelper.getOrganizationId()
+  result.workflowOrganizationId =
+    await workflowContextHelper.getOrganizationId()
 
   // Set safe.directory in git global config.
   result.setSafeDirectory =
@@ -153,12 +154,19 @@ export async function getInputs(): Promise<IGitSourceSettings> {
     'TRUE'
   core.debug(`allow unsafe PR checkout = ${result.allowUnsafePrCheckout}`)
 
-  unsafePrCheckoutHelper.assertSafePrCheckout({
-    qualifiedRepository,
-    ref: result.ref,
-    commit: result.commit,
-    allowUnsafePrCheckout: result.allowUnsafePrCheckout
-  })
+  // The default self-checkout (this repository with no explicit ref) always
+  // resolves to the trusted ref/commit GitHub set for the triggering event, so
+  // the fork-checkout guard only needs to run when the caller customized the
+  // repository or ref.
+  const isDefaultCheckout = isWorkflowRepository && !core.getInput('ref')
+  if (!isDefaultCheckout) {
+    unsafePrCheckoutHelper.assertSafePrCheckout({
+      qualifiedRepository,
+      ref: result.ref,
+      commit: result.commit,
+      allowUnsafePrCheckout: result.allowUnsafePrCheckout
+    })
+  }
 
   return result
 }