From 8e01fd3fa5b46ac73510eed60a40a1214947704a Mon Sep 17 00:00:00 2001 From: evilgensec Date: Fri, 5 Jun 2026 07:10:11 +0545 Subject: [PATCH] integration_test.yml: fix CODEOWNERS authorization bypass The /run-integration-tests authorization check authorized any commenter whenever CODEOWNERS contained a team entry. The fallback branch only tested whether some "@org/team" owner existed and started with the repo owner; it never checked the commenter, so a single "@org/" line in CODEOWNERS would let any fork PR author trigger the integration job, which checks out PR-supplied code and runs it (pip install -e, pytest) with the Chronicle service-account credentials in env. Verify the commenter's actual team membership via teams.getMembershipForUserInOrg, falling back to named ownership. The check fails closed when membership cannot be read. --- .github/workflows/integration_test.yml | 37 +++++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 24dac49..2bad038 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -144,13 +144,36 @@ jobs: } } - // Check if commenter is in CODEOWNERS - const isAuthorized = - owners.has(commenter) || - Array.from(owners).some(owner => - owner.includes('/') && - owner.startsWith(context.repo.owner + '/') - ); + // A commenter is authorized if they are named directly in + // CODEOWNERS, or are an active member of a CODEOWNERS team in this + // org. The team check MUST verify the commenter's own membership: + // the previous code authorized every commenter whenever any + // "@org/team" entry was present, regardless of who commented, so a + // single team line in CODEOWNERS would let any fork PR author run + // this workflow. + let isAuthorized = owners.has(commenter); + if (!isAuthorized) { + for (const owner of owners) { + const slash = owner.indexOf('/'); + if (slash < 0) continue; + const org = owner.slice(0, slash); + const team = owner.slice(slash + 1); + if (org !== context.repo.owner) continue; + try { + const membership = await github.rest.teams.getMembershipForUserInOrg({ + org, + team_slug: team, + username: commenter, + }); + if (membership.data && membership.data.state === 'active') { + isAuthorized = true; + break; + } + } catch (error) { + // 404 (not a member) or missing org-read scope: stay unauthorized. + } + } + } // Output authorization result as a simple string core.setOutput("authorized", isAuthorized ? "true" : "false");