diff --git a/actions/setup/js/run_validate_workflows.cjs b/actions/setup/js/run_validate_workflows.cjs index e2ca7f1ac5..ff0d3be0ce 100644 --- a/actions/setup/js/run_validate_workflows.cjs +++ b/actions/setup/js/run_validate_workflows.cjs @@ -4,6 +4,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { generateFooterWithMessages, generateXMLMarker } = require("./messages_footer.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); +const { sanitizeContent } = require("./sanitize_content.cjs"); /** * Run full workflow validation using gh-aw compile --validate and all known @@ -89,6 +90,7 @@ async function main() { core.info(`Found existing issue #${existingIssue.number}: ${existingIssue.html_url}`); const truncatedOutput = combinedOutput.substring(0, 50000) + (combinedOutput.length > 50000 ? "\n\n... (output truncated)" : ""); + const sanitizedOutput = sanitizeContent(truncatedOutput); const xmlMarker = generateXMLMarker(workflowName, runUrl); const commentBody = `Validation still has findings (exit code: ${exitCode}). @@ -97,7 +99,7 @@ async function main() { Validation output \`\`\` -${truncatedOutput} +${sanitizedOutput} \`\`\` @@ -130,6 +132,7 @@ ${xmlMarker}`; core.info("No existing issue found, creating a new issue with validation findings"); const truncatedOutput = combinedOutput.substring(0, 50000) + (combinedOutput.length > 50000 ? "\n\n... (output truncated)" : ""); + const sanitizedOutput = sanitizeContent(truncatedOutput); const xmlMarker = generateXMLMarker(workflowName, runUrl); const issueBody = `## Problem @@ -145,7 +148,7 @@ Workflow validation found errors or warnings that need to be addressed. Full output \`\`\` -${truncatedOutput} +${sanitizedOutput} \`\`\` diff --git a/actions/setup/js/run_validate_workflows.test.cjs b/actions/setup/js/run_validate_workflows.test.cjs index 4dd59f4e6a..e2efcaa25a 100644 --- a/actions/setup/js/run_validate_workflows.test.cjs +++ b/actions/setup/js/run_validate_workflows.test.cjs @@ -230,4 +230,51 @@ describe("run_validate_workflows", () => { const createCall = mockGithub.rest.issues.create.mock.calls[0][0]; expect(createCall.body).toContain("... (output truncated)"); }); + + it("should sanitize output containing @mentions in new issue body", async () => { + mockExec.exec.mockImplementation(async (_cmd, _args, options) => { + if (options?.listeners?.stderr) { + options.listeners.stderr(Buffer.from("Error: @malicious-user triggered a warning\n")); + } + return 1; + }); + + mockGithub.rest.search.issuesAndPullRequests.mockResolvedValue({ + data: { total_count: 0, items: [] }, + }); + + mockGithub.rest.issues.create.mockResolvedValue({ + data: { number: 45, html_url: "https://github.com/testowner/testrepo/issues/45" }, + }); + + const { main } = await import("./run_validate_workflows.cjs"); + await main(); + + const createCall = mockGithub.rest.issues.create.mock.calls[0][0]; + // sanitizeContent should neutralize @mentions so they don't ping users + expect(createCall.body).not.toMatch(/@malicious-user(?!`)/); + }); + + it("should sanitize output containing @mentions in comment body", async () => { + mockExec.exec.mockImplementation(async (_cmd, _args, options) => { + if (options?.listeners?.stderr) { + options.listeners.stderr(Buffer.from("Error: @malicious-user triggered a warning\n")); + } + return 1; + }); + + mockGithub.rest.search.issuesAndPullRequests.mockResolvedValue({ + data: { + total_count: 1, + items: [{ number: 10, html_url: "https://github.com/testowner/testrepo/issues/10" }], + }, + }); + + const { main } = await import("./run_validate_workflows.cjs"); + await main(); + + const commentCall = mockGithub.rest.issues.createComment.mock.calls[0][0]; + // sanitizeContent should neutralize @mentions so they don't ping users + expect(commentCall.body).not.toMatch(/@malicious-user(?!`)/); + }); });