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")); + }); });