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
23 changes: 17 additions & 6 deletions actions/setup/js/run_validate_workflows.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,23 @@
/// <reference types="@actions/github-script" />

const { getErrorMessage } = require("./error_helpers.cjs");
const { generateFooterWithMessages, generateXMLMarker } = require("./messages_footer.cjs");
const { generateXMLMarker } = require("./messages_footer.cjs");
const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");

const MAX_VALIDATION_OUTPUT_LENGTH = 50000;

/**
* Truncate and sanitize validation output for safe inclusion in an issue or comment body.
* @param {string} output - Raw combined stdout/stderr output
* @param {number} [maxLength] - Maximum length before truncation
* @returns {string} Sanitized, possibly truncated output
*/
function prepareValidationOutput(output, maxLength = MAX_VALIDATION_OUTPUT_LENGTH) {
const truncated = output.substring(0, maxLength) + (output.length > maxLength ? "\n\n... (output truncated)" : "");
return sanitizeContent(truncated);
}

/**
* Run full workflow validation using gh-aw compile --validate and all known
* checkers (zizmor, actionlint, poutine). If any errors or warnings are found,
Expand Down Expand Up @@ -89,8 +102,7 @@ async function main() {
const existingIssue = searchResult.data.items[0];
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 sanitizedOutput = prepareValidationOutput(combinedOutput);

const xmlMarker = generateXMLMarker(workflowName, runUrl);
const commentBody = `Validation still has findings (exit code: ${exitCode}).
Expand Down Expand Up @@ -131,8 +143,7 @@ ${xmlMarker}`;
// No existing issue found, create a new one
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 sanitizedOutput = prepareValidationOutput(combinedOutput);

const xmlMarker = generateXMLMarker(workflowName, runUrl);
const issueBody = `## Problem
Expand Down Expand Up @@ -198,4 +209,4 @@ ${xmlMarker}
}
}

module.exports = { main };
module.exports = { main, prepareValidationOutput };
26 changes: 26 additions & 0 deletions actions/setup/js/run_validate_workflows.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,32 @@ describe("run_validate_workflows", () => {
expect(createCall.body).not.toMatch(/@malicious-user(?!`)/);
});

it("prepareValidationOutput returns output unchanged when under the length limit", async () => {
const { prepareValidationOutput } = await import("./run_validate_workflows.cjs");
const result = prepareValidationOutput("short output");
expect(result).toBe("short output");
});

it("prepareValidationOutput truncates output over the default length limit", async () => {
const { prepareValidationOutput } = await import("./run_validate_workflows.cjs");
const longOutput = "y".repeat(60000);
const result = prepareValidationOutput(longOutput);
expect(result).toContain("... (output truncated)");
expect(result.length).toBeLessThan(longOutput.length);
});

it("prepareValidationOutput respects a custom max length", async () => {
const { prepareValidationOutput } = await import("./run_validate_workflows.cjs");
const result = prepareValidationOutput("abcdefghij", 5);
expect(result).toBe("abcde\n\n... (output truncated)");
});

it("prepareValidationOutput sanitizes @mentions", async () => {
const { prepareValidationOutput } = await import("./run_validate_workflows.cjs");
const result = prepareValidationOutput("Error: @malicious-user triggered a warning");
expect(result).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) {
Expand Down
Loading