From 8ede745742587384dcfda9fa3cd12089ad355dac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:31:07 +0000 Subject: [PATCH 1/5] Initial plan From 1aa31b3aac8971fbf63ffd24bcce38baf2bef391 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:52:21 +0000 Subject: [PATCH 2/5] fix(runtime-import): handle leading / in repo-root-absolute import paths Strip the leading "/" from paths like /.agents/skills/my-skill/instructions.md and /.github/agents/planner.md before the existing prefix checks in processRuntimeImport(). This mirrors the compile-time behavior in ResolveIncludePath (remote_fetch.go) introduced in PR #24501. Add regression tests for: - /.agents/... paths resolving to workspace-root .agents folder - /.github/... paths resolving within .github folder - /etc/passwd (unrecognised prefix) safely falling through to .github/workflows/ Closes #24501 Agent-Logs-Url: https://github.com/github/gh-aw/sessions/8a3e3249-84d4-4655-a138-450674869af8 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/runtime_import.cjs | 10 +++++++++ actions/setup/js/runtime_import.test.cjs | 27 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/actions/setup/js/runtime_import.cjs b/actions/setup/js/runtime_import.cjs index 29adeea26f1..d445dd72da4 100644 --- a/actions/setup/js/runtime_import.cjs +++ b/actions/setup/js/runtime_import.cjs @@ -746,6 +746,16 @@ async function processRuntimeImport(filepathOrUrl, optional, workspaceDir, start let filepath = filepathOrUrl; let isAgentsPath = false; + // Strip leading "/" for repo-root-absolute paths (e.g. /.agents/skills/..., /.github/agents/...). + // After stripping, the existing .agents/ and .github/ prefix checks handle resolution correctly. + // Only strip when the result begins with .agents/ or .github/ to preserve security restrictions. + if (filepath.startsWith("/")) { + const stripped = filepath.substring(1); + if (stripped.startsWith(".agents/") || stripped.startsWith(".agents\\") || stripped.startsWith(".github/") || stripped.startsWith(".github\\")) { + filepath = stripped; + } + } + // Check if this is a .agents/ path (top-level folder for skills) if (filepath.startsWith(".agents/")) { isAgentsPath = true; diff --git a/actions/setup/js/runtime_import.test.cjs b/actions/setup/js/runtime_import.test.cjs index a83110f92ae..ed5d65a7521 100644 --- a/actions/setup/js/runtime_import.test.cjs +++ b/actions/setup/js/runtime_import.test.cjs @@ -466,6 +466,33 @@ describe("runtime_import", () => { const result = await processRuntimeImport(".agents/test-skill.md", !1, tempDir); expect(result).toBe(content); }), + it("should support /.agents/ prefix (leading slash, repo-root-absolute)", async () => { + // Regression test: /.agents/skills/my-skill/instructions.md should resolve to + // /.agents/skills/my-skill/instructions.md (not .github/workflows/.agents/...) + const skillsDir = path.join(tempDir, ".agents", "skills", "my-skill"); + fs.mkdirSync(skillsDir, { recursive: true }); + const content = "# My Skill\n\nThis is the skill content."; + fs.writeFileSync(path.join(skillsDir, "instructions.md"), content); + const result = await processRuntimeImport("/.agents/skills/my-skill/instructions.md", !1, tempDir); + expect(result).toBe(content); + }), + it("should support /.github/ prefix (leading slash, repo-root-absolute)", async () => { + // Regression test: /.github/agents/planner.md should resolve to + // /.github/agents/planner.md (not .github/workflows/.github/agents/...) + const agentsDir = path.join(tempDir, ".github", "agents"); + fs.mkdirSync(agentsDir, { recursive: true }); + const content = "# Planner Agent\n\nThis is the planner content."; + fs.writeFileSync(path.join(agentsDir, "planner.md"), content); + const result = await processRuntimeImport("/.github/agents/planner.md", !1, tempDir); + expect(result).toBe(content); + }), + it("should reject /-prefixed paths not under .agents/ or .github/", async () => { + // A leading "/" that does not map to .agents/ or .github/ should NOT be stripped — + // it falls through to the default branch and is confined to .github/workflows/, so it + // cannot reach arbitrary filesystem paths like /etc/passwd. + // The resolved path (.github/workflows/etc/passwd) does not exist, so it throws "file not found". + await expect(processRuntimeImport("/etc/passwd", !1, tempDir)).rejects.toThrow(); + }), it("should support nested .github/workflows/shared/ path (issue: runtime-import fails for .github/workflows/* paths)", async () => { // Regression test: .github/workflows/shared/reporting.md should resolve to // /.github/workflows/shared/reporting.md (not /workflows/shared/reporting.md) From 3b3f16d01bf6f7b9c86567687f31633e587372ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:53:36 +0000 Subject: [PATCH 3/5] refine: use specific error message in security test Agent-Logs-Url: https://github.com/github/gh-aw/sessions/8a3e3249-84d4-4655-a138-450674869af8 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/runtime_import.test.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup/js/runtime_import.test.cjs b/actions/setup/js/runtime_import.test.cjs index ed5d65a7521..82e9d4d6630 100644 --- a/actions/setup/js/runtime_import.test.cjs +++ b/actions/setup/js/runtime_import.test.cjs @@ -491,7 +491,7 @@ describe("runtime_import", () => { // it falls through to the default branch and is confined to .github/workflows/, so it // cannot reach arbitrary filesystem paths like /etc/passwd. // The resolved path (.github/workflows/etc/passwd) does not exist, so it throws "file not found". - await expect(processRuntimeImport("/etc/passwd", !1, tempDir)).rejects.toThrow(); + await expect(processRuntimeImport("/etc/passwd", !1, tempDir)).rejects.toThrow("Runtime import file not found"); }), it("should support nested .github/workflows/shared/ path (issue: runtime-import fails for .github/workflows/* paths)", async () => { // Regression test: .github/workflows/shared/reporting.md should resolve to From 0a485058226daf9f05c463b8979820f23acd5e65 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:00:59 +0000 Subject: [PATCH 4/5] fix(runtime-import): handle paths with multiple leading slashes (//) Replace single-slash strip with regex /^\/+/ so that paths beginning with // (or any number of slashes) are also normalised before the .agents/ / .github/ prefix checks. Add regression tests for //.agents/ and //.github/. Agent-Logs-Url: https://github.com/github/gh-aw/sessions/26793fd8-125c-4d4e-a130-a9381c998dcf Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/runtime_import.cjs | 5 +++-- actions/setup/js/runtime_import.test.cjs | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/actions/setup/js/runtime_import.cjs b/actions/setup/js/runtime_import.cjs index d445dd72da4..948c0535649 100644 --- a/actions/setup/js/runtime_import.cjs +++ b/actions/setup/js/runtime_import.cjs @@ -746,11 +746,12 @@ async function processRuntimeImport(filepathOrUrl, optional, workspaceDir, start let filepath = filepathOrUrl; let isAgentsPath = false; - // Strip leading "/" for repo-root-absolute paths (e.g. /.agents/skills/..., /.github/agents/...). + // Strip leading "/" or "//" (and any number of slashes) for repo-root-absolute paths + // (e.g. /.agents/skills/..., //.github/agents/...). // After stripping, the existing .agents/ and .github/ prefix checks handle resolution correctly. // Only strip when the result begins with .agents/ or .github/ to preserve security restrictions. if (filepath.startsWith("/")) { - const stripped = filepath.substring(1); + const stripped = filepath.replace(/^\/+/, ""); if (stripped.startsWith(".agents/") || stripped.startsWith(".agents\\") || stripped.startsWith(".github/") || stripped.startsWith(".github\\")) { filepath = stripped; } diff --git a/actions/setup/js/runtime_import.test.cjs b/actions/setup/js/runtime_import.test.cjs index 82e9d4d6630..9bcbf271743 100644 --- a/actions/setup/js/runtime_import.test.cjs +++ b/actions/setup/js/runtime_import.test.cjs @@ -476,6 +476,16 @@ describe("runtime_import", () => { const result = await processRuntimeImport("/.agents/skills/my-skill/instructions.md", !1, tempDir); expect(result).toBe(content); }), + it("should support //.agents/ prefix (double leading slash, repo-root-absolute)", async () => { + // Regression test: //.agents/skills/my-skill/instructions.md (double slash) should also + // resolve to /.agents/skills/my-skill/instructions.md + const skillsDir = path.join(tempDir, ".agents", "skills", "my-skill"); + fs.mkdirSync(skillsDir, { recursive: true }); + const content = "# My Skill (double slash)\n\nThis is the skill content."; + fs.writeFileSync(path.join(skillsDir, "instructions.md"), content); + const result = await processRuntimeImport("//.agents/skills/my-skill/instructions.md", !1, tempDir); + expect(result).toBe(content); + }), it("should support /.github/ prefix (leading slash, repo-root-absolute)", async () => { // Regression test: /.github/agents/planner.md should resolve to // /.github/agents/planner.md (not .github/workflows/.github/agents/...) @@ -486,6 +496,16 @@ describe("runtime_import", () => { const result = await processRuntimeImport("/.github/agents/planner.md", !1, tempDir); expect(result).toBe(content); }), + it("should support //.github/ prefix (double leading slash, repo-root-absolute)", async () => { + // Regression test: //.github/agents/planner.md (double slash) should also resolve to + // /.github/agents/planner.md + const agentsDir = path.join(tempDir, ".github", "agents"); + fs.mkdirSync(agentsDir, { recursive: true }); + const content = "# Planner Agent (double slash)\n\nThis is the planner content."; + fs.writeFileSync(path.join(agentsDir, "planner.md"), content); + const result = await processRuntimeImport("//.github/agents/planner.md", !1, tempDir); + expect(result).toBe(content); + }), it("should reject /-prefixed paths not under .agents/ or .github/", async () => { // A leading "/" that does not map to .agents/ or .github/ should NOT be stripped — // it falls through to the default branch and is confined to .github/workflows/, so it From 566357151767bd62bb00e8c1ff7e4405bc36dcef Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Mon, 6 Apr 2026 09:11:48 -0700 Subject: [PATCH 5/5] Update runtime_import.test.cjs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- actions/setup/js/runtime_import.test.cjs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/actions/setup/js/runtime_import.test.cjs b/actions/setup/js/runtime_import.test.cjs index 9bcbf271743..2c6c83f7feb 100644 --- a/actions/setup/js/runtime_import.test.cjs +++ b/actions/setup/js/runtime_import.test.cjs @@ -507,11 +507,12 @@ describe("runtime_import", () => { expect(result).toBe(content); }), it("should reject /-prefixed paths not under .agents/ or .github/", async () => { - // A leading "/" that does not map to .agents/ or .github/ should NOT be stripped — - // it falls through to the default branch and is confined to .github/workflows/, so it - // cannot reach arbitrary filesystem paths like /etc/passwd. - // The resolved path (.github/workflows/etc/passwd) does not exist, so it throws "file not found". - await expect(processRuntimeImport("/etc/passwd", !1, tempDir)).rejects.toThrow("Runtime import file not found"); + // A leading "/" that does not map to .agents/ or .github/ should NOT be stripped. + // It falls through to the default branch, and path joining preserves the absolute path + // (/etc/passwd on POSIX), which is then rejected by the .github base-folder security check. + // Assert the security rejection rather than a file-not-found error so the test remains + // stable across platforms. + await expect(processRuntimeImport("/etc/passwd", !1, tempDir)).rejects.toThrow("Security: Path"); }), it("should support nested .github/workflows/shared/ path (issue: runtime-import fails for .github/workflows/* paths)", async () => { // Regression test: .github/workflows/shared/reporting.md should resolve to