Skip to content

Commit 06e62e3

Browse files
Fix Malicious Site Protection Duplicate PRs (#3136)
* Add rate-limiting to prevent getting blocked by bot detection. * Add check-for-duplicates step to workflow to prevent duplicate PRs being created. * Lint fix. * Fix yaml syntax. * Remove duplicate check, introduce PR ID instead. * Use PR_ID in branch to ensure no duplication. * Add test branch to test PR creation with ID * Update test branch typo. * Remove comment from yaml. * Update triggers for testing. * Update peter-evans/create-pull-request version * Revert testing workflow configs - ready for merge. * Update cron.
1 parent 5e8919b commit 06e62e3

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

‎.github/workflows/msp-filter-stale.yml‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ jobs:
2929
echo "TEMPLATE=$TEMPLATE" >> $GITHUB_ENV
3030
PR_BODY="${TEMPLATE//\{\{date\}\}/$DATE}"
3131
PR_BODY="${PR_BODY//\\n/$'\n'}" # Replace escaped newlines with actual newlines
32+
PR_ID="$(echo "$PR_BODY" | grep 'PR ID: ' | cut -d' ' -f3)"
33+
echo "PR_ID=$PR_ID" >> $GITHUB_ENV
3234
echo "PR_BODY<<EOF" >> $GITHUB_ENV
3335
echo "$PR_BODY" >> $GITHUB_ENV
3436
echo "EOF" >> $GITHUB_ENV
3537
npm run lint-fix
3638
- name: Create PR with updated configs
37-
uses: peter-evans/create-pull-request@88bf0de51c7487d91e1abbb4899332e602c58bbf
39+
uses: peter-evans/create-pull-request@889dce9eaba7900ce30494f5e1ac7220b27e5c81
3840
id: create-pr
3941
with:
4042
github-token: ${{ secrets.GITHUB_TOKEN }}
@@ -43,7 +45,7 @@ jobs:
4345
./
4446
commit-message: Remove stale exemptions from malicious site protection
4547
assignees: not-a-rootkit
46-
branch: remove-stale-exemptions-${{ env.DATE }}
48+
branch: remove-stale-exemptions-${{ env.PR_ID }}
4749
title: Remove stale exemptions from malicious site protection - ${{ env.DATE }}
4850
body: |
4951
${{ env.PR_BODY }}

‎scripts/filter-malicious.js‎

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,35 @@ const fs = require('fs').promises;
22
const path = require('path');
33
const fetch = require('node-fetch').default;
44
const crypto = require('crypto');
5+
const _ = require('lodash'); // Add this import
6+
7+
// Rate limit fetch to 30 requests per second using a promise queue
8+
// https://thoughtspile.github.io/2018/07/07/rate-limit-promises/
9+
const resolveAfter = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10+
11+
function rateLimit1(fn, msPerOp) {
12+
let wait = Promise.resolve();
13+
return (...a) => {
14+
const res = wait.then(() => fn(...a));
15+
wait = wait.then(() => resolveAfter(msPerOp));
16+
return res;
17+
};
18+
}
19+
20+
function rateLimit(fn, windowMs, reqInWindow = 1) {
21+
// A battery of 1-rate-limiters
22+
const queue = _.range(reqInWindow).map(() => rateLimit1(fn, windowMs));
23+
// Circular queue cursor
24+
let i = 0;
25+
return (...a) => {
26+
// to enqueue, we move the cursor...
27+
i = (i + 1) % reqInWindow;
28+
// and return the rate-limited operation.
29+
return queue[i](...a);
30+
};
31+
}
32+
33+
const slowFetch = rateLimit(fetch, 1000, 30);
534

635
class ConfigProcessor {
736
constructor(options = {}) {
@@ -47,7 +76,12 @@ class ConfigProcessor {
4776
const hashPrefix = this.generateHashPrefix(domain);
4877
const url = `${this.apiBaseUrl}/${platform}/matches?hashPrefix=${hashPrefix}`;
4978
try {
50-
const response = await fetch(url);
79+
const response = await slowFetch(url, {
80+
headers: {
81+
'User-Agent':
82+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3.1 Safari/605.1.15 Ddg/18.3.1',
83+
},
84+
});
5185
if (!response.ok) return false;
5286

5387
const data = await response.json();
@@ -101,6 +135,11 @@ class ConfigProcessor {
101135
removedDomains.forEach((domain) => {
102136
prBody += ` - ${domain}\\n`;
103137
});
138+
139+
// PR ID is a first half of the hash of the sorted list of removed domains
140+
const prId = crypto.createHash('sha256').update(Array.from(removedDomains).sort().join(',')).digest('hex').slice(0, 16);
141+
prBody += `\\nPR ID: ${prId}\\n`;
142+
prBody += `\\nThis PR is auto-generated. Please do not edit it manually.`;
104143
console.log(prBody);
105144
}
106145

0 commit comments

Comments
 (0)