Skip to content
Closed
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
100 changes: 100 additions & 0 deletions .github/workflows/close-duplicate-effort-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Close Duplicate Effort PRs

on:
pull_request_target:
types: [opened]

jobs:
close-if-issue-assigned:
runs-on: ubuntu-latest
timeout-minutes: 5

Check warning on line 10 in .github/workflows/close-duplicate-effort-prs.yml

View check run for this annotation

@sentry/warden / warden: code-review

Missing explicit permissions on pull_request_target workflow

The workflow uses `pull_request_target` trigger but doesn't specify explicit `permissions`. With `pull_request_target`, the workflow runs with the permissions of the base repository, not the fork. Without explicit permission restrictions, this workflow may have broader access than needed (e.g., contents: write, packages: write, etc.). While this specific workflow only needs `pull-requests: write` and `issues: read`, it may inherit broader default permissions.
Comment on lines +4 to +10
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing explicit permissions on pull_request_target workflow

The workflow uses pull_request_target trigger but doesn't specify explicit permissions. With pull_request_target, the workflow runs with the permissions of the base repository, not the fork. Without explicit permission restrictions, this workflow may have broader access than needed (e.g., contents: write, packages: write, etc.). While this specific workflow only needs pull-requests: write and issues: read, it may inherit broader default permissions.

Verification

Verified by reading the workflow file which shows no permissions: block. Compared to changelog-preview.yml in the same repo which does specify explicit permissions for its pull_request_target workflow. The GitHub documentation recommends always specifying minimal permissions for pull_request_target workflows.

Suggested fix: Add explicit permissions to limit the workflow's access to only what's needed

Suggested change
pull_request_target:
types: [opened]
jobs:
close-if-issue-assigned:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
pull-requests: write
issues: read

Identified by Warden code-review · GZP-9D3


steps:
- name: Check org membership and referenced issues
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const prAuthor = context.payload.pull_request.user.login;
const prNumber = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;

// Check if PR author is a member of the getsentry org
try {
await github.rest.orgs.checkMembershipForUser({
org: 'getsentry',
username: prAuthor,
});
console.log(`${prAuthor} is a member of getsentry, skipping.`);
return;
} catch (error) {
// 404 means not a member, which is what we want to continue with
if (error.status !== 404) {
throw error;
}
console.log(`${prAuthor} is not a member of getsentry.`);
}

// Extract issue references from PR title and body
const prTitle = context.payload.pull_request.title || '';
const prBody = context.payload.pull_request.body || '';
const text = `${prTitle}\n${prBody}`;

// Match patterns like #123, fixes #123, closes #123, resolves #123,
// as well as full URL references like github.com/owner/repo/issues/123
const issuePattern = /(?:(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?)\s*:?\s*)?(?:https?:\/\/github\.com\/[\w.-]+\/[\w.-]+\/issues\/(\d+)|#(\d+))/gi;

const issueNumbers = new Set();
let match;
while ((match = issuePattern.exec(text)) !== null) {
const num = match[1] || match[2];
issueNumbers.add(parseInt(num, 10));
}

if (issueNumbers.size === 0) {
console.log('No issue references found in PR, skipping.');
return;
}

console.log(`Found referenced issues: ${[...issueNumbers].join(', ')}`);

// Check if any referenced issue has an assignee
for (const issueNumber of issueNumbers) {
let issue;
try {
const response = await github.rest.issues.get({
owner,
repo,
issue_number: issueNumber,
});
issue = response.data;
} catch (error) {
console.log(`Could not fetch issue #${issueNumber}: ${error.message}`);
continue;
}

if (issue.assignees && issue.assignees.length > 0) {
const assignees = issue.assignees.map(a => a.login).join(', ');
console.log(`Issue #${issueNumber} is assigned to: ${assignees}. Closing PR.`);

// Add a comment explaining why the PR is being closed
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: `Hey @${prAuthor}, thanks for contributing!\n\nIt looks like the issue (#${issueNumber}) you're addressing in this pull request already has someone assigned to it. Please check on the issue whether help is wanted before opening a pull request.\n\nIf you believe your contribution is still valuable, please leave a comment on the issue to coordinate with the assignee.`,
});

// Close the PR
await github.rest.pulls.update({
owner,
repo,
pull_number: prNumber,
state: 'closed',
});

return;
}
}

console.log('No referenced issues have assignees, PR is fine.');
Loading