Sanitize subprocess output in run_validate_workflows.cjs (SEC-004)#25971
Sanitize subprocess output in run_validate_workflows.cjs (SEC-004)#25971
Conversation
… (SEC-004) Import and apply the shared sanitizeContent helper to truncatedOutput before embedding it in GitHub issue/comment bodies in run_validate_workflows.cjs. This prevents untrusted subprocess output from escaping code fences or injecting unexpected Markdown/HTML. Add tests verifying @mentions in output are neutralized in both the new-issue and existing-issue-comment code paths. Agent-Logs-Url: https://github.com/github/gh-aw/sessions/b1e91242-6a52-4b62-a671-b50030ed6caf Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR aims to prevent Markdown/HTML injection when run_validate_workflows.cjs posts gh aw compile --validate output into GitHub issue/comment bodies by sanitizing the embedded output.
Changes:
- Import and apply the shared
sanitizeContenthelper to the truncated validator output before embedding it in issue/comment templates. - Add Vitest coverage to ensure
@mentionsin output are neutralized in both “new issue” and “existing issue comment” paths.
Show a summary per file
| File | Description |
|---|---|
| actions/setup/js/run_validate_workflows.cjs | Sanitizes truncated subprocess output before embedding in issue/comment Markdown. |
| actions/setup/js/run_validate_workflows.test.cjs | Adds tests asserting @mention neutralization in both posting paths. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 3
| <details> | ||
| <summary>Validation output</summary> | ||
|
|
||
| \`\`\` | ||
| ${truncatedOutput} | ||
| ${sanitizedOutput} | ||
| \`\`\` |
There was a problem hiding this comment.
The validation output is embedded inside a triple-backtick fenced code block. If the subprocess output itself contains a bare fence line like "```" (or "~~~"), it can prematurely close the outer fence and escape the intended code region. sanitizeContent() balances markdown fences but does not neutralize/escape fence delimiters, so this is still exploitable for Markdown injection. Consider switching the wrapper to a longer fence length (the repo uses 6 backticks for untrusted content; see `actions/setup/js/log_parser_shared.cjs:1006-1013`) or otherwise ensure fence sequences in the output cannot terminate the wrapper fence.
| const truncatedOutput = combinedOutput.substring(0, 50000) + (combinedOutput.length > 50000 ? "\n\n... (output truncated)" : ""); | ||
| const sanitizedOutput = sanitizeContent(truncatedOutput); |
There was a problem hiding this comment.
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.
| 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); |
| 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(?!`)/); | ||
| }); |
There was a problem hiding this comment.
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).
🧪 Test Quality Sentinel ReportTest Quality Score: 80/100✅ Excellent test quality
Test Classification Details📋 Per-test classification (2 tests)
Test AnalysisBoth new tests target the SEC-004 sanitization behavior added in Mocking assessment: The tests mock Error coverage: Both tests drive an error path (exec returns exit code 1) which exercises the code paths that produce issues/comments. Full credit. ✅ Coverage gap noted: The tests cover Inflation NoteThe 9.4x test-to-production ratio (47 test lines / 5 production lines) technically triggers the inflation flag, but this is contextually justified: the production change is minimal (calling an already-existing Language SupportTests analyzed:
Verdict
📖 Understanding Test ClassificationsDesign Tests (High Value) verify what the system does:
Implementation Tests (Low Value) verify how the system does it:
Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.
|
run_validate_workflows.cjsembeds rawgh aw compile --validatestdout/stderr into GitHub issue and comment bodies without sanitization. Malformed tool output could escape the code fence or inject unexpected Markdown/HTML.Changes
sanitizeContenthelper (used by 30+ other handlers) and apply it totruncatedOutputbefore embedding in both the comment body (existing issue path) and issue body (new issue path)