From db09ce04b1b2aeef069ab489308f1af03c7c8784 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:06:52 +0000 Subject: [PATCH 1/6] Initial plan From 4300b87e7050160a580c03918104c07f1565142d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:10:17 +0000 Subject: [PATCH 2/6] Initial plan for pagination implementation Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- .github/workflows/bot-detection.lock.yml | 8 ++------ .github/workflows/smoke-copilot.lock.yml | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/bot-detection.lock.yml b/.github/workflows/bot-detection.lock.yml index d750ac7b9dd..0316b6fa936 100644 --- a/.github/workflows/bot-detection.lock.yml +++ b/.github/workflows/bot-detection.lock.yml @@ -21,7 +21,7 @@ # # Investigates suspicious repository activity and maintains a single triage issue # -# frontmatter-hash: 4c52fe089425a05e8f0c3eb417d76791e70457a80961e818e7ab24ce0b7eb5f1 +# frontmatter-hash: f54859bebf7a13ba4572c7b7a7517c28034a4b338ff8332d2a311c31a425daa4 name: "Bot Detection" "on": @@ -1018,10 +1018,6 @@ jobs: return new Date(d).toISOString(); } - function safeLower(s) { - return (s || "").toString().toLowerCase(); - } - function normalizeForDup(s) { return (s || "") .toString() @@ -1285,7 +1281,7 @@ jobs: } // PR file touches (sensitive paths) - deterministic and bounded - for (const it of items.filter(i => i.is_pr)) { + for (const it of prItems) { const login = it.author; if (!login) continue; const s = ensureAuthor(login); diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index bb8eb9abb82..156d988faf1 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -37,7 +37,7 @@ name: "Smoke Copilot" types: - labeled schedule: - - cron: "37 */12 * * *" + - cron: "46 */12 * * *" workflow_dispatch: null permissions: {} From e11c6e6b95b5142cfff56758242cc3ab69f5f114 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:12:42 +0000 Subject: [PATCH 3/6] Use github.paginate for all PR/issue API calls with 500-item cap Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- .github/workflows/bot-detection.lock.yml | 83 ++++++++++++------- .github/workflows/bot-detection.md | 81 +++++++++++------- .../js/merge_remote_agent_github_folder.cjs | 14 ++-- 3 files changed, 114 insertions(+), 64 deletions(-) diff --git a/.github/workflows/bot-detection.lock.yml b/.github/workflows/bot-detection.lock.yml index 0316b6fa936..7ac58f104aa 100644 --- a/.github/workflows/bot-detection.lock.yml +++ b/.github/workflows/bot-detection.lock.yml @@ -21,7 +21,7 @@ # # Investigates suspicious repository activity and maintains a single triage issue # -# frontmatter-hash: f54859bebf7a13ba4572c7b7a7517c28034a4b338ff8332d2a311c31a425daa4 +# frontmatter-hash: 0bac2c340b44ea7294a8fda4f2dd2a465985b67136517651ad01a0e9e3192397 name: "Bot Detection" "on": @@ -1205,39 +1205,57 @@ jobs: let issueComments = []; try { - const { data } = await github.rest.issues.listComments({ - owner, - repo, - issue_number: it.number, - per_page: 100, - }); - issueComments = data || []; + issueComments = await github.paginate( + github.rest.issues.listComments, + { + owner, + repo, + issue_number: it.number, + per_page: 100, + }, + (response, done) => { + if (response.data.length >= 500) done(); + return response.data; + } + ); } catch { // ignore } let reviewComments = []; try { - const { data } = await github.rest.pulls.listReviewComments({ - owner, - repo, - pull_number: it.number, - per_page: 100, - }); - reviewComments = data || []; + reviewComments = await github.paginate( + github.rest.pulls.listReviewComments, + { + owner, + repo, + pull_number: it.number, + per_page: 100, + }, + (response, done) => { + if (response.data.length >= 500) done(); + return response.data; + } + ); } catch { // ignore } let reviews = []; try { - const { data } = await github.rest.pulls.listReviews({ - owner, - repo, - pull_number: it.number, - per_page: 100, - }); - reviews = data || []; + reviews = await github.paginate( + github.rest.pulls.listReviews, + { + owner, + repo, + pull_number: it.number, + per_page: 100, + }, + (response, done) => { + if (response.data.length >= 500) done(); + return response.data; + } + ); } catch { // ignore } @@ -1287,13 +1305,20 @@ jobs: const s = ensureAuthor(login); try { - const filesResp = await github.rest.pulls.listFiles({ - owner, - repo, - pull_number: it.number, - per_page: 100, - }); - const filenames = (filesResp.data || []).map(f => f.filename); + const files = await github.paginate( + github.rest.pulls.listFiles, + { + owner, + repo, + pull_number: it.number, + per_page: 100, + }, + (response, done) => { + if (response.data.length >= 500) done(); + return response.data; + } + ); + const filenames = files.map(f => f.filename); for (const fn of filenames) { if (fn.startsWith(".github/workflows/") || fn.startsWith(".github/actions/")) s.touchesWorkflows = true; if (fn === "Dockerfile" || fn === "Makefile" || fn.startsWith("scripts/") || fn.startsWith("actions/")) s.touchesCI = true; diff --git a/.github/workflows/bot-detection.md b/.github/workflows/bot-detection.md index a5957d68000..a5bcf6b2528 100644 --- a/.github/workflows/bot-detection.md +++ b/.github/workflows/bot-detection.md @@ -232,39 +232,57 @@ jobs: let issueComments = []; try { - const { data } = await github.rest.issues.listComments({ - owner, - repo, - issue_number: it.number, - per_page: 100, - }); - issueComments = data || []; + issueComments = await github.paginate( + github.rest.issues.listComments, + { + owner, + repo, + issue_number: it.number, + per_page: 100, + }, + (response, done) => { + if (response.data.length >= 500) done(); + return response.data; + } + ); } catch { // ignore } let reviewComments = []; try { - const { data } = await github.rest.pulls.listReviewComments({ - owner, - repo, - pull_number: it.number, - per_page: 100, - }); - reviewComments = data || []; + reviewComments = await github.paginate( + github.rest.pulls.listReviewComments, + { + owner, + repo, + pull_number: it.number, + per_page: 100, + }, + (response, done) => { + if (response.data.length >= 500) done(); + return response.data; + } + ); } catch { // ignore } let reviews = []; try { - const { data } = await github.rest.pulls.listReviews({ - owner, - repo, - pull_number: it.number, - per_page: 100, - }); - reviews = data || []; + reviews = await github.paginate( + github.rest.pulls.listReviews, + { + owner, + repo, + pull_number: it.number, + per_page: 100, + }, + (response, done) => { + if (response.data.length >= 500) done(); + return response.data; + } + ); } catch { // ignore } @@ -314,13 +332,20 @@ jobs: const s = ensureAuthor(login); try { - const filesResp = await github.rest.pulls.listFiles({ - owner, - repo, - pull_number: it.number, - per_page: 100, - }); - const filenames = (filesResp.data || []).map(f => f.filename); + const files = await github.paginate( + github.rest.pulls.listFiles, + { + owner, + repo, + pull_number: it.number, + per_page: 100, + }, + (response, done) => { + if (response.data.length >= 500) done(); + return response.data; + } + ); + const filenames = files.map(f => f.filename); for (const fn of filenames) { if (fn.startsWith(".github/workflows/") || fn.startsWith(".github/actions/")) s.touchesWorkflows = true; if (fn === "Dockerfile" || fn === "Makefile" || fn.startsWith("scripts/") || fn.startsWith("actions/")) s.touchesCI = true; diff --git a/actions/setup/js/merge_remote_agent_github_folder.cjs b/actions/setup/js/merge_remote_agent_github_folder.cjs index 6f843a36ac2..77a0a58356d 100644 --- a/actions/setup/js/merge_remote_agent_github_folder.cjs +++ b/actions/setup/js/merge_remote_agent_github_folder.cjs @@ -146,23 +146,23 @@ function validateGitParameter(value, name) { */ function validateSafePath(userPath, basePath, name) { // Reject paths with null bytes - if (userPath.includes('\0')) { + if (userPath.includes("\0")) { throw new Error(`Invalid ${name}: contains null bytes`); } - + // Reject paths that attempt to traverse up (..) - if (userPath.includes('..')) { + if (userPath.includes("..")) { throw new Error(`Invalid ${name}: path traversal detected`); } - + // Resolve the full path and ensure it's within the base path const resolvedPath = path.resolve(basePath, userPath); const resolvedBase = path.resolve(basePath); - + if (!resolvedPath.startsWith(resolvedBase + path.sep) && resolvedPath !== resolvedBase) { throw new Error(`Invalid ${name}: path escapes base directory`); } - + return resolvedPath; } @@ -239,7 +239,7 @@ function mergeGithubFolder(sourcePath, destPath) { for (const relativePath of sourceFiles) { // Validate relative path to prevent path traversal validateSafePath(relativePath, sourcePath, "relative file path"); - + // Check if the file is in one of the allowed subfolders const pathParts = relativePath.split(path.sep); const topLevelFolder = pathParts[0]; From 93c8d673e5a08f543c4c1444818450982fd009e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:13:38 +0000 Subject: [PATCH 4/6] Fix pagination cap logic to track total accumulated items Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- .github/workflows/bot-detection.lock.yml | 18 +++++++++++++----- .github/workflows/bot-detection.md | 16 ++++++++++++---- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/bot-detection.lock.yml b/.github/workflows/bot-detection.lock.yml index 7ac58f104aa..7a769204748 100644 --- a/.github/workflows/bot-detection.lock.yml +++ b/.github/workflows/bot-detection.lock.yml @@ -21,7 +21,7 @@ # # Investigates suspicious repository activity and maintains a single triage issue # -# frontmatter-hash: 0bac2c340b44ea7294a8fda4f2dd2a465985b67136517651ad01a0e9e3192397 +# frontmatter-hash: 0c356641b3006617f23fd6e9f85cdcbf37e8c31bb3d5e42bdc61851d0fae913a name: "Bot Detection" "on": @@ -1205,6 +1205,7 @@ jobs: let issueComments = []; try { + let total = 0; issueComments = await github.paginate( github.rest.issues.listComments, { @@ -1214,7 +1215,8 @@ jobs: per_page: 100, }, (response, done) => { - if (response.data.length >= 500) done(); + total += response.data.length; + if (total >= 500) done(); return response.data; } ); @@ -1224,6 +1226,7 @@ jobs: let reviewComments = []; try { + let total = 0; reviewComments = await github.paginate( github.rest.pulls.listReviewComments, { @@ -1233,7 +1236,8 @@ jobs: per_page: 100, }, (response, done) => { - if (response.data.length >= 500) done(); + total += response.data.length; + if (total >= 500) done(); return response.data; } ); @@ -1243,6 +1247,7 @@ jobs: let reviews = []; try { + let total = 0; reviews = await github.paginate( github.rest.pulls.listReviews, { @@ -1252,7 +1257,8 @@ jobs: per_page: 100, }, (response, done) => { - if (response.data.length >= 500) done(); + total += response.data.length; + if (total >= 500) done(); return response.data; } ); @@ -1305,6 +1311,7 @@ jobs: const s = ensureAuthor(login); try { + let total = 0; const files = await github.paginate( github.rest.pulls.listFiles, { @@ -1314,7 +1321,8 @@ jobs: per_page: 100, }, (response, done) => { - if (response.data.length >= 500) done(); + total += response.data.length; + if (total >= 500) done(); return response.data; } ); diff --git a/.github/workflows/bot-detection.md b/.github/workflows/bot-detection.md index a5bcf6b2528..b7ec1a78e2b 100644 --- a/.github/workflows/bot-detection.md +++ b/.github/workflows/bot-detection.md @@ -232,6 +232,7 @@ jobs: let issueComments = []; try { + let total = 0; issueComments = await github.paginate( github.rest.issues.listComments, { @@ -241,7 +242,8 @@ jobs: per_page: 100, }, (response, done) => { - if (response.data.length >= 500) done(); + total += response.data.length; + if (total >= 500) done(); return response.data; } ); @@ -251,6 +253,7 @@ jobs: let reviewComments = []; try { + let total = 0; reviewComments = await github.paginate( github.rest.pulls.listReviewComments, { @@ -260,7 +263,8 @@ jobs: per_page: 100, }, (response, done) => { - if (response.data.length >= 500) done(); + total += response.data.length; + if (total >= 500) done(); return response.data; } ); @@ -270,6 +274,7 @@ jobs: let reviews = []; try { + let total = 0; reviews = await github.paginate( github.rest.pulls.listReviews, { @@ -279,7 +284,8 @@ jobs: per_page: 100, }, (response, done) => { - if (response.data.length >= 500) done(); + total += response.data.length; + if (total >= 500) done(); return response.data; } ); @@ -332,6 +338,7 @@ jobs: const s = ensureAuthor(login); try { + let total = 0; const files = await github.paginate( github.rest.pulls.listFiles, { @@ -341,7 +348,8 @@ jobs: per_page: 100, }, (response, done) => { - if (response.data.length >= 500) done(); + total += response.data.length; + if (total >= 500) done(); return response.data; } ); From c1e5918288cb1eb50627744d2e8cd062e85f0889 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:15:07 +0000 Subject: [PATCH 5/6] Enforce exact 500-item cap by slicing response data Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- .github/workflows/bot-detection.lock.yml | 42 +++++++++++++++++++++--- .github/workflows/bot-detection.md | 40 +++++++++++++++++++--- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/.github/workflows/bot-detection.lock.yml b/.github/workflows/bot-detection.lock.yml index 7a769204748..24da0cc3ee4 100644 --- a/.github/workflows/bot-detection.lock.yml +++ b/.github/workflows/bot-detection.lock.yml @@ -21,7 +21,7 @@ # # Investigates suspicious repository activity and maintains a single triage issue # -# frontmatter-hash: 0c356641b3006617f23fd6e9f85cdcbf37e8c31bb3d5e42bdc61851d0fae913a +# frontmatter-hash: 5a2aa2efd24123412162d29a03e86cd5d7708172ea7fc071192e38713f6d3537 name: "Bot Detection" "on": @@ -1215,8 +1215,16 @@ jobs: per_page: 100, }, (response, done) => { + const remaining = 500 - total; + if (remaining <= 0) { + done(); + return []; + } total += response.data.length; - if (total >= 500) done(); + if (total >= 500) { + done(); + return response.data.slice(0, remaining); + } return response.data; } ); @@ -1236,8 +1244,16 @@ jobs: per_page: 100, }, (response, done) => { + const remaining = 500 - total; + if (remaining <= 0) { + done(); + return []; + } total += response.data.length; - if (total >= 500) done(); + if (total >= 500) { + done(); + return response.data.slice(0, remaining); + } return response.data; } ); @@ -1257,8 +1273,16 @@ jobs: per_page: 100, }, (response, done) => { + const remaining = 500 - total; + if (remaining <= 0) { + done(); + return []; + } total += response.data.length; - if (total >= 500) done(); + if (total >= 500) { + done(); + return response.data.slice(0, remaining); + } return response.data; } ); @@ -1321,8 +1345,16 @@ jobs: per_page: 100, }, (response, done) => { + const remaining = 500 - total; + if (remaining <= 0) { + done(); + return []; + } total += response.data.length; - if (total >= 500) done(); + if (total >= 500) { + done(); + return response.data.slice(0, remaining); + } return response.data; } ); diff --git a/.github/workflows/bot-detection.md b/.github/workflows/bot-detection.md index b7ec1a78e2b..d5b968b29f5 100644 --- a/.github/workflows/bot-detection.md +++ b/.github/workflows/bot-detection.md @@ -242,8 +242,16 @@ jobs: per_page: 100, }, (response, done) => { + const remaining = 500 - total; + if (remaining <= 0) { + done(); + return []; + } total += response.data.length; - if (total >= 500) done(); + if (total >= 500) { + done(); + return response.data.slice(0, remaining); + } return response.data; } ); @@ -263,8 +271,16 @@ jobs: per_page: 100, }, (response, done) => { + const remaining = 500 - total; + if (remaining <= 0) { + done(); + return []; + } total += response.data.length; - if (total >= 500) done(); + if (total >= 500) { + done(); + return response.data.slice(0, remaining); + } return response.data; } ); @@ -284,8 +300,16 @@ jobs: per_page: 100, }, (response, done) => { + const remaining = 500 - total; + if (remaining <= 0) { + done(); + return []; + } total += response.data.length; - if (total >= 500) done(); + if (total >= 500) { + done(); + return response.data.slice(0, remaining); + } return response.data; } ); @@ -348,8 +372,16 @@ jobs: per_page: 100, }, (response, done) => { + const remaining = 500 - total; + if (remaining <= 0) { + done(); + return []; + } total += response.data.length; - if (total >= 500) done(); + if (total >= 500) { + done(); + return response.data.slice(0, remaining); + } return response.data; } ); From 0b2adb3a82cc858516b4388b85718ca46e80abf3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:16:24 +0000 Subject: [PATCH 6/6] Fix pagination logic: check total before updating, use remaining correctly Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- .github/workflows/bot-detection.lock.yml | 22 +++++++++++++--------- .github/workflows/bot-detection.md | 20 ++++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/.github/workflows/bot-detection.lock.yml b/.github/workflows/bot-detection.lock.yml index 24da0cc3ee4..1583e368877 100644 --- a/.github/workflows/bot-detection.lock.yml +++ b/.github/workflows/bot-detection.lock.yml @@ -21,7 +21,7 @@ # # Investigates suspicious repository activity and maintains a single triage issue # -# frontmatter-hash: 5a2aa2efd24123412162d29a03e86cd5d7708172ea7fc071192e38713f6d3537 +# frontmatter-hash: 2178e3732b12824d02944782c3e73dbab22b3c402a400b320ac94f7f77cdb68d name: "Bot Detection" "on": @@ -1220,11 +1220,12 @@ jobs: done(); return []; } - total += response.data.length; - if (total >= 500) { + if (total + response.data.length >= 500) { + total = 500; done(); return response.data.slice(0, remaining); } + total += response.data.length; return response.data; } ); @@ -1249,11 +1250,12 @@ jobs: done(); return []; } - total += response.data.length; - if (total >= 500) { + if (total + response.data.length >= 500) { + total = 500; done(); return response.data.slice(0, remaining); } + total += response.data.length; return response.data; } ); @@ -1278,11 +1280,12 @@ jobs: done(); return []; } - total += response.data.length; - if (total >= 500) { + if (total + response.data.length >= 500) { + total = 500; done(); return response.data.slice(0, remaining); } + total += response.data.length; return response.data; } ); @@ -1350,11 +1353,12 @@ jobs: done(); return []; } - total += response.data.length; - if (total >= 500) { + if (total + response.data.length >= 500) { + total = 500; done(); return response.data.slice(0, remaining); } + total += response.data.length; return response.data; } ); diff --git a/.github/workflows/bot-detection.md b/.github/workflows/bot-detection.md index d5b968b29f5..ba4b049463c 100644 --- a/.github/workflows/bot-detection.md +++ b/.github/workflows/bot-detection.md @@ -247,11 +247,12 @@ jobs: done(); return []; } - total += response.data.length; - if (total >= 500) { + if (total + response.data.length >= 500) { + total = 500; done(); return response.data.slice(0, remaining); } + total += response.data.length; return response.data; } ); @@ -276,11 +277,12 @@ jobs: done(); return []; } - total += response.data.length; - if (total >= 500) { + if (total + response.data.length >= 500) { + total = 500; done(); return response.data.slice(0, remaining); } + total += response.data.length; return response.data; } ); @@ -305,11 +307,12 @@ jobs: done(); return []; } - total += response.data.length; - if (total >= 500) { + if (total + response.data.length >= 500) { + total = 500; done(); return response.data.slice(0, remaining); } + total += response.data.length; return response.data; } ); @@ -377,11 +380,12 @@ jobs: done(); return []; } - total += response.data.length; - if (total >= 500) { + if (total + response.data.length >= 500) { + total = 500; done(); return response.data.slice(0, remaining); } + total += response.data.length; return response.data; } );