ci: add rate limits for issues and prs (#66265)

This commit is contained in:
2^1 2026-02-10 17:48:40 +01:00 • committed by GitHub
commit 4166311daf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 182 additions and 0 deletions

91
.github/workflows/issue-rate-limit.yml vendored Normal file
View file

@ -0,0 +1,91 @@
name: Issue rate limit
on:
issues:
types: [opened, reopened]
permissions:
issues: write
contents: read
jobs:
limit:
runs-on: ubuntu-latest
env:
BLOCK_USER_ENV: ${{ secrets.BLOCK_USER_ENV }}
steps:
- uses: actions/github-script@v7
with:
script: |
const timeLimitMinutes = 30;
const blockThreshold = 10;
const skipMaintainers = true;
const skipBots = true;
const author = context.payload.issue.user.login;
const authorType = context.payload.issue.user.type;
const currentIssue = context.payload.issue.number;
const since = new Date(Date.now() - timeLimitMinutes * 60 * 1000).toISOString();
const blockToken = process.env.BLOCK_USER_ENV || "";
if (skipBots && (authorType === "Bot" || author === "github-bot")) {
core.info(`Skipping rate limit for bot account: ${author}`);
return;
}
const permission = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: author
});
if (skipMaintainers && ["admin", "maintain", "write"].includes(permission.data.permission)) {
core.info(`Skipping rate limit for maintainer: ${author}`);
return;
}
const searchQuery = `repo:${context.repo.owner}/${context.repo.repo} is:issue author:${author} created:>=${since}`;
const { search } = await github.graphql(
`query($searchQuery: String!) {
search(type: ISSUE, query: $searchQuery, first: 1) {
issueCount
}
}`,
{ searchQuery }
);
const count = search.issueCount;
core.info(`Issues in last ${timeLimitMinutes} min by ${author}: ${count}`);
if (count > 5) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: currentIssue,
body: `🚫 Rate limit reached: Please wait a while, before creating more issues.`
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: currentIssue,
state: "closed"
});
}
if (count > blockThreshold) {
if (!blockToken) {
core.warning(`Missing BLOCK_USER_ENV token. Skipping block for ${author}.`);
return;
}
const { getOctokit } = require("@actions/github");
const blockClient = getOctokit(blockToken);
await blockClient.rest.users.block({
username: author
});
core.info(`Blocked user ${author} after ${count} issues in ${timeLimitMinutes} min.`);
}

91
.github/workflows/pr-rate-limit.yml vendored Normal file
View file

@ -0,0 +1,91 @@
name: PR rate limit
on:
pull_request:
types: [opened, reopened]
permissions:
pull-requests: write
contents: read
jobs:
limit:
runs-on: ubuntu-latest
env:
BLOCK_USER_ENV: ${{ secrets.BLOCK_USER_ENV }}
steps:
- uses: actions/github-script@v7
with:
script: |
const timeLimitMinutes = 30;
const blockThreshold = 10;
const skipMaintainers = true;
const skipBots = true;
const author = context.payload.pull_request.user.login;
const authorType = context.payload.pull_request.user.type;
const currentPr = context.payload.pull_request.number;
const since = new Date(Date.now() - timeLimitMinutes * 60 * 1000).toISOString();
const blockToken = process.env.BLOCK_USER_ENV || "";
if (skipBots && (authorType === "Bot" || author === "github-bot")) {
core.info(`Skipping rate limit for bot account: ${author}`);
return;
}
const permission = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: author
});
if (skipMaintainers && ["admin", "maintain", "write"].includes(permission.data.permission)) {
core.info(`Skipping rate limit for maintainer: ${author}`);
return;
}
const searchQuery = `repo:${context.repo.owner}/${context.repo.repo} is:pr author:${author} created:>=${since}`;
const { search } = await github.graphql(
`query($searchQuery: String!) {
search(type: ISSUE, query: $searchQuery, first: 1) {
issueCount
}
}`,
{ searchQuery }
);
const count = search.issueCount;
core.info(`PRs in last ${timeLimitMinutes} min by ${author}: ${count}`);
if (count > 5) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: currentPr,
body: `🚫 Rate limit reached: Please wait a while, before creating more pull requests.`
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: currentPr,
state: "closed"
});
}
if (count > blockThreshold) {
if (!blockToken) {
core.warning(`Missing BLOCK_USER_ENV token. Skipping block for ${author}.`);
return;
}
const { getOctokit } = require("@actions/github");
const blockClient = getOctokit(blockToken);
await blockClient.rest.users.block({
username: author
});
core.info(`Blocked user ${author} after ${count} PRs in ${timeLimitMinutes} min.`);
}