diff --git a/.github/workflows/test-safe-output-add-issue-comment.lock.yml b/.github/workflows/test-safe-output-add-issue-comment.lock.yml index b33ef09f785..03b8c3d6b57 100644 --- a/.github/workflows/test-safe-output-add-issue-comment.lock.yml +++ b/.github/workflows/test-safe-output-add-issue-comment.lock.yml @@ -27,79 +27,6 @@ jobs: outputs: output: ${{ steps.collect_output.outputs.output }} steps: - - name: Check team membership for workflow - id: check-team-member - uses: actions/github-script@v7 - env: - GITHUB_AW_REQUIRED_ROLES: admin,maintainer - with: - script: | - async function main() { - const { eventName } = context; - // skip check for safe events - const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; - if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); - return; - } - const actor = context.actor; - const { owner, repo } = context.repo; - const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES; - const requiredPermissions = requiredPermissionsEnv - ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") - : []; - if (!requiredPermissions || requiredPermissions.length === 0) { - core.error( - "❌ Configuration error: Required permissions not specified. Contact repository administrator." - ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); - return; - } - // Check if the actor has the required repository permissions - try { - console.log( - `Checking if user '${actor}' has required permissions for ${owner}/${repo}` - ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); - const repoPermission = - await github.rest.repos.getCollaboratorPermissionLevel({ - owner: owner, - repo: repo, - username: actor, - }); - const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); - // Check if user has one of the required permission levels - for (const requiredPerm of requiredPermissions) { - if ( - permission === requiredPerm || - (requiredPerm === "maintainer" && permission === "maintain") - ) { - console.log(`✅ User has ${permission} access to repository`); - return; - } - } - console.log( - `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` - ); - } catch (repoError) { - const errorMessage = - repoError instanceof Error ? repoError.message : String(repoError); - core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); - return; - } - // Cancel the job when permission check fails - core.warning( - `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - core.setCancelled( - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - } - await main(); - name: Checkout repository uses: actions/checkout@v5 - name: Setup agent output diff --git a/.github/workflows/test-safe-output-add-issue-label.lock.yml b/.github/workflows/test-safe-output-add-issue-label.lock.yml index bff150a66fd..c7c62044189 100644 --- a/.github/workflows/test-safe-output-add-issue-label.lock.yml +++ b/.github/workflows/test-safe-output-add-issue-label.lock.yml @@ -29,79 +29,6 @@ jobs: outputs: output: ${{ steps.collect_output.outputs.output }} steps: - - name: Check team membership for workflow - id: check-team-member - uses: actions/github-script@v7 - env: - GITHUB_AW_REQUIRED_ROLES: admin,maintainer - with: - script: | - async function main() { - const { eventName } = context; - // skip check for safe events - const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; - if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); - return; - } - const actor = context.actor; - const { owner, repo } = context.repo; - const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES; - const requiredPermissions = requiredPermissionsEnv - ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") - : []; - if (!requiredPermissions || requiredPermissions.length === 0) { - core.error( - "❌ Configuration error: Required permissions not specified. Contact repository administrator." - ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); - return; - } - // Check if the actor has the required repository permissions - try { - console.log( - `Checking if user '${actor}' has required permissions for ${owner}/${repo}` - ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); - const repoPermission = - await github.rest.repos.getCollaboratorPermissionLevel({ - owner: owner, - repo: repo, - username: actor, - }); - const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); - // Check if user has one of the required permission levels - for (const requiredPerm of requiredPermissions) { - if ( - permission === requiredPerm || - (requiredPerm === "maintainer" && permission === "maintain") - ) { - console.log(`✅ User has ${permission} access to repository`); - return; - } - } - console.log( - `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` - ); - } catch (repoError) { - const errorMessage = - repoError instanceof Error ? repoError.message : String(repoError); - core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); - return; - } - // Cancel the job when permission check fails - core.warning( - `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - core.setCancelled( - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - } - await main(); - name: Checkout repository uses: actions/checkout@v5 - name: Setup agent output diff --git a/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml b/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml index fee2be52feb..0ce2707e45f 100644 --- a/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml +++ b/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml @@ -31,79 +31,6 @@ jobs: outputs: output: ${{ steps.collect_output.outputs.output }} steps: - - name: Check team membership for workflow - id: check-team-member - uses: actions/github-script@v7 - env: - GITHUB_AW_REQUIRED_ROLES: admin,maintainer - with: - script: | - async function main() { - const { eventName } = context; - // skip check for safe events - const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; - if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); - return; - } - const actor = context.actor; - const { owner, repo } = context.repo; - const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES; - const requiredPermissions = requiredPermissionsEnv - ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") - : []; - if (!requiredPermissions || requiredPermissions.length === 0) { - core.error( - "❌ Configuration error: Required permissions not specified. Contact repository administrator." - ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); - return; - } - // Check if the actor has the required repository permissions - try { - console.log( - `Checking if user '${actor}' has required permissions for ${owner}/${repo}` - ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); - const repoPermission = - await github.rest.repos.getCollaboratorPermissionLevel({ - owner: owner, - repo: repo, - username: actor, - }); - const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); - // Check if user has one of the required permission levels - for (const requiredPerm of requiredPermissions) { - if ( - permission === requiredPerm || - (requiredPerm === "maintainer" && permission === "maintain") - ) { - console.log(`✅ User has ${permission} access to repository`); - return; - } - } - console.log( - `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` - ); - } catch (repoError) { - const errorMessage = - repoError instanceof Error ? repoError.message : String(repoError); - core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); - return; - } - // Cancel the job when permission check fails - core.warning( - `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - core.setCancelled( - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - } - await main(); - name: Checkout repository uses: actions/checkout@v5 - name: Setup agent output diff --git a/.github/workflows/test-safe-output-create-discussion.lock.yml b/.github/workflows/test-safe-output-create-discussion.lock.yml index dc242b54875..55936478bf2 100644 --- a/.github/workflows/test-safe-output-create-discussion.lock.yml +++ b/.github/workflows/test-safe-output-create-discussion.lock.yml @@ -26,79 +26,6 @@ jobs: outputs: output: ${{ steps.collect_output.outputs.output }} steps: - - name: Check team membership for workflow - id: check-team-member - uses: actions/github-script@v7 - env: - GITHUB_AW_REQUIRED_ROLES: admin,maintainer - with: - script: | - async function main() { - const { eventName } = context; - // skip check for safe events - const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; - if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); - return; - } - const actor = context.actor; - const { owner, repo } = context.repo; - const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES; - const requiredPermissions = requiredPermissionsEnv - ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") - : []; - if (!requiredPermissions || requiredPermissions.length === 0) { - core.error( - "❌ Configuration error: Required permissions not specified. Contact repository administrator." - ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); - return; - } - // Check if the actor has the required repository permissions - try { - console.log( - `Checking if user '${actor}' has required permissions for ${owner}/${repo}` - ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); - const repoPermission = - await github.rest.repos.getCollaboratorPermissionLevel({ - owner: owner, - repo: repo, - username: actor, - }); - const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); - // Check if user has one of the required permission levels - for (const requiredPerm of requiredPermissions) { - if ( - permission === requiredPerm || - (requiredPerm === "maintainer" && permission === "maintain") - ) { - console.log(`✅ User has ${permission} access to repository`); - return; - } - } - console.log( - `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` - ); - } catch (repoError) { - const errorMessage = - repoError instanceof Error ? repoError.message : String(repoError); - core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); - return; - } - // Cancel the job when permission check fails - core.warning( - `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - core.setCancelled( - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - } - await main(); - name: Checkout repository uses: actions/checkout@v5 - name: Setup agent output diff --git a/.github/workflows/test-safe-output-create-issue.lock.yml b/.github/workflows/test-safe-output-create-issue.lock.yml index 7ed149ea4fa..497fbf750a8 100644 --- a/.github/workflows/test-safe-output-create-issue.lock.yml +++ b/.github/workflows/test-safe-output-create-issue.lock.yml @@ -23,79 +23,6 @@ jobs: outputs: output: ${{ steps.collect_output.outputs.output }} steps: - - name: Check team membership for workflow - id: check-team-member - uses: actions/github-script@v7 - env: - GITHUB_AW_REQUIRED_ROLES: admin,maintainer - with: - script: | - async function main() { - const { eventName } = context; - // skip check for safe events - const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; - if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); - return; - } - const actor = context.actor; - const { owner, repo } = context.repo; - const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES; - const requiredPermissions = requiredPermissionsEnv - ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") - : []; - if (!requiredPermissions || requiredPermissions.length === 0) { - core.error( - "❌ Configuration error: Required permissions not specified. Contact repository administrator." - ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); - return; - } - // Check if the actor has the required repository permissions - try { - console.log( - `Checking if user '${actor}' has required permissions for ${owner}/${repo}` - ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); - const repoPermission = - await github.rest.repos.getCollaboratorPermissionLevel({ - owner: owner, - repo: repo, - username: actor, - }); - const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); - // Check if user has one of the required permission levels - for (const requiredPerm of requiredPermissions) { - if ( - permission === requiredPerm || - (requiredPerm === "maintainer" && permission === "maintain") - ) { - console.log(`✅ User has ${permission} access to repository`); - return; - } - } - console.log( - `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` - ); - } catch (repoError) { - const errorMessage = - repoError instanceof Error ? repoError.message : String(repoError); - core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); - return; - } - // Cancel the job when permission check fails - core.warning( - `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - core.setCancelled( - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - } - await main(); - name: Checkout repository uses: actions/checkout@v5 - name: Setup agent output @@ -1104,79 +1031,6 @@ jobs: issue_number: ${{ steps.create_issue.outputs.issue_number }} issue_url: ${{ steps.create_issue.outputs.issue_url }} steps: - - name: Check team membership for workflow - id: check-team-member - uses: actions/github-script@v7 - env: - GITHUB_AW_REQUIRED_ROLES: admin,maintainer - with: - script: | - async function main() { - const { eventName } = context; - // skip check for safe events - const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; - if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); - return; - } - const actor = context.actor; - const { owner, repo } = context.repo; - const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES; - const requiredPermissions = requiredPermissionsEnv - ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") - : []; - if (!requiredPermissions || requiredPermissions.length === 0) { - core.error( - "❌ Configuration error: Required permissions not specified. Contact repository administrator." - ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); - return; - } - // Check if the actor has the required repository permissions - try { - console.log( - `Checking if user '${actor}' has required permissions for ${owner}/${repo}` - ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); - const repoPermission = - await github.rest.repos.getCollaboratorPermissionLevel({ - owner: owner, - repo: repo, - username: actor, - }); - const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); - // Check if user has one of the required permission levels - for (const requiredPerm of requiredPermissions) { - if ( - permission === requiredPerm || - (requiredPerm === "maintainer" && permission === "maintain") - ) { - console.log(`✅ User has ${permission} access to repository`); - return; - } - } - console.log( - `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` - ); - } catch (repoError) { - const errorMessage = - repoError instanceof Error ? repoError.message : String(repoError); - core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); - return; - } - // Cancel the job when permission check fails - core.warning( - `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - core.setCancelled( - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - } - await main(); - name: Create Output Issue id: create_issue uses: actions/github-script@v7 diff --git a/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml b/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml index 4c51a1c98a5..5f1627b5ec4 100644 --- a/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml +++ b/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml @@ -25,79 +25,6 @@ jobs: outputs: output: ${{ steps.collect_output.outputs.output }} steps: - - name: Check team membership for workflow - id: check-team-member - uses: actions/github-script@v7 - env: - GITHUB_AW_REQUIRED_ROLES: admin,maintainer - with: - script: | - async function main() { - const { eventName } = context; - // skip check for safe events - const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; - if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); - return; - } - const actor = context.actor; - const { owner, repo } = context.repo; - const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES; - const requiredPermissions = requiredPermissionsEnv - ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") - : []; - if (!requiredPermissions || requiredPermissions.length === 0) { - core.error( - "❌ Configuration error: Required permissions not specified. Contact repository administrator." - ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); - return; - } - // Check if the actor has the required repository permissions - try { - console.log( - `Checking if user '${actor}' has required permissions for ${owner}/${repo}` - ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); - const repoPermission = - await github.rest.repos.getCollaboratorPermissionLevel({ - owner: owner, - repo: repo, - username: actor, - }); - const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); - // Check if user has one of the required permission levels - for (const requiredPerm of requiredPermissions) { - if ( - permission === requiredPerm || - (requiredPerm === "maintainer" && permission === "maintain") - ) { - console.log(`✅ User has ${permission} access to repository`); - return; - } - } - console.log( - `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` - ); - } catch (repoError) { - const errorMessage = - repoError instanceof Error ? repoError.message : String(repoError); - core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); - return; - } - // Cancel the job when permission check fails - core.warning( - `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - core.setCancelled( - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - } - await main(); - name: Checkout repository uses: actions/checkout@v5 - name: Setup agent output diff --git a/.github/workflows/test-safe-output-create-pull-request.lock.yml b/.github/workflows/test-safe-output-create-pull-request.lock.yml index 4b5a8933cf2..4f467f77334 100644 --- a/.github/workflows/test-safe-output-create-pull-request.lock.yml +++ b/.github/workflows/test-safe-output-create-pull-request.lock.yml @@ -24,79 +24,6 @@ jobs: outputs: output: ${{ steps.collect_output.outputs.output }} steps: - - name: Check team membership for workflow - id: check-team-member - uses: actions/github-script@v7 - env: - GITHUB_AW_REQUIRED_ROLES: admin,maintainer - with: - script: | - async function main() { - const { eventName } = context; - // skip check for safe events - const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; - if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); - return; - } - const actor = context.actor; - const { owner, repo } = context.repo; - const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES; - const requiredPermissions = requiredPermissionsEnv - ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") - : []; - if (!requiredPermissions || requiredPermissions.length === 0) { - core.error( - "❌ Configuration error: Required permissions not specified. Contact repository administrator." - ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); - return; - } - // Check if the actor has the required repository permissions - try { - console.log( - `Checking if user '${actor}' has required permissions for ${owner}/${repo}` - ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); - const repoPermission = - await github.rest.repos.getCollaboratorPermissionLevel({ - owner: owner, - repo: repo, - username: actor, - }); - const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); - // Check if user has one of the required permission levels - for (const requiredPerm of requiredPermissions) { - if ( - permission === requiredPerm || - (requiredPerm === "maintainer" && permission === "maintain") - ) { - console.log(`✅ User has ${permission} access to repository`); - return; - } - } - console.log( - `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` - ); - } catch (repoError) { - const errorMessage = - repoError instanceof Error ? repoError.message : String(repoError); - core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); - return; - } - // Cancel the job when permission check fails - core.warning( - `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - core.setCancelled( - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - } - await main(); - name: Checkout repository uses: actions/checkout@v5 - name: Configure Git credentials diff --git a/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml b/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml index bfda88579f3..90f58d1980a 100644 --- a/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml +++ b/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml @@ -25,79 +25,6 @@ jobs: outputs: output: ${{ steps.collect_output.outputs.output }} steps: - - name: Check team membership for workflow - id: check-team-member - uses: actions/github-script@v7 - env: - GITHUB_AW_REQUIRED_ROLES: admin,maintainer - with: - script: | - async function main() { - const { eventName } = context; - // skip check for safe events - const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; - if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); - return; - } - const actor = context.actor; - const { owner, repo } = context.repo; - const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES; - const requiredPermissions = requiredPermissionsEnv - ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") - : []; - if (!requiredPermissions || requiredPermissions.length === 0) { - core.error( - "❌ Configuration error: Required permissions not specified. Contact repository administrator." - ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); - return; - } - // Check if the actor has the required repository permissions - try { - console.log( - `Checking if user '${actor}' has required permissions for ${owner}/${repo}` - ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); - const repoPermission = - await github.rest.repos.getCollaboratorPermissionLevel({ - owner: owner, - repo: repo, - username: actor, - }); - const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); - // Check if user has one of the required permission levels - for (const requiredPerm of requiredPermissions) { - if ( - permission === requiredPerm || - (requiredPerm === "maintainer" && permission === "maintain") - ) { - console.log(`✅ User has ${permission} access to repository`); - return; - } - } - console.log( - `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` - ); - } catch (repoError) { - const errorMessage = - repoError instanceof Error ? repoError.message : String(repoError); - core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); - return; - } - // Cancel the job when permission check fails - core.warning( - `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - core.setCancelled( - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - } - await main(); - name: Checkout repository uses: actions/checkout@v5 - name: Configure Git credentials diff --git a/.github/workflows/test-safe-output-update-issue.lock.yml b/.github/workflows/test-safe-output-update-issue.lock.yml index be62dddc14c..5f7108012a5 100644 --- a/.github/workflows/test-safe-output-update-issue.lock.yml +++ b/.github/workflows/test-safe-output-update-issue.lock.yml @@ -24,79 +24,6 @@ jobs: outputs: output: ${{ steps.collect_output.outputs.output }} steps: - - name: Check team membership for workflow - id: check-team-member - uses: actions/github-script@v7 - env: - GITHUB_AW_REQUIRED_ROLES: admin,maintainer - with: - script: | - async function main() { - const { eventName } = context; - // skip check for safe events - const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"]; - if (safeEvents.includes(eventName)) { - console.log(`✅ Event ${eventName} does not require validation`); - return; - } - const actor = context.actor; - const { owner, repo } = context.repo; - const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES; - const requiredPermissions = requiredPermissionsEnv - ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") - : []; - if (!requiredPermissions || requiredPermissions.length === 0) { - core.error( - "❌ Configuration error: Required permissions not specified. Contact repository administrator." - ); - core.setCancelled( - "Configuration error: Required permissions not specified" - ); - return; - } - // Check if the actor has the required repository permissions - try { - console.log( - `Checking if user '${actor}' has required permissions for ${owner}/${repo}` - ); - console.log(`Required permissions: ${requiredPermissions.join(", ")}`); - const repoPermission = - await github.rest.repos.getCollaboratorPermissionLevel({ - owner: owner, - repo: repo, - username: actor, - }); - const permission = repoPermission.data.permission; - console.log(`Repository permission level: ${permission}`); - // Check if user has one of the required permission levels - for (const requiredPerm of requiredPermissions) { - if ( - permission === requiredPerm || - (requiredPerm === "maintainer" && permission === "maintain") - ) { - console.log(`✅ User has ${permission} access to repository`); - return; - } - } - console.log( - `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}` - ); - } catch (repoError) { - const errorMessage = - repoError instanceof Error ? repoError.message : String(repoError); - core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); - return; - } - // Cancel the job when permission check fails - core.warning( - `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - core.setCancelled( - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` - ); - } - await main(); - name: Checkout repository uses: actions/checkout@v5 - name: Setup agent output diff --git a/pkg/workflow/actions_write_permission_test.go b/pkg/workflow/actions_write_permission_test.go new file mode 100644 index 00000000000..dd91f8acd1f --- /dev/null +++ b/pkg/workflow/actions_write_permission_test.go @@ -0,0 +1,259 @@ +package workflow + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +// TestActionsWritePermissionForSelfCancellation tests that actions: write permission +// is added to jobs that include team member checks for self-cancellation functionality +func TestActionsWritePermissionForSelfCancellation(t *testing.T) { + // Create temporary directory for test files + tmpDir, err := os.MkdirTemp("", "actions-write-permission-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + compiler := NewCompiler(false, "", "test") + + tests := []struct { + name string + frontmatter string + filename string + expectActionsWrite bool + jobName string + description string + }{ + { + name: "command workflow task job should have actions: write", + frontmatter: `--- +on: + command: + name: test-bot +tools: + github: + allowed: [list_issues] +--- + +# Command Workflow +Test workflow with command trigger.`, + filename: "command-workflow.md", + expectActionsWrite: true, + jobName: "task", + description: "Task job should have actions: write for self-cancellation", + }, + { + name: "push workflow main job should have actions: write", + frontmatter: `--- +on: + push: + branches: [main] +tools: + github: + allowed: [list_issues] +--- + +# Push Workflow +Test workflow with push trigger that needs permission checks.`, + filename: "push-workflow.md", + expectActionsWrite: true, + jobName: "push-workflow", + description: "Main job should have actions: write for permission checks", + }, + { + name: "workflow_dispatch should not have actions: write", + frontmatter: `--- +on: + workflow_dispatch: +tools: + github: + allowed: [list_issues] +--- + +# Workflow Dispatch +Test workflow with safe event.`, + filename: "workflow-dispatch.md", + expectActionsWrite: false, + jobName: "", + description: "Safe events should not need actions: write permission", + }, + { + name: "schedule workflow should not have actions: write", + frontmatter: `--- +on: + schedule: + - cron: "0 9 * * 1" +tools: + github: + allowed: [list_issues] +--- + +# Schedule Workflow +Test workflow with schedule trigger.`, + filename: "schedule-workflow.md", + expectActionsWrite: false, + jobName: "", + description: "Schedule events should not need actions: write permission", + }, + { + name: "roles: all should not have actions: write", + frontmatter: `--- +on: + push: + branches: [main] +roles: all +tools: + github: + allowed: [list_issues] +--- + +# Unrestricted Workflow +Test workflow with unrestricted access.`, + filename: "unrestricted-workflow.md", + expectActionsWrite: false, + jobName: "", + description: "Unrestricted workflows should not need actions: write permission", + }, + { + name: "main job should have actions: write when no task job", + frontmatter: `--- +on: + issues: + types: [opened] +tools: + github: + allowed: [list_issues] +--- + +# Issues Workflow +Test workflow with permission checks but no task job.`, + filename: "issues-workflow.md", + expectActionsWrite: true, + jobName: "issues-workflow", + description: "Main job should have actions: write for permission checks", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create test file + testFile := filepath.Join(tmpDir, tt.filename) + err := os.WriteFile(testFile, []byte(tt.frontmatter), 0644) + if err != nil { + t.Fatal(err) + } + + // Compile the workflow + err = compiler.CompileWorkflow(testFile) + if err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.TrimSuffix(testFile, ".md") + ".lock.yml" + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read lock file: %v", err) + } + lockContentStr := string(lockContent) + + if tt.expectActionsWrite { + // Check that the specified job exists + if !strings.Contains(lockContentStr, " "+tt.jobName+":") { + t.Fatalf("Job '%s' not found in workflow", tt.jobName) + } + + // Check for actions: write permission anywhere in the workflow (should be in the right job) + if !strings.Contains(lockContentStr, "permissions:") || !strings.Contains(lockContentStr, "actions: write") { + t.Errorf("%s: Expected 'actions: write' permission in workflow but not found", tt.description) + } + + // Check that setCancelled function is present + if !strings.Contains(lockContentStr, "async function setCancelled(message)") { + t.Errorf("%s: Expected custom setCancelled function but not found", tt.description) + } + + // Check that github.rest.actions.cancelWorkflowRun is called + if !strings.Contains(lockContentStr, "github.rest.actions.cancelWorkflowRun") { + t.Errorf("%s: Expected self-cancellation API call but not found", tt.description) + } + + } else { + // Check that actions: write permission is not present + if strings.Contains(lockContentStr, "actions: write") { + t.Errorf("%s: Did not expect 'actions: write' permission but found it", tt.description) + } + } + }) + } +} + +// TestMainJobActionsWritePermission tests that the main job gets actions: write +// permission when permission checks are needed and no task job exists +func TestMainJobActionsWritePermission(t *testing.T) { + // Create temporary directory for test files + tmpDir, err := os.MkdirTemp("", "main-job-actions-write-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + compiler := NewCompiler(false, "", "test") + + testContent := `--- +on: + issues: + types: [opened] +tools: + github: + allowed: [list_issues] +--- + +# Issues Workflow +This workflow needs permission checks but has no task job.` + + // Create test file + testFile := filepath.Join(tmpDir, "issues-workflow.md") + err = os.WriteFile(testFile, []byte(testContent), 0644) + if err != nil { + t.Fatal(err) + } + + // Compile the workflow + err = compiler.CompileWorkflow(testFile) + if err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockFile := strings.TrimSuffix(testFile, ".md") + ".lock.yml" + lockContent, err := os.ReadFile(lockFile) + if err != nil { + t.Fatalf("Failed to read lock file: %v", err) + } + lockContentStr := string(lockContent) + + // Check that the main job has actions: write permission + // Look for the main job (should be "issues-workflow" based on filename) + if !strings.Contains(lockContentStr, " issues-workflow:") { + t.Fatal("Main job 'issues-workflow' not found in workflow") + } + + // Check for actions: write permission anywhere in the workflow + if !strings.Contains(lockContentStr, "permissions:") || !strings.Contains(lockContentStr, "actions: write") { + t.Error("Expected 'actions: write' permission in main job but not found") + } + + // Check that permission check step is present + if !strings.Contains(lockContentStr, "Check team membership for workflow") { + t.Error("Expected team membership check step in main job but not found") + } + + // Check that setCancelled function is present + if !strings.Contains(lockContentStr, "async function setCancelled(message)") { + t.Error("Expected custom setCancelled function but not found") + } +} diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index ce7092e5cd7..c0dd63e75d9 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -122,33 +122,34 @@ func NewCompilerWithCustomOutput(verbose bool, engineOverride string, customOutp // WorkflowData holds all the data needed to generate a GitHub Actions workflow type WorkflowData struct { - Name string - FrontmatterName string // name field from frontmatter (for code scanning alert driver default) - On string - Permissions string - Network string // top-level network permissions configuration - Concurrency string - RunName string - Env string - If string - TimeoutMinutes string - CustomSteps string - PostSteps string // steps to run after AI execution - RunsOn string - Tools map[string]any - MarkdownContent string - AI string // "claude" or "codex" (for backwards compatibility) - EngineConfig *EngineConfig // Extended engine configuration - StopTime string - Command string // for /command trigger support - CommandOtherEvents map[string]any // for merging command with other events - AIReaction string // AI reaction type like "eyes", "heart", etc. - Jobs map[string]any // custom job configurations with dependencies - Cache string // cache configuration - NeedsTextOutput bool // whether the workflow uses ${{ needs.task.outputs.text }} - NetworkPermissions *NetworkPermissions // parsed network permissions - SafeOutputs *SafeOutputsConfig // output configuration for automatic output routes - Roles []string // permission levels required to trigger workflow + Name string + FrontmatterName string // name field from frontmatter (for code scanning alert driver default) + On string + Permissions string + Network string // top-level network permissions configuration + Concurrency string + RunName string + Env string + If string + TimeoutMinutes string + CustomSteps string + PostSteps string // steps to run after AI execution + RunsOn string + Tools map[string]any + MarkdownContent string + AI string // "claude" or "codex" (for backwards compatibility) + EngineConfig *EngineConfig // Extended engine configuration + StopTime string + Command string // for /command trigger support + CommandOtherEvents map[string]any // for merging command with other events + AIReaction string // AI reaction type like "eyes", "heart", etc. + Jobs map[string]any // custom job configurations with dependencies + Cache string // cache configuration + NeedsTextOutput bool // whether the workflow uses ${{ needs.task.outputs.text }} + NetworkPermissions *NetworkPermissions // parsed network permissions + SafeOutputs *SafeOutputsConfig // output configuration for automatic output routes + Roles []string // permission levels required to trigger workflow + HasExplicitGitHubTools bool // whether GitHub tools were explicitly configured (vs default) } // SafeOutputsConfig holds configuration for automatic output routes @@ -1287,6 +1288,23 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) { if data.RunsOn == "" { data.RunsOn = "runs-on: ubuntu-latest" } + + // Store whether tools were explicitly configured before applying defaults + hasExplicitTools := len(data.Tools) > 0 + if hasExplicitTools { + // Check if github tools are explicitly configured with non-empty allowed list + if githubTools, hasGithub := data.Tools["github"]; hasGithub { + if githubMap, ok := githubTools.(map[string]any); ok { + if allowed, hasAllowed := githubMap["allowed"]; hasAllowed { + if allowedList, ok := allowed.([]any); ok && len(allowedList) > 0 { + // Mark that explicit GitHub tools are configured + data.HasExplicitGitHubTools = true + } + } + } + } + } + // Apply default tools data.Tools = c.applyDefaultTools(data.Tools, data.SafeOutputs) } @@ -1755,11 +1773,8 @@ func (c *Compiler) needsPermissionChecks(data *WorkflowData) bool { return false } - // Permission checks are needed by default unless workflow uses only safe events - // Safe events: workflow_dispatch, workflow_run, schedule - // For now, we'll implement a simple heuristic since we don't have frontmatter here - // We'll implement the full logic later when we have access to frontmatter - return true + // Only need permission checks if GitHub tools are explicitly configured + return data.HasExplicitGitHubTools } // needsPermissionChecksWithFrontmatter determines if the workflow needs permission checks with full context @@ -1769,12 +1784,16 @@ func (c *Compiler) needsPermissionChecksWithFrontmatter(data *WorkflowData, fron return false } + // Only need permission checks if GitHub tools are explicitly configured + if !data.HasExplicitGitHubTools { + return false + } + // Check if the workflow uses only safe events (only if frontmatter is available) if frontmatter != nil && c.hasSafeEventsOnly(data, frontmatter) { return false } - // Permission checks are needed by default for non-safe events return true } @@ -2018,11 +2037,18 @@ func (c *Compiler) buildTaskJob(data *WorkflowData, frontmatter map[string]any) steps = append(steps, " run: echo \"Task job executed - conditions satisfied\"\n") } + // Determine permissions needed for task job + var permissions string + if needsPermissionCheck || data.Command != "" { + // Add actions: write permission for self-cancellation when team member checks are present + permissions = "permissions:\n actions: write" + } + job := &Job{ Name: "task", If: data.If, // Use the existing condition (which may include alias checks) RunsOn: "runs-on: ubuntu-latest", - Permissions: "", // No permissions needed - task job does not require content access + Permissions: permissions, Steps: steps, Outputs: outputs, } @@ -2039,7 +2065,7 @@ core.setOutput("is_team_member", "true"); console.log("Permission check skipped - 'roles: all' specified");` } - // Use the embedded check_permissions.cjs script + // Use the embedded check_permissions.cjs script for flexible permission checking // The GITHUB_AW_REQUIRED_ROLES environment variable is set via the env field return checkPermissionsScript } @@ -2101,11 +2127,17 @@ func (c *Compiler) buildAddReactionJob(data *WorkflowData, taskJobCreated bool, depends = []string{"task"} // Depend on the task job only if it exists } + // Determine permissions - add actions: write if permission checks are added + permissions := "permissions:\n issues: write\n pull-requests: write" + if !taskJobCreated && c.needsPermissionChecks(data) { + permissions = "permissions:\n actions: write\n issues: write\n pull-requests: write" + } + job := &Job{ Name: "add_reaction", If: reactionCondition.Render(), RunsOn: "runs-on: ubuntu-latest", - Permissions: "permissions:\n issues: write\n pull-requests: write", + Permissions: permissions, Steps: steps, Outputs: outputs, Needs: depends, @@ -2186,11 +2218,17 @@ func (c *Compiler) buildCreateOutputIssueJob(data *WorkflowData, mainJobName str jobCondition = "" // No conditional execution } + // Determine permissions - add actions: write if permission checks are added + permissions := "permissions:\n contents: read\n issues: write" + if !taskJobCreated && c.needsPermissionChecks(data) { + permissions = "permissions:\n actions: write\n contents: read\n issues: write" + } + job := &Job{ Name: "create_issue", If: jobCondition, RunsOn: "runs-on: ubuntu-latest", - Permissions: "permissions:\n contents: read\n issues: write", + Permissions: permissions, TimeoutMinutes: 10, // 10-minute timeout as required Steps: steps, Outputs: outputs, @@ -2660,11 +2698,45 @@ func (c *Compiler) buildMainJob(data *WorkflowData, jobName string, taskJobCreat jobCondition = data.If // Use the original If condition from the workflow data } + // Determine permissions - modify permissions to include actions: write if permission checks are added + permissions := data.Permissions + if !taskJobCreated { + var needsPermissionCheck bool + // Check if permission checks are needed using frontmatter if available + if frontmatter != nil { + needsPermissionCheck = c.needsPermissionChecksWithFrontmatter(data, frontmatter) + } else { + needsPermissionCheck = c.needsPermissionChecks(data) + } + + if needsPermissionCheck { + // Add actions: write permission for self-cancellation + if permissions == "" || permissions == "permissions: read-all" { + permissions = "permissions:\n actions: write\n contents: read\n issues: read\n pull-requests: read" + } else { + // Parse existing permissions and add actions: write if not present + if !strings.Contains(permissions, "actions:") { + // Insert actions: write at the beginning of the permissions block + lines := strings.Split(permissions, "\n") + if len(lines) > 0 && strings.Contains(lines[0], "permissions:") { + // Insert after the permissions: line + newLines := []string{lines[0], " actions: write"} + newLines = append(newLines, lines[1:]...) + permissions = strings.Join(newLines, "\n") + } else { + // Fallback: prepend to existing permissions + permissions = "permissions:\n actions: write\n" + strings.TrimPrefix(permissions, "permissions:\n") + } + } + } + } + } + job := &Job{ Name: jobName, If: jobCondition, RunsOn: c.indentYAMLLines(data.RunsOn, " "), - Permissions: c.indentYAMLLines(data.Permissions, " "), + Permissions: c.indentYAMLLines(permissions, " "), Steps: steps, Needs: depends, Outputs: outputs, diff --git a/pkg/workflow/compiler_test.go b/pkg/workflow/compiler_test.go index b7ab335f592..2c42dad155a 100644 --- a/pkg/workflow/compiler_test.go +++ b/pkg/workflow/compiler_test.go @@ -4202,13 +4202,17 @@ This workflow should get default permissions applied automatically. lockContentStr := string(lockContent) // Verify that default permissions are present in the generated workflow + // This test workflow has explicit GitHub tools (tools.github.allowed), so it should + // get specific permissions including actions: write for self-cancellation, + // NOT the default "read-all" permissions expectedDefaultPermissions := []string{ - "read-all", + "actions: write", // Required for self-cancellation + "contents: read", // Basic read access } for _, expectedPerm := range expectedDefaultPermissions { if !strings.Contains(lockContentStr, expectedPerm) { - t.Errorf("Expected default permission '%s' not found in generated workflow.\nGenerated content:\n%s", expectedPerm, lockContentStr) + t.Errorf("Expected permission '%s' not found in generated workflow.\nGenerated content:\n%s", expectedPerm, lockContentStr) } } @@ -4255,13 +4259,25 @@ This workflow should get default permissions applied automatically. t.Fatal("Permissions section not found in main job") } - // Verify permissions is a map - permissionsValue, ok := permissions.(string) - if !ok { - t.Fatal("Permissions section is not a string") - } - if permissionsValue != "read-all" { - t.Fatal("Default permissions not read-all") + // Verify permissions is either a string or a map + if permissionsStr, ok := permissions.(string); ok { + // Handle string permissions (like "read-all") + if !strings.Contains(permissionsStr, "actions: write") { + t.Fatalf("Expected permissions to include 'actions: write' for workflows with explicit GitHub tools, got: %s", permissionsStr) + } + if !strings.Contains(permissionsStr, "contents: read") { + t.Fatalf("Expected permissions to include 'contents: read' for workflows with explicit GitHub tools, got: %s", permissionsStr) + } + } else if permissionsMap, ok := permissions.(map[string]interface{}); ok { + // Handle map permissions (YAML object) + if actionsVal, hasActions := permissionsMap["actions"]; !hasActions || actionsVal != "write" { + t.Fatalf("Expected permissions to include 'actions: write' for workflows with explicit GitHub tools, got: %v", permissionsMap) + } + if contentsVal, hasContents := permissionsMap["contents"]; !hasContents || contentsVal != "read" { + t.Fatalf("Expected permissions to include 'contents: read' for workflows with explicit GitHub tools, got: %v", permissionsMap) + } + } else { + t.Fatalf("Permissions section is neither a string nor a map, got type: %T, value: %v", permissions, permissions) } } diff --git a/pkg/workflow/js/check_permissions.cjs b/pkg/workflow/js/check_permissions.cjs index e2ec20060bb..f6559b276c2 100644 --- a/pkg/workflow/js/check_permissions.cjs +++ b/pkg/workflow/js/check_permissions.cjs @@ -1,3 +1,25 @@ +/** + * Custom setCancelled function that uses self-cancellation + * @param {string} message - The cancellation message + */ +async function setCancelled(message) { + try { + // Cancel the current workflow run using GitHub Actions API + await github.rest.actions.cancelWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId, + }); + + core.info(`Cancellation requested for this workflow run: ${message}`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + core.warning(`Failed to cancel workflow run: ${errorMessage}`); + // Fallback to core.setFailed if API call fails (since core.setCancelled doesn't exist in types) + core.setFailed(message); + } +} + async function main() { const { eventName } = context; @@ -19,7 +41,7 @@ async function main() { core.error( "❌ Configuration error: Required permissions not specified. Contact repository administrator." ); - core.setCancelled( + await setCancelled( "Configuration error: Required permissions not specified" ); return; @@ -60,7 +82,7 @@ async function main() { const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); core.error(`Repository permission check failed: ${errorMessage}`); - core.setCancelled(`Repository permission check failed: ${errorMessage}`); + await setCancelled(`Repository permission check failed: ${errorMessage}`); return; } @@ -68,8 +90,9 @@ async function main() { core.warning( `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); - core.setCancelled( + await setCancelled( `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}` ); + return; } await main(); diff --git a/pkg/workflow/js/check_permissions.test.cjs b/pkg/workflow/js/check_permissions.test.cjs index 4cfa98faf39..73916a72cce 100644 --- a/pkg/workflow/js/check_permissions.test.cjs +++ b/pkg/workflow/js/check_permissions.test.cjs @@ -10,6 +10,7 @@ const mockCore = { setFailed: vi.fn(), setCancelled: vi.fn(), setError: vi.fn(), + info: vi.fn(), }; const mockGithub = { @@ -17,6 +18,9 @@ const mockGithub = { repos: { getCollaboratorPermissionLevel: vi.fn(), }, + actions: { + cancelWorkflowRun: vi.fn(), + }, }, }; @@ -27,6 +31,7 @@ const mockContext = { owner: "testowner", repo: "testrepo", }, + runId: 12345, }; // Set up global variables diff --git a/pkg/workflow/js/check_team_member.cjs b/pkg/workflow/js/check_team_member.cjs index 3a342bae627..8610e78e901 100644 --- a/pkg/workflow/js/check_team_member.cjs +++ b/pkg/workflow/js/check_team_member.cjs @@ -1,3 +1,25 @@ +/** + * Custom setCancelled function that uses self-cancellation + * @param {string} message - The cancellation message + */ +async function setCancelled(message) { + try { + // Cancel the current workflow run using GitHub Actions API + await github.rest.actions.cancelWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId, + }); + + core.info(`Cancellation requested for this workflow run: ${message}`); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + core.warning(`Failed to cancel workflow run: ${errorMessage}`); + // Fallback to core.setFailed if API call fails (since core.setCancelled doesn't exist in types) + core.setFailed(message); + } +} + async function main() { const actor = context.actor; const { owner, repo } = context.repo; @@ -29,6 +51,16 @@ async function main() { core.warning(`Repository permission check failed: ${errorMessage}`); } + // Team membership check failed - use self-cancellation + const failureMessage = `Access denied: User '${actor}' is not authorized to trigger this workflow. Only admin or maintainer users can run this workflow.`; + core.warning(`❌ ${failureMessage}`); + + await setCancelled(failureMessage); + + // Set output for any dependent steps that might check before cancellation takes effect core.setOutput("is_team_member", "false"); + + // Return to finish the script + return; } await main(); diff --git a/pkg/workflow/js/check_team_member.test.cjs b/pkg/workflow/js/check_team_member.test.cjs index ad3a5db524f..e4a9017de50 100644 --- a/pkg/workflow/js/check_team_member.test.cjs +++ b/pkg/workflow/js/check_team_member.test.cjs @@ -7,6 +7,9 @@ const mockCore = { setOutput: vi.fn(), warning: vi.fn(), error: vi.fn(), + info: vi.fn(), + setCancelled: vi.fn(), + setFailed: vi.fn(), }; const mockGithub = { @@ -14,6 +17,9 @@ const mockGithub = { repos: { getCollaboratorPermissionLevel: vi.fn(), }, + actions: { + cancelWorkflowRun: vi.fn(), + }, }, }; @@ -23,6 +29,7 @@ const mockContext = { owner: "testowner", repo: "testrepo", }, + runId: 12345, }; // Set up global variables @@ -145,11 +152,13 @@ describe("check_team_member.cjs", () => { consoleSpy.mockRestore(); }); - it("should set is_team_member to false for read permission", async () => { + it("should use self-cancellation when team membership fails", async () => { mockGithub.rest.repos.getCollaboratorPermissionLevel.mockResolvedValue({ data: { permission: "read" }, }); + mockGithub.rest.actions.cancelWorkflowRun.mockResolvedValue({}); + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); // Execute the script @@ -169,6 +178,64 @@ describe("check_team_member.cjs", () => { expect(consoleSpy).toHaveBeenCalledWith( "Repository permission level: read" ); + + // Check that self-cancellation was attempted + expect(mockGithub.rest.actions.cancelWorkflowRun).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + run_id: 12345, + }); + + expect(mockCore.info).toHaveBeenCalledWith( + expect.stringContaining("Cancellation requested for this workflow run") + ); + expect(mockCore.warning).toHaveBeenCalledWith( + expect.stringContaining( + "Access denied: User 'testuser' is not authorized" + ) + ); + expect(mockCore.setOutput).toHaveBeenCalledWith("is_team_member", "false"); + + consoleSpy.mockRestore(); + }); + + it("should fallback to core.setCancelled when API call fails", async () => { + mockGithub.rest.repos.getCollaboratorPermissionLevel.mockResolvedValue({ + data: { permission: "read" }, + }); + + const apiError = new Error("API Error: Forbidden"); + mockGithub.rest.actions.cancelWorkflowRun.mockRejectedValue(apiError); + + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + // Execute the script + await eval(`(async () => { ${checkTeamMemberScript} })()`); + + expect( + mockGithub.rest.repos.getCollaboratorPermissionLevel + ).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + username: "testuser", + }); + + // Check that self-cancellation was attempted but failed + expect(mockGithub.rest.actions.cancelWorkflowRun).toHaveBeenCalledWith({ + owner: "testowner", + repo: "testrepo", + run_id: 12345, + }); + + // Check that fallback occurred + expect(mockCore.warning).toHaveBeenCalledWith( + "Failed to cancel workflow run: API Error: Forbidden" + ); + expect(mockCore.setCancelled).toHaveBeenCalledWith( + expect.stringContaining( + "Access denied: User 'testuser' is not authorized" + ) + ); expect(mockCore.setOutput).toHaveBeenCalledWith("is_team_member", "false"); consoleSpy.mockRestore(); diff --git a/pkg/workflow/output_test.go b/pkg/workflow/output_test.go index 6a712147c3d..bf1237e7fe8 100644 --- a/pkg/workflow/output_test.go +++ b/pkg/workflow/output_test.go @@ -261,10 +261,15 @@ This workflow tests the create-issue job generation. t.Error("Expected 10-minute timeout in create_issue job") } - if !strings.Contains(lockContent, "permissions:\n contents: read\n issues: write") { + if !strings.Contains(lockContent, "permissions:") || !strings.Contains(lockContent, "contents: read") || !strings.Contains(lockContent, "issues: write") { t.Error("Expected correct permissions in create_issue job") } + // Since this workflow has explicit GitHub tools, it should also have actions: write for self-cancellation + if !strings.Contains(lockContent, "actions: write") { + t.Error("Expected actions: write permission in create_issue job for workflows with explicit GitHub tools") + } + // Verify the job uses github-script if !strings.Contains(lockContent, "uses: actions/github-script@v7") { t.Error("Expected github-script action to be used in create_issue job")