Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions actions/setup/js/run_validate_workflows.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Comment on lines 92 to +93
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sanitizeContent(truncatedOutput) is called without a maxLength, and sanitization can expand the string (e.g., wrapping many @mentions in backticks, balancing fences). That means the final issue/comment body can exceed GitHub's size limits even though you pre-truncate to 50k. To keep the post size bounded, pass an explicit maxLength to sanitizeContent (e.g., 50k) and/or perform truncation after sanitization so the sanitized string is what gets size-limited.

Suggested change
const truncatedOutput = combinedOutput.substring(0, 50000) + (combinedOutput.length > 50000 ? "\n\n... (output truncated)" : "");
const sanitizedOutput = sanitizeContent(truncatedOutput);
const maxOutputLength = 50000;
const truncatedOutput = combinedOutput.substring(0, maxOutputLength) + (combinedOutput.length > maxOutputLength ? "\n\n... (output truncated)" : "");
const sanitizedOutput = sanitizeContent(truncatedOutput, maxOutputLength);

Copilot uses AI. Check for mistakes.

const xmlMarker = generateXMLMarker(workflowName, runUrl);
const commentBody = `Validation still has findings (exit code: ${exitCode}).
Expand All @@ -97,7 +99,7 @@ async function main() {
<summary>Validation output</summary>

\`\`\`
${truncatedOutput}
${sanitizedOutput}
\`\`\`

</details>
Expand Down Expand Up @@ -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
Expand All @@ -145,7 +148,7 @@ Workflow validation found errors or warnings that need to be addressed.
<summary>Full output</summary>

\`\`\`
${truncatedOutput}
${sanitizedOutput}
\`\`\`

</details>
Expand Down
47 changes: 47 additions & 0 deletions actions/setup/js/run_validate_workflows.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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(?!`)/);
});
Comment on lines +234 to +256
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new tests only assert that @mentions are neutralized, but the PR description calls out code-fence escaping/Markdown injection as the main risk. Add a regression test where the tool output contains a bare fence line (e.g., "```\n...") and verify the created issue/comment body still keeps the output confined (for example by using a longer wrapper fence or escaping fence delimiters).

Copilot uses AI. Check for mistakes.

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(?!`)/);
});
});
Loading