From 2e1791c86b06ae742baa7ef45a1627f791492141 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Apr 2026 22:16:14 +0000
Subject: [PATCH 1/4] Initial plan
From bc17d9e731926180c9fea93e6f9fcade6a01df48 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Apr 2026 22:38:29 +0000
Subject: [PATCH 2/4] fix: enforce SEC-005 allowlist for comment memory
target-repo
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/cb517a73-fb9e-44b8-a005-10bd2144c9a5
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.../setup/js/setup_comment_memory_files.cjs | 16 ++++
.../js/setup_comment_memory_files.test.cjs | 84 +++++++++++++++++++
2 files changed, 100 insertions(+)
diff --git a/actions/setup/js/setup_comment_memory_files.cjs b/actions/setup/js/setup_comment_memory_files.cjs
index 147795ee5f..8ba0305843 100644
--- a/actions/setup/js/setup_comment_memory_files.cjs
+++ b/actions/setup/js/setup_comment_memory_files.cjs
@@ -5,6 +5,7 @@ require("./shim.cjs");
const fs = require("fs");
const path = require("path");
const { getErrorMessage } = require("./error_helpers.cjs");
+const { parseAllowedRepos, validateTargetRepo } = require("./repo_helpers.cjs");
const {
COMMENT_MEMORY_DIR,
COMMENT_MEMORY_MAX_SCAN_PAGES,
@@ -74,6 +75,21 @@ async function collectCommentMemoryFiles(githubClient, commentMemoryConfig) {
return [];
}
+ const contextRepoSlug = `${context.repo.owner}/${context.repo.repo}`;
+ const isCrossRepo = targetRepo.slug !== contextRepoSlug;
+ if (isCrossRepo) {
+ const allowedRepos = parseAllowedRepos(commentMemoryConfig?.allowed_repos);
+ if (allowedRepos.size === 0) {
+ throw new Error(`E004: Cross-repository comment-memory setup to '${targetRepo.slug}' is not permitted. No allowlist is configured. Define 'allowed_repos' to enable cross-repository access.`);
+ }
+
+ const repoValidation = validateTargetRepo(targetRepo.slug, contextRepoSlug, allowedRepos);
+ if (!repoValidation.valid) {
+ throw new Error(`E004: ${repoValidation.error}`);
+ }
+ core.info(`comment_memory setup: cross-repo allowlist check passed for ${targetRepo.slug}`);
+ }
+
core.info(`comment_memory setup: loading managed comment memory from ${targetRepo.slug}#${targetNumber}`);
const memoryMap = new Map();
let emptyPageCount = 0;
diff --git a/actions/setup/js/setup_comment_memory_files.test.cjs b/actions/setup/js/setup_comment_memory_files.test.cjs
index 9167cc70e6..f52c5ebcfa 100644
--- a/actions/setup/js/setup_comment_memory_files.test.cjs
+++ b/actions/setup/js/setup_comment_memory_files.test.cjs
@@ -96,4 +96,88 @@ describe("setup_comment_memory_files", () => {
expect(fs.readFileSync(memoryFile, "utf8")).toBe("Late memory\n");
expect(listComments).toHaveBeenCalledTimes(6);
});
+
+ it("rejects cross-repo comment-memory setup when no allowlist is configured", async () => {
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify({ "comment-memory": { target: "triggering", "target-repo": "other-org/other-repo" } }));
+ const listComments = vi.fn().mockResolvedValue({ data: [] });
+ global.github = {
+ rest: {
+ issues: {
+ listComments,
+ },
+ },
+ };
+
+ const module = await import("./setup_comment_memory_files.cjs");
+ await module.main();
+
+ expect(listComments).not.toHaveBeenCalled();
+ expect(global.core.warning).toHaveBeenCalledWith(expect.stringContaining("E004"));
+ expect(global.core.warning).toHaveBeenCalledWith(expect.stringContaining("No allowlist is configured"));
+ });
+
+ it("rejects cross-repo comment-memory setup when target repo is not in allowlist", async () => {
+ fs.writeFileSync(
+ CONFIG_PATH,
+ JSON.stringify({
+ "comment-memory": {
+ target: "triggering",
+ "target-repo": "other-org/other-repo",
+ allowed_repos: ["other-org/different-repo"],
+ },
+ })
+ );
+ const listComments = vi.fn().mockResolvedValue({ data: [] });
+ global.github = {
+ rest: {
+ issues: {
+ listComments,
+ },
+ },
+ };
+
+ const module = await import("./setup_comment_memory_files.cjs");
+ await module.main();
+
+ expect(listComments).not.toHaveBeenCalled();
+ expect(global.core.warning).toHaveBeenCalledWith(expect.stringContaining("E004"));
+ expect(global.core.warning).toHaveBeenCalledWith(expect.stringContaining("not in the allowed-repos list"));
+ });
+
+ it("allows cross-repo comment-memory setup when target repo is in allowlist", async () => {
+ fs.writeFileSync(
+ CONFIG_PATH,
+ JSON.stringify({
+ "comment-memory": {
+ target: "triggering",
+ "target-repo": "other-org/other-repo",
+ allowed_repos: ["other-org/other-repo"],
+ },
+ })
+ );
+ const listComments = vi.fn().mockResolvedValue({
+ data: [{ body: '\nCross repo memory\n' }],
+ });
+ global.github = {
+ rest: {
+ issues: {
+ listComments,
+ },
+ },
+ };
+
+ const module = await import("./setup_comment_memory_files.cjs");
+ await module.main();
+
+ expect(listComments).toHaveBeenCalledWith(
+ expect.objectContaining({
+ owner: "other-org",
+ repo: "other-repo",
+ issue_number: 42,
+ })
+ );
+ const memoryFile = path.join(COMMENT_MEMORY_DIR, "default.md");
+ expect(fs.existsSync(memoryFile)).toBe(true);
+ expect(fs.readFileSync(memoryFile, "utf8")).toBe("Cross repo memory\n");
+ });
});
From d1bb53e60f09ca4a4dafd5745d8dea4fa6df622c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Apr 2026 23:27:31 +0000
Subject: [PATCH 3/4] chore: use shared error code constants in comment-memory
SEC-005 errors
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/b9e38677-4bc5-4423-871a-4e41e9ded064
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
actions/setup/js/setup_comment_memory_files.cjs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/actions/setup/js/setup_comment_memory_files.cjs b/actions/setup/js/setup_comment_memory_files.cjs
index 8ba0305843..474e5ba879 100644
--- a/actions/setup/js/setup_comment_memory_files.cjs
+++ b/actions/setup/js/setup_comment_memory_files.cjs
@@ -4,6 +4,7 @@ require("./shim.cjs");
const fs = require("fs");
const path = require("path");
+const { ERR_VALIDATION } = require("./error_codes.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { parseAllowedRepos, validateTargetRepo } = require("./repo_helpers.cjs");
const {
@@ -80,12 +81,12 @@ async function collectCommentMemoryFiles(githubClient, commentMemoryConfig) {
if (isCrossRepo) {
const allowedRepos = parseAllowedRepos(commentMemoryConfig?.allowed_repos);
if (allowedRepos.size === 0) {
- throw new Error(`E004: Cross-repository comment-memory setup to '${targetRepo.slug}' is not permitted. No allowlist is configured. Define 'allowed_repos' to enable cross-repository access.`);
+ throw new Error(`${ERR_VALIDATION}: E004: Cross-repository comment-memory setup to '${targetRepo.slug}' is not permitted. No allowlist is configured. Define 'allowed_repos' to enable cross-repository access.`);
}
const repoValidation = validateTargetRepo(targetRepo.slug, contextRepoSlug, allowedRepos);
if (!repoValidation.valid) {
- throw new Error(`E004: ${repoValidation.error}`);
+ throw new Error(`${ERR_VALIDATION}: E004: ${repoValidation.error}`);
}
core.info(`comment_memory setup: cross-repo allowlist check passed for ${targetRepo.slug}`);
}
From 5ad07bb7c4d17f0689c0dade2a6bdcb7a7329085 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Apr 2026 23:56:57 +0000
Subject: [PATCH 4/4] fix: make comment-memory cross-repo check
case-insensitive
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/3b05466a-d50c-40a8-b555-0bfe58ad90f5
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.../setup/js/setup_comment_memory_files.cjs | 4 ++-
.../js/setup_comment_memory_files.test.cjs | 34 +++++++++++++++++++
2 files changed, 37 insertions(+), 1 deletion(-)
diff --git a/actions/setup/js/setup_comment_memory_files.cjs b/actions/setup/js/setup_comment_memory_files.cjs
index 474e5ba879..ba2e6a075f 100644
--- a/actions/setup/js/setup_comment_memory_files.cjs
+++ b/actions/setup/js/setup_comment_memory_files.cjs
@@ -77,7 +77,9 @@ async function collectCommentMemoryFiles(githubClient, commentMemoryConfig) {
}
const contextRepoSlug = `${context.repo.owner}/${context.repo.repo}`;
- const isCrossRepo = targetRepo.slug !== contextRepoSlug;
+ const normalizedTargetRepoSlug = targetRepo.slug.toLowerCase();
+ const normalizedContextRepoSlug = contextRepoSlug.toLowerCase();
+ const isCrossRepo = normalizedTargetRepoSlug !== normalizedContextRepoSlug;
if (isCrossRepo) {
const allowedRepos = parseAllowedRepos(commentMemoryConfig?.allowed_repos);
if (allowedRepos.size === 0) {
diff --git a/actions/setup/js/setup_comment_memory_files.test.cjs b/actions/setup/js/setup_comment_memory_files.test.cjs
index f52c5ebcfa..1580880cf3 100644
--- a/actions/setup/js/setup_comment_memory_files.test.cjs
+++ b/actions/setup/js/setup_comment_memory_files.test.cjs
@@ -180,4 +180,38 @@ describe("setup_comment_memory_files", () => {
expect(fs.existsSync(memoryFile)).toBe(true);
expect(fs.readFileSync(memoryFile, "utf8")).toBe("Cross repo memory\n");
});
+
+ it("treats target-repo as same repo when slug differs only by case", async () => {
+ fs.writeFileSync(
+ CONFIG_PATH,
+ JSON.stringify({
+ "comment-memory": {
+ target: "triggering",
+ "target-repo": "Octo/Repo",
+ },
+ })
+ );
+ const listComments = vi.fn().mockResolvedValue({
+ data: [{ body: '\nSame repo memory\n' }],
+ });
+ global.github = {
+ rest: {
+ issues: {
+ listComments,
+ },
+ },
+ };
+
+ const module = await import("./setup_comment_memory_files.cjs");
+ await module.main();
+
+ expect(listComments).toHaveBeenCalledWith(
+ expect.objectContaining({
+ owner: "Octo",
+ repo: "Repo",
+ issue_number: 42,
+ })
+ );
+ expect(global.core.warning).not.toHaveBeenCalledWith(expect.stringContaining("E004"));
+ });
});