From 2364ea42ba285de52803c8090708986b7323c743 Mon Sep 17 00:00:00 2001 From: Don Syme Date: Wed, 17 Sep 2025 13:19:24 +0100 Subject: [PATCH] update improvers --- .github/workflows/ask.lock.yml | 83 ++++++++++-------- .github/workflows/ask.md | 2 +- .github/workflows/ci-doctor.lock.yml | 83 ++++++++++-------- .github/workflows/ci-doctor.md | 2 +- .../workflows/daily-backlog-burner.lock.yml | 86 ++++++++++-------- .github/workflows/daily-backlog-burner.md | 3 +- .../workflows/daily-perf-improver.lock.yml | 86 ++++++++++-------- .github/workflows/daily-perf-improver.md | 3 +- .../workflows/daily-test-improver.lock.yml | 87 +++++++++++-------- .github/workflows/daily-test-improver.md | 3 +- .github/workflows/pr-fix.lock.yml | 83 ++++++++++-------- .github/workflows/pr-fix.md | 2 +- 12 files changed, 295 insertions(+), 228 deletions(-) diff --git a/.github/workflows/ask.lock.yml b/.github/workflows/ask.lock.yml index 1fd02e3cb..bbaa645b4 100644 --- a/.github/workflows/ask.lock.yml +++ b/.github/workflows/ask.lock.yml @@ -2,7 +2,7 @@ # To update this file, edit the corresponding .md file and run: # gh aw compile # -# Effective stop-time: 2025-09-19 10:32:53 +# Effective stop-time: 2025-09-19 12:19:14 name: "Question Answering Researcher" on: @@ -594,7 +594,7 @@ jobs: main(); - name: Setup Safe Outputs Collector MCP env: - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true}}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true}}" run: | mkdir -p /tmp/safe-outputs cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF' @@ -747,7 +747,7 @@ jobs: }, }, { - name: "add-issue-comment", + name: "add-comment", description: "Add a comment to a GitHub issue or pull request", inputSchema: { type: "object", @@ -854,7 +854,7 @@ jobs: }, }, { - name: "add-issue-labels", + name: "add-labels", description: "Add labels to a GitHub issue or pull request", inputSchema: { type: "object", @@ -1028,7 +1028,7 @@ jobs: - name: Setup MCPs env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true}}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true}}" run: | mkdir -p /tmp/mcp-config cat > /tmp/mcp-config/mcp-servers.json << 'EOF' @@ -1066,7 +1066,7 @@ jobs: WORKFLOW_NAME="Question Answering Researcher" # Check stop-time limit - STOP_TIME="2025-09-19 10:32:53" + STOP_TIME="2025-09-19 12:19:14" echo "Checking stop-time limit: $STOP_TIME" # Convert stop time to epoch seconds @@ -1157,7 +1157,7 @@ jobs: **Adding a Comment to an Issue or Pull Request** - To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP + To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP EOF - name: Print prompt to step summary @@ -1327,7 +1327,7 @@ jobs: uses: actions/github-script@v8 env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true}}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true}}" with: script: | async function main() { @@ -1360,15 +1360,12 @@ jobs: let sanitized = content; // Neutralize @mentions to prevent unintended notifications sanitized = neutralizeMentions(sanitized); + // Remove XML comments to prevent content hiding + sanitized = removeXmlComments(sanitized); + // Remove ANSI escape sequences BEFORE removing control characters + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); // Remove control characters (except newlines and tabs) sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - // XML character escaping - sanitized = sanitized - .replace(/&/g, "&") // Must be first to avoid double-escaping - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); // URI filtering - replace non-https protocols with "(redacted)" sanitized = sanitizeUrlProtocols(sanitized); // Domain filtering for HTTPS URIs @@ -1388,8 +1385,7 @@ jobs: lines.slice(0, maxLines).join("\n") + "\n[Content truncated due to line count]"; } - // Remove ANSI escape sequences - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + // ANSI escape sequences already removed earlier in the function // Neutralize common bot trigger phrases sanitized = neutralizeBotTriggers(sanitized); // Trim excessive whitespace @@ -1401,10 +1397,12 @@ jobs: */ function sanitizeUrlDomains(s) { return s.replace( - /\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi, - (match, domain) => { + /\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi, + (match) => { + // Extract just the URL part after https:// + const urlAfterProtocol = match.slice(8); // Remove 'https://' // Extract the hostname part (before first slash, colon, or other delimiter) - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase(); // Check if this domain or any parent domain is in the allowlist const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); @@ -1423,9 +1421,10 @@ jobs: * @returns {string} The string with non-https protocols redacted */ function sanitizeUrlProtocols(s) { - // Match both protocol:// and protocol: patterns + // Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs + // Avoid matching command line flags like -v:10 or z3 -memory:high return s.replace( - /\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, + /\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { // Allow https (case insensitive), redact everything else return protocol.toLowerCase() === "https" ? match : "(redacted)"; @@ -1444,6 +1443,16 @@ jobs: (_m, p1, p2) => `${p1}\`@${p2}\`` ); } + /** + * Removes XML comments to prevent content hiding + * @param {string} s - The string to process + * @returns {string} The string with XML comments removed + */ + function removeXmlComments(s) { + // Remove XML/HTML comments including malformed ones that might be used to hide content + // Matches: and and variations + return s.replace(//g, "").replace(//g, ""); + } /** * Neutralizes bot trigger phrases by wrapping them in backticks * @param {string} s - The string to process @@ -1477,13 +1486,13 @@ jobs: switch (itemType) { case "create-issue": return 1; // Only one issue allowed - case "add-issue-comment": + case "add-comment": return 1; // Only one comment allowed case "create-pull-request": return 1; // Only one pull request allowed case "create-pull-request-review-comment": return 10; // Default to 10 review comments allowed - case "add-issue-labels": + case "add-labels": return 5; // Only one labels operation allowed case "update-issue": return 1; // Only one issue update allowed @@ -1698,10 +1707,10 @@ jobs: ); } break; - case "add-issue-comment": + case "add-comment": if (!item.body || typeof item.body !== "string") { errors.push( - `Line ${i + 1}: add-issue-comment requires a 'body' string field` + `Line ${i + 1}: add-comment requires a 'body' string field` ); continue; } @@ -1736,10 +1745,10 @@ jobs: ); } break; - case "add-issue-labels": + case "add-labels": if (!item.labels || !Array.isArray(item.labels)) { errors.push( - `Line ${i + 1}: add-issue-labels requires a 'labels' array field` + `Line ${i + 1}: add-labels requires a 'labels' array field` ); continue; } @@ -1749,7 +1758,7 @@ jobs: ) ) { errors.push( - `Line ${i + 1}: add-issue-labels labels array must contain only strings` + `Line ${i + 1}: add-labels labels array must contain only strings` ); continue; } @@ -2671,11 +2680,11 @@ jobs: pull-requests: write timeout-minutes: 10 outputs: - comment_id: ${{ steps.create_comment.outputs.comment_id }} - comment_url: ${{ steps.create_comment.outputs.comment_url }} + comment_id: ${{ steps.add_comment.outputs.comment_id }} + comment_url: ${{ steps.add_comment.outputs.comment_url }} steps: - name: Add Issue Comment - id: create_comment + id: add_comment uses: actions/github-script@v8 env: GITHUB_AW_AGENT_OUTPUT: ${{ needs.question-answering-researcher.outputs.output }} @@ -2709,15 +2718,15 @@ jobs: core.info("No valid items found in agent output"); return; } - // Find all add-issue-comment items + // Find all add-comment items const commentItems = validatedOutput.items.filter( - /** @param {any} item */ item => item.type === "add-issue-comment" + /** @param {any} item */ item => item.type === "add-comment" ); if (commentItems.length === 0) { - core.info("No add-issue-comment items found in agent output"); + core.info("No add-comment items found in agent output"); return; } - core.info(`Found ${commentItems.length} add-issue-comment item(s)`); + core.info(`Found ${commentItems.length} add-comment item(s)`); // If in staged mode, emit step summary instead of creating comments if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n"; @@ -2761,7 +2770,7 @@ jobs: for (let i = 0; i < commentItems.length; i++) { const commentItem = commentItems[i]; core.info( - `Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` + `Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` ); // Determine the issue/PR number and comment endpoint for this comment let issueNumber; diff --git a/.github/workflows/ask.md b/.github/workflows/ask.md index a2736339f..cc3077d88 100644 --- a/.github/workflows/ask.md +++ b/.github/workflows/ask.md @@ -11,7 +11,7 @@ permissions: read-all network: defaults safe-outputs: - add-issue-comment: + add-comment: tools: web-fetch: diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 265c7cd60..31045da84 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -2,7 +2,7 @@ # To update this file, edit the corresponding .md file and run: # gh aw compile # -# Effective stop-time: 2025-09-19 10:32:53 +# Effective stop-time: 2025-09-19 12:19:14 name: "CI Failure Doctor" "on": @@ -75,7 +75,7 @@ jobs: main(); - name: Setup Safe Outputs Collector MCP env: - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true}" run: | mkdir -p /tmp/safe-outputs cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF' @@ -228,7 +228,7 @@ jobs: }, }, { - name: "add-issue-comment", + name: "add-comment", description: "Add a comment to a GitHub issue or pull request", inputSchema: { type: "object", @@ -335,7 +335,7 @@ jobs: }, }, { - name: "add-issue-labels", + name: "add-labels", description: "Add labels to a GitHub issue or pull request", inputSchema: { type: "object", @@ -509,7 +509,7 @@ jobs: - name: Setup MCPs env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true}" run: | mkdir -p /tmp/mcp-config cat > /tmp/mcp-config/mcp-servers.json << 'EOF' @@ -547,7 +547,7 @@ jobs: WORKFLOW_NAME="CI Failure Doctor" # Check stop-time limit - STOP_TIME="2025-09-19 10:32:53" + STOP_TIME="2025-09-19 12:19:14" echo "Checking stop-time limit: $STOP_TIME" # Convert stop time to epoch seconds @@ -770,7 +770,7 @@ jobs: **Adding a Comment to an Issue or Pull Request** - To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP + To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP **Creating an Issue** @@ -941,7 +941,7 @@ jobs: uses: actions/github-script@v8 env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true}" with: script: | async function main() { @@ -974,15 +974,12 @@ jobs: let sanitized = content; // Neutralize @mentions to prevent unintended notifications sanitized = neutralizeMentions(sanitized); + // Remove XML comments to prevent content hiding + sanitized = removeXmlComments(sanitized); + // Remove ANSI escape sequences BEFORE removing control characters + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); // Remove control characters (except newlines and tabs) sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - // XML character escaping - sanitized = sanitized - .replace(/&/g, "&") // Must be first to avoid double-escaping - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); // URI filtering - replace non-https protocols with "(redacted)" sanitized = sanitizeUrlProtocols(sanitized); // Domain filtering for HTTPS URIs @@ -1002,8 +999,7 @@ jobs: lines.slice(0, maxLines).join("\n") + "\n[Content truncated due to line count]"; } - // Remove ANSI escape sequences - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + // ANSI escape sequences already removed earlier in the function // Neutralize common bot trigger phrases sanitized = neutralizeBotTriggers(sanitized); // Trim excessive whitespace @@ -1015,10 +1011,12 @@ jobs: */ function sanitizeUrlDomains(s) { return s.replace( - /\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi, - (match, domain) => { + /\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi, + (match) => { + // Extract just the URL part after https:// + const urlAfterProtocol = match.slice(8); // Remove 'https://' // Extract the hostname part (before first slash, colon, or other delimiter) - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase(); // Check if this domain or any parent domain is in the allowlist const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); @@ -1037,9 +1035,10 @@ jobs: * @returns {string} The string with non-https protocols redacted */ function sanitizeUrlProtocols(s) { - // Match both protocol:// and protocol: patterns + // Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs + // Avoid matching command line flags like -v:10 or z3 -memory:high return s.replace( - /\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, + /\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { // Allow https (case insensitive), redact everything else return protocol.toLowerCase() === "https" ? match : "(redacted)"; @@ -1058,6 +1057,16 @@ jobs: (_m, p1, p2) => `${p1}\`@${p2}\`` ); } + /** + * Removes XML comments to prevent content hiding + * @param {string} s - The string to process + * @returns {string} The string with XML comments removed + */ + function removeXmlComments(s) { + // Remove XML/HTML comments including malformed ones that might be used to hide content + // Matches: and and variations + return s.replace(//g, "").replace(//g, ""); + } /** * Neutralizes bot trigger phrases by wrapping them in backticks * @param {string} s - The string to process @@ -1091,13 +1100,13 @@ jobs: switch (itemType) { case "create-issue": return 1; // Only one issue allowed - case "add-issue-comment": + case "add-comment": return 1; // Only one comment allowed case "create-pull-request": return 1; // Only one pull request allowed case "create-pull-request-review-comment": return 10; // Default to 10 review comments allowed - case "add-issue-labels": + case "add-labels": return 5; // Only one labels operation allowed case "update-issue": return 1; // Only one issue update allowed @@ -1312,10 +1321,10 @@ jobs: ); } break; - case "add-issue-comment": + case "add-comment": if (!item.body || typeof item.body !== "string") { errors.push( - `Line ${i + 1}: add-issue-comment requires a 'body' string field` + `Line ${i + 1}: add-comment requires a 'body' string field` ); continue; } @@ -1350,10 +1359,10 @@ jobs: ); } break; - case "add-issue-labels": + case "add-labels": if (!item.labels || !Array.isArray(item.labels)) { errors.push( - `Line ${i + 1}: add-issue-labels requires a 'labels' array field` + `Line ${i + 1}: add-labels requires a 'labels' array field` ); continue; } @@ -1363,7 +1372,7 @@ jobs: ) ) { errors.push( - `Line ${i + 1}: add-issue-labels labels array must contain only strings` + `Line ${i + 1}: add-labels labels array must contain only strings` ); continue; } @@ -2480,11 +2489,11 @@ jobs: pull-requests: write timeout-minutes: 10 outputs: - comment_id: ${{ steps.create_comment.outputs.comment_id }} - comment_url: ${{ steps.create_comment.outputs.comment_url }} + comment_id: ${{ steps.add_comment.outputs.comment_id }} + comment_url: ${{ steps.add_comment.outputs.comment_url }} steps: - name: Add Issue Comment - id: create_comment + id: add_comment uses: actions/github-script@v8 env: GITHUB_AW_AGENT_OUTPUT: ${{ needs.ci-failure-doctor.outputs.output }} @@ -2518,15 +2527,15 @@ jobs: core.info("No valid items found in agent output"); return; } - // Find all add-issue-comment items + // Find all add-comment items const commentItems = validatedOutput.items.filter( - /** @param {any} item */ item => item.type === "add-issue-comment" + /** @param {any} item */ item => item.type === "add-comment" ); if (commentItems.length === 0) { - core.info("No add-issue-comment items found in agent output"); + core.info("No add-comment items found in agent output"); return; } - core.info(`Found ${commentItems.length} add-issue-comment item(s)`); + core.info(`Found ${commentItems.length} add-comment item(s)`); // If in staged mode, emit step summary instead of creating comments if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n"; @@ -2570,7 +2579,7 @@ jobs: for (let i = 0; i < commentItems.length; i++) { const commentItem = commentItems[i]; core.info( - `Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` + `Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` ); // Determine the issue/PR number and comment endpoint for this comment let issueNumber; diff --git a/.github/workflows/ci-doctor.md b/.github/workflows/ci-doctor.md index e8743b0af..930d3d7d9 100644 --- a/.github/workflows/ci-doctor.md +++ b/.github/workflows/ci-doctor.md @@ -18,7 +18,7 @@ network: defaults safe-outputs: create-issue: title-prefix: "${{ github.workflow }}" - add-issue-comment: + add-comment: tools: web-fetch: diff --git a/.github/workflows/daily-backlog-burner.lock.yml b/.github/workflows/daily-backlog-burner.lock.yml index ea0e0b89f..29b7593e9 100644 --- a/.github/workflows/daily-backlog-burner.lock.yml +++ b/.github/workflows/daily-backlog-burner.lock.yml @@ -2,7 +2,7 @@ # To update this file, edit the corresponding .md file and run: # gh aw compile # -# Effective stop-time: 2025-09-19 10:32:53 +# Effective stop-time: 2025-09-19 12:19:14 name: "Daily Backlog Burner" "on": @@ -55,7 +55,7 @@ jobs: main(); - name: Setup Safe Outputs Collector MCP env: - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" run: | mkdir -p /tmp/safe-outputs cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF' @@ -208,7 +208,7 @@ jobs: }, }, { - name: "add-issue-comment", + name: "add-comment", description: "Add a comment to a GitHub issue or pull request", inputSchema: { type: "object", @@ -315,7 +315,7 @@ jobs: }, }, { - name: "add-issue-labels", + name: "add-labels", description: "Add labels to a GitHub issue or pull request", inputSchema: { type: "object", @@ -489,7 +489,7 @@ jobs: - name: Setup MCPs env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" run: | mkdir -p /tmp/mcp-config cat > /tmp/mcp-config/mcp-servers.json << 'EOF' @@ -527,7 +527,7 @@ jobs: WORKFLOW_NAME="Daily Backlog Burner" # Check stop-time limit - STOP_TIME="2025-09-19 10:32:53" + STOP_TIME="2025-09-19 12:19:14" echo "Checking stop-time limit: $STOP_TIME" # Convert stop time to epoch seconds @@ -668,7 +668,7 @@ jobs: **Adding a Comment to an Issue or Pull Request** - To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP + To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP **Creating an Issue** @@ -854,7 +854,7 @@ jobs: uses: actions/github-script@v8 env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" with: script: | async function main() { @@ -887,15 +887,12 @@ jobs: let sanitized = content; // Neutralize @mentions to prevent unintended notifications sanitized = neutralizeMentions(sanitized); + // Remove XML comments to prevent content hiding + sanitized = removeXmlComments(sanitized); + // Remove ANSI escape sequences BEFORE removing control characters + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); // Remove control characters (except newlines and tabs) sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - // XML character escaping - sanitized = sanitized - .replace(/&/g, "&") // Must be first to avoid double-escaping - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); // URI filtering - replace non-https protocols with "(redacted)" sanitized = sanitizeUrlProtocols(sanitized); // Domain filtering for HTTPS URIs @@ -915,8 +912,7 @@ jobs: lines.slice(0, maxLines).join("\n") + "\n[Content truncated due to line count]"; } - // Remove ANSI escape sequences - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + // ANSI escape sequences already removed earlier in the function // Neutralize common bot trigger phrases sanitized = neutralizeBotTriggers(sanitized); // Trim excessive whitespace @@ -928,10 +924,12 @@ jobs: */ function sanitizeUrlDomains(s) { return s.replace( - /\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi, - (match, domain) => { + /\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi, + (match) => { + // Extract just the URL part after https:// + const urlAfterProtocol = match.slice(8); // Remove 'https://' // Extract the hostname part (before first slash, colon, or other delimiter) - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase(); // Check if this domain or any parent domain is in the allowlist const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); @@ -950,9 +948,10 @@ jobs: * @returns {string} The string with non-https protocols redacted */ function sanitizeUrlProtocols(s) { - // Match both protocol:// and protocol: patterns + // Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs + // Avoid matching command line flags like -v:10 or z3 -memory:high return s.replace( - /\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, + /\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { // Allow https (case insensitive), redact everything else return protocol.toLowerCase() === "https" ? match : "(redacted)"; @@ -971,6 +970,16 @@ jobs: (_m, p1, p2) => `${p1}\`@${p2}\`` ); } + /** + * Removes XML comments to prevent content hiding + * @param {string} s - The string to process + * @returns {string} The string with XML comments removed + */ + function removeXmlComments(s) { + // Remove XML/HTML comments including malformed ones that might be used to hide content + // Matches: and and variations + return s.replace(//g, "").replace(//g, ""); + } /** * Neutralizes bot trigger phrases by wrapping them in backticks * @param {string} s - The string to process @@ -1004,13 +1013,13 @@ jobs: switch (itemType) { case "create-issue": return 1; // Only one issue allowed - case "add-issue-comment": + case "add-comment": return 1; // Only one comment allowed case "create-pull-request": return 1; // Only one pull request allowed case "create-pull-request-review-comment": return 10; // Default to 10 review comments allowed - case "add-issue-labels": + case "add-labels": return 5; // Only one labels operation allowed case "update-issue": return 1; // Only one issue update allowed @@ -1225,10 +1234,10 @@ jobs: ); } break; - case "add-issue-comment": + case "add-comment": if (!item.body || typeof item.body !== "string") { errors.push( - `Line ${i + 1}: add-issue-comment requires a 'body' string field` + `Line ${i + 1}: add-comment requires a 'body' string field` ); continue; } @@ -1263,10 +1272,10 @@ jobs: ); } break; - case "add-issue-labels": + case "add-labels": if (!item.labels || !Array.isArray(item.labels)) { errors.push( - `Line ${i + 1}: add-issue-labels requires a 'labels' array field` + `Line ${i + 1}: add-labels requires a 'labels' array field` ); continue; } @@ -1276,7 +1285,7 @@ jobs: ) ) { errors.push( - `Line ${i + 1}: add-issue-labels labels array must contain only strings` + `Line ${i + 1}: add-labels labels array must contain only strings` ); continue; } @@ -2406,6 +2415,7 @@ jobs: GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-backlog-burner.outputs.output }} GITHUB_AW_ISSUE_TITLE_PREFIX: "${{ github.workflow }}" with: + github-token: ${{ secrets.DSYME_GH_TOKEN}} script: | async function main() { // Check if we're in staged mode @@ -2595,16 +2605,17 @@ jobs: pull-requests: write timeout-minutes: 10 outputs: - comment_id: ${{ steps.create_comment.outputs.comment_id }} - comment_url: ${{ steps.create_comment.outputs.comment_url }} + comment_id: ${{ steps.add_comment.outputs.comment_id }} + comment_url: ${{ steps.add_comment.outputs.comment_url }} steps: - name: Add Issue Comment - id: create_comment + id: add_comment uses: actions/github-script@v8 env: GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-backlog-burner.outputs.output }} GITHUB_AW_COMMENT_TARGET: "*" with: + github-token: ${{ secrets.DSYME_GH_TOKEN}} script: | async function main() { // Check if we're in staged mode @@ -2634,15 +2645,15 @@ jobs: core.info("No valid items found in agent output"); return; } - // Find all add-issue-comment items + // Find all add-comment items const commentItems = validatedOutput.items.filter( - /** @param {any} item */ item => item.type === "add-issue-comment" + /** @param {any} item */ item => item.type === "add-comment" ); if (commentItems.length === 0) { - core.info("No add-issue-comment items found in agent output"); + core.info("No add-comment items found in agent output"); return; } - core.info(`Found ${commentItems.length} add-issue-comment item(s)`); + core.info(`Found ${commentItems.length} add-comment item(s)`); // If in staged mode, emit step summary instead of creating comments if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n"; @@ -2686,7 +2697,7 @@ jobs: for (let i = 0; i < commentItems.length; i++) { const commentItem = commentItems[i]; core.info( - `Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` + `Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` ); // Determine the issue/PR number and comment endpoint for this comment let issueNumber; @@ -2827,6 +2838,7 @@ jobs: GITHUB_AW_PR_DRAFT: "true" GITHUB_AW_PR_IF_NO_CHANGES: "warn" with: + github-token: ${{ secrets.DSYME_GH_TOKEN}} script: | /** @type {typeof import("fs")} */ const fs = require("fs"); diff --git a/.github/workflows/daily-backlog-burner.md b/.github/workflows/daily-backlog-burner.md index 1bd2d81bf..eca1fc341 100644 --- a/.github/workflows/daily-backlog-burner.md +++ b/.github/workflows/daily-backlog-burner.md @@ -14,11 +14,12 @@ safe-outputs: create-issue: title-prefix: "${{ github.workflow }}" max: 3 - add-issue-comment: + add-comment: target: "*" # all issues and PRs max: 3 create-pull-request: draft: true + github-token: ${{ secrets.DSYME_GH_TOKEN}} tools: web-fetch: diff --git a/.github/workflows/daily-perf-improver.lock.yml b/.github/workflows/daily-perf-improver.lock.yml index 0d41ffa35..e72ffb56f 100644 --- a/.github/workflows/daily-perf-improver.lock.yml +++ b/.github/workflows/daily-perf-improver.lock.yml @@ -2,7 +2,7 @@ # To update this file, edit the corresponding .md file and run: # gh aw compile # -# Effective stop-time: 2025-09-19 10:32:53 +# Effective stop-time: 2025-09-19 12:19:14 name: "Daily Perf Improver" "on": @@ -69,7 +69,7 @@ jobs: main(); - name: Setup Safe Outputs Collector MCP env: - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" run: | mkdir -p /tmp/safe-outputs cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF' @@ -222,7 +222,7 @@ jobs: }, }, { - name: "add-issue-comment", + name: "add-comment", description: "Add a comment to a GitHub issue or pull request", inputSchema: { type: "object", @@ -329,7 +329,7 @@ jobs: }, }, { - name: "add-issue-labels", + name: "add-labels", description: "Add labels to a GitHub issue or pull request", inputSchema: { type: "object", @@ -503,7 +503,7 @@ jobs: - name: Setup MCPs env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" run: | mkdir -p /tmp/mcp-config cat > /tmp/mcp-config/mcp-servers.json << 'EOF' @@ -541,7 +541,7 @@ jobs: WORKFLOW_NAME="Daily Perf Improver" # Check stop-time limit - STOP_TIME="2025-09-19 10:32:53" + STOP_TIME="2025-09-19 12:19:14" echo "Checking stop-time limit: $STOP_TIME" # Convert stop time to epoch seconds @@ -743,7 +743,7 @@ jobs: **Adding a Comment to an Issue or Pull Request** - To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP + To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP **Creating an Issue** @@ -929,7 +929,7 @@ jobs: uses: actions/github-script@v8 env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true}" with: script: | async function main() { @@ -962,15 +962,12 @@ jobs: let sanitized = content; // Neutralize @mentions to prevent unintended notifications sanitized = neutralizeMentions(sanitized); + // Remove XML comments to prevent content hiding + sanitized = removeXmlComments(sanitized); + // Remove ANSI escape sequences BEFORE removing control characters + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); // Remove control characters (except newlines and tabs) sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - // XML character escaping - sanitized = sanitized - .replace(/&/g, "&") // Must be first to avoid double-escaping - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); // URI filtering - replace non-https protocols with "(redacted)" sanitized = sanitizeUrlProtocols(sanitized); // Domain filtering for HTTPS URIs @@ -990,8 +987,7 @@ jobs: lines.slice(0, maxLines).join("\n") + "\n[Content truncated due to line count]"; } - // Remove ANSI escape sequences - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + // ANSI escape sequences already removed earlier in the function // Neutralize common bot trigger phrases sanitized = neutralizeBotTriggers(sanitized); // Trim excessive whitespace @@ -1003,10 +999,12 @@ jobs: */ function sanitizeUrlDomains(s) { return s.replace( - /\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi, - (match, domain) => { + /\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi, + (match) => { + // Extract just the URL part after https:// + const urlAfterProtocol = match.slice(8); // Remove 'https://' // Extract the hostname part (before first slash, colon, or other delimiter) - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase(); // Check if this domain or any parent domain is in the allowlist const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); @@ -1025,9 +1023,10 @@ jobs: * @returns {string} The string with non-https protocols redacted */ function sanitizeUrlProtocols(s) { - // Match both protocol:// and protocol: patterns + // Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs + // Avoid matching command line flags like -v:10 or z3 -memory:high return s.replace( - /\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, + /\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { // Allow https (case insensitive), redact everything else return protocol.toLowerCase() === "https" ? match : "(redacted)"; @@ -1046,6 +1045,16 @@ jobs: (_m, p1, p2) => `${p1}\`@${p2}\`` ); } + /** + * Removes XML comments to prevent content hiding + * @param {string} s - The string to process + * @returns {string} The string with XML comments removed + */ + function removeXmlComments(s) { + // Remove XML/HTML comments including malformed ones that might be used to hide content + // Matches: and and variations + return s.replace(//g, "").replace(//g, ""); + } /** * Neutralizes bot trigger phrases by wrapping them in backticks * @param {string} s - The string to process @@ -1079,13 +1088,13 @@ jobs: switch (itemType) { case "create-issue": return 1; // Only one issue allowed - case "add-issue-comment": + case "add-comment": return 1; // Only one comment allowed case "create-pull-request": return 1; // Only one pull request allowed case "create-pull-request-review-comment": return 10; // Default to 10 review comments allowed - case "add-issue-labels": + case "add-labels": return 5; // Only one labels operation allowed case "update-issue": return 1; // Only one issue update allowed @@ -1300,10 +1309,10 @@ jobs: ); } break; - case "add-issue-comment": + case "add-comment": if (!item.body || typeof item.body !== "string") { errors.push( - `Line ${i + 1}: add-issue-comment requires a 'body' string field` + `Line ${i + 1}: add-comment requires a 'body' string field` ); continue; } @@ -1338,10 +1347,10 @@ jobs: ); } break; - case "add-issue-labels": + case "add-labels": if (!item.labels || !Array.isArray(item.labels)) { errors.push( - `Line ${i + 1}: add-issue-labels requires a 'labels' array field` + `Line ${i + 1}: add-labels requires a 'labels' array field` ); continue; } @@ -1351,7 +1360,7 @@ jobs: ) ) { errors.push( - `Line ${i + 1}: add-issue-labels labels array must contain only strings` + `Line ${i + 1}: add-labels labels array must contain only strings` ); continue; } @@ -2481,6 +2490,7 @@ jobs: GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-perf-improver.outputs.output }} GITHUB_AW_ISSUE_TITLE_PREFIX: "${{ github.workflow }}" with: + github-token: ${{ secrets.DSYME_GH_TOKEN}} script: | async function main() { // Check if we're in staged mode @@ -2670,16 +2680,17 @@ jobs: pull-requests: write timeout-minutes: 10 outputs: - comment_id: ${{ steps.create_comment.outputs.comment_id }} - comment_url: ${{ steps.create_comment.outputs.comment_url }} + comment_id: ${{ steps.add_comment.outputs.comment_id }} + comment_url: ${{ steps.add_comment.outputs.comment_url }} steps: - name: Add Issue Comment - id: create_comment + id: add_comment uses: actions/github-script@v8 env: GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-perf-improver.outputs.output }} GITHUB_AW_COMMENT_TARGET: "*" with: + github-token: ${{ secrets.DSYME_GH_TOKEN}} script: | async function main() { // Check if we're in staged mode @@ -2709,15 +2720,15 @@ jobs: core.info("No valid items found in agent output"); return; } - // Find all add-issue-comment items + // Find all add-comment items const commentItems = validatedOutput.items.filter( - /** @param {any} item */ item => item.type === "add-issue-comment" + /** @param {any} item */ item => item.type === "add-comment" ); if (commentItems.length === 0) { - core.info("No add-issue-comment items found in agent output"); + core.info("No add-comment items found in agent output"); return; } - core.info(`Found ${commentItems.length} add-issue-comment item(s)`); + core.info(`Found ${commentItems.length} add-comment item(s)`); // If in staged mode, emit step summary instead of creating comments if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n"; @@ -2761,7 +2772,7 @@ jobs: for (let i = 0; i < commentItems.length; i++) { const commentItem = commentItems[i]; core.info( - `Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` + `Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` ); // Determine the issue/PR number and comment endpoint for this comment let issueNumber; @@ -2902,6 +2913,7 @@ jobs: GITHUB_AW_PR_DRAFT: "true" GITHUB_AW_PR_IF_NO_CHANGES: "warn" with: + github-token: ${{ secrets.DSYME_GH_TOKEN}} script: | /** @type {typeof import("fs")} */ const fs = require("fs"); diff --git a/.github/workflows/daily-perf-improver.md b/.github/workflows/daily-perf-improver.md index 80b965852..c0169e99f 100644 --- a/.github/workflows/daily-perf-improver.md +++ b/.github/workflows/daily-perf-improver.md @@ -16,10 +16,11 @@ safe-outputs: create-issue: title-prefix: "${{ github.workflow }}" max: 5 - add-issue-comment: + add-comment: target: "*" # can add a comment to any one single issue or pull request create-pull-request: draft: true + github-token: ${{ secrets.DSYME_GH_TOKEN}} tools: web-fetch: diff --git a/.github/workflows/daily-test-improver.lock.yml b/.github/workflows/daily-test-improver.lock.yml index 05bcf7ed5..465f0eb33 100644 --- a/.github/workflows/daily-test-improver.lock.yml +++ b/.github/workflows/daily-test-improver.lock.yml @@ -2,7 +2,7 @@ # To update this file, edit the corresponding .md file and run: # gh aw compile # -# Effective stop-time: 2025-09-19 10:32:53 +# Effective stop-time: 2025-09-19 12:19:15 name: "Daily Test Coverage Improver" "on": @@ -69,7 +69,7 @@ jobs: main(); - name: Setup Safe Outputs Collector MCP env: - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}" run: | mkdir -p /tmp/safe-outputs cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF' @@ -222,7 +222,7 @@ jobs: }, }, { - name: "add-issue-comment", + name: "add-comment", description: "Add a comment to a GitHub issue or pull request", inputSchema: { type: "object", @@ -329,7 +329,7 @@ jobs: }, }, { - name: "add-issue-labels", + name: "add-labels", description: "Add labels to a GitHub issue or pull request", inputSchema: { type: "object", @@ -503,7 +503,7 @@ jobs: - name: Setup MCPs env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}" run: | mkdir -p /tmp/mcp-config cat > /tmp/mcp-config/mcp-servers.json << 'EOF' @@ -541,7 +541,7 @@ jobs: WORKFLOW_NAME="Daily Test Coverage Improver" # Check stop-time limit - STOP_TIME="2025-09-19 10:32:53" + STOP_TIME="2025-09-19 12:19:15" echo "Checking stop-time limit: $STOP_TIME" # Convert stop time to epoch seconds @@ -714,7 +714,7 @@ jobs: **Adding a Comment to an Issue or Pull Request** - To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP + To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP **Creating an Issue** @@ -904,7 +904,7 @@ jobs: uses: actions/github-script@v8 env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true,\"target\":\"*\"},\"create-issue\":true,\"create-pull-request\":true,\"update-issue\":true}" with: script: | async function main() { @@ -937,15 +937,12 @@ jobs: let sanitized = content; // Neutralize @mentions to prevent unintended notifications sanitized = neutralizeMentions(sanitized); + // Remove XML comments to prevent content hiding + sanitized = removeXmlComments(sanitized); + // Remove ANSI escape sequences BEFORE removing control characters + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); // Remove control characters (except newlines and tabs) sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - // XML character escaping - sanitized = sanitized - .replace(/&/g, "&") // Must be first to avoid double-escaping - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); // URI filtering - replace non-https protocols with "(redacted)" sanitized = sanitizeUrlProtocols(sanitized); // Domain filtering for HTTPS URIs @@ -965,8 +962,7 @@ jobs: lines.slice(0, maxLines).join("\n") + "\n[Content truncated due to line count]"; } - // Remove ANSI escape sequences - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + // ANSI escape sequences already removed earlier in the function // Neutralize common bot trigger phrases sanitized = neutralizeBotTriggers(sanitized); // Trim excessive whitespace @@ -978,10 +974,12 @@ jobs: */ function sanitizeUrlDomains(s) { return s.replace( - /\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi, - (match, domain) => { + /\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi, + (match) => { + // Extract just the URL part after https:// + const urlAfterProtocol = match.slice(8); // Remove 'https://' // Extract the hostname part (before first slash, colon, or other delimiter) - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase(); // Check if this domain or any parent domain is in the allowlist const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); @@ -1000,9 +998,10 @@ jobs: * @returns {string} The string with non-https protocols redacted */ function sanitizeUrlProtocols(s) { - // Match both protocol:// and protocol: patterns + // Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs + // Avoid matching command line flags like -v:10 or z3 -memory:high return s.replace( - /\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, + /\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { // Allow https (case insensitive), redact everything else return protocol.toLowerCase() === "https" ? match : "(redacted)"; @@ -1021,6 +1020,16 @@ jobs: (_m, p1, p2) => `${p1}\`@${p2}\`` ); } + /** + * Removes XML comments to prevent content hiding + * @param {string} s - The string to process + * @returns {string} The string with XML comments removed + */ + function removeXmlComments(s) { + // Remove XML/HTML comments including malformed ones that might be used to hide content + // Matches: and and variations + return s.replace(//g, "").replace(//g, ""); + } /** * Neutralizes bot trigger phrases by wrapping them in backticks * @param {string} s - The string to process @@ -1054,13 +1063,13 @@ jobs: switch (itemType) { case "create-issue": return 1; // Only one issue allowed - case "add-issue-comment": + case "add-comment": return 1; // Only one comment allowed case "create-pull-request": return 1; // Only one pull request allowed case "create-pull-request-review-comment": return 10; // Default to 10 review comments allowed - case "add-issue-labels": + case "add-labels": return 5; // Only one labels operation allowed case "update-issue": return 1; // Only one issue update allowed @@ -1275,10 +1284,10 @@ jobs: ); } break; - case "add-issue-comment": + case "add-comment": if (!item.body || typeof item.body !== "string") { errors.push( - `Line ${i + 1}: add-issue-comment requires a 'body' string field` + `Line ${i + 1}: add-comment requires a 'body' string field` ); continue; } @@ -1313,10 +1322,10 @@ jobs: ); } break; - case "add-issue-labels": + case "add-labels": if (!item.labels || !Array.isArray(item.labels)) { errors.push( - `Line ${i + 1}: add-issue-labels requires a 'labels' array field` + `Line ${i + 1}: add-labels requires a 'labels' array field` ); continue; } @@ -1326,7 +1335,7 @@ jobs: ) ) { errors.push( - `Line ${i + 1}: add-issue-labels labels array must contain only strings` + `Line ${i + 1}: add-labels labels array must contain only strings` ); continue; } @@ -2456,6 +2465,7 @@ jobs: GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-test-coverage-improver.outputs.output }} GITHUB_AW_ISSUE_TITLE_PREFIX: "${{ github.workflow }}" with: + github-token: ${{ secrets.DSYME_GH_TOKEN}} script: | async function main() { // Check if we're in staged mode @@ -2645,16 +2655,17 @@ jobs: pull-requests: write timeout-minutes: 10 outputs: - comment_id: ${{ steps.create_comment.outputs.comment_id }} - comment_url: ${{ steps.create_comment.outputs.comment_url }} + comment_id: ${{ steps.add_comment.outputs.comment_id }} + comment_url: ${{ steps.add_comment.outputs.comment_url }} steps: - name: Add Issue Comment - id: create_comment + id: add_comment uses: actions/github-script@v8 env: GITHUB_AW_AGENT_OUTPUT: ${{ needs.daily-test-coverage-improver.outputs.output }} GITHUB_AW_COMMENT_TARGET: "*" with: + github-token: ${{ secrets.DSYME_GH_TOKEN}} script: | async function main() { // Check if we're in staged mode @@ -2684,15 +2695,15 @@ jobs: core.info("No valid items found in agent output"); return; } - // Find all add-issue-comment items + // Find all add-comment items const commentItems = validatedOutput.items.filter( - /** @param {any} item */ item => item.type === "add-issue-comment" + /** @param {any} item */ item => item.type === "add-comment" ); if (commentItems.length === 0) { - core.info("No add-issue-comment items found in agent output"); + core.info("No add-comment items found in agent output"); return; } - core.info(`Found ${commentItems.length} add-issue-comment item(s)`); + core.info(`Found ${commentItems.length} add-comment item(s)`); // If in staged mode, emit step summary instead of creating comments if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n"; @@ -2736,7 +2747,7 @@ jobs: for (let i = 0; i < commentItems.length; i++) { const commentItem = commentItems[i]; core.info( - `Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` + `Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` ); // Determine the issue/PR number and comment endpoint for this comment let issueNumber; @@ -2877,6 +2888,7 @@ jobs: GITHUB_AW_PR_DRAFT: "true" GITHUB_AW_PR_IF_NO_CHANGES: "warn" with: + github-token: ${{ secrets.DSYME_GH_TOKEN}} script: | /** @type {typeof import("fs")} */ const fs = require("fs"); @@ -3197,6 +3209,7 @@ jobs: GITHUB_AW_UPDATE_BODY: true GITHUB_AW_UPDATE_TARGET: "*" with: + github-token: ${{ secrets.DSYME_GH_TOKEN}} script: | async function main() { // Check if we're in staged mode diff --git a/.github/workflows/daily-test-improver.md b/.github/workflows/daily-test-improver.md index 7c9f1c01b..893f64efd 100644 --- a/.github/workflows/daily-test-improver.md +++ b/.github/workflows/daily-test-improver.md @@ -19,10 +19,11 @@ safe-outputs: target: "*" # one single issue body: # can update the issue title/body only title: # can update the issue title/body only - add-issue-comment: + add-comment: target: "*" # can add a comment to any one single issue or pull request create-pull-request: # can create a pull request draft: true + github-token: ${{ secrets.DSYME_GH_TOKEN}} tools: web-fetch: diff --git a/.github/workflows/pr-fix.lock.yml b/.github/workflows/pr-fix.lock.yml index c834bbf53..9810ca9d0 100644 --- a/.github/workflows/pr-fix.lock.yml +++ b/.github/workflows/pr-fix.lock.yml @@ -2,7 +2,7 @@ # To update this file, edit the corresponding .md file and run: # gh aw compile # -# Effective stop-time: 2025-09-19 10:32:53 +# Effective stop-time: 2025-09-19 12:19:15 name: "PR Fix" on: @@ -599,7 +599,7 @@ jobs: main(); - name: Setup Safe Outputs Collector MCP env: - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}" run: | mkdir -p /tmp/safe-outputs cat > /tmp/safe-outputs/mcp-server.cjs << 'EOF' @@ -752,7 +752,7 @@ jobs: }, }, { - name: "add-issue-comment", + name: "add-comment", description: "Add a comment to a GitHub issue or pull request", inputSchema: { type: "object", @@ -859,7 +859,7 @@ jobs: }, }, { - name: "add-issue-labels", + name: "add-labels", description: "Add labels to a GitHub issue or pull request", inputSchema: { type: "object", @@ -1033,7 +1033,7 @@ jobs: - name: Setup MCPs env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}" run: | mkdir -p /tmp/mcp-config cat > /tmp/mcp-config/mcp-servers.json << 'EOF' @@ -1071,7 +1071,7 @@ jobs: WORKFLOW_NAME="PR Fix" # Check stop-time limit - STOP_TIME="2025-09-19 10:32:53" + STOP_TIME="2025-09-19 12:19:15" echo "Checking stop-time limit: $STOP_TIME" # Convert stop time to epoch seconds @@ -1174,7 +1174,7 @@ jobs: **Adding a Comment to an Issue or Pull Request** - To add a comment to an issue or pull request, use the add-issue-comments tool from the safe-outputs MCP + To add a comment to an issue or pull request, use the add-comments tool from the safe-outputs MCP **Creating an Issue** @@ -1358,7 +1358,7 @@ jobs: uses: actions/github-script@v8 env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} - GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-issue-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}" + GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"enabled\":true},\"create-issue\":true,\"push-to-pr-branch\":{\"enabled\":true}}" with: script: | async function main() { @@ -1391,15 +1391,12 @@ jobs: let sanitized = content; // Neutralize @mentions to prevent unintended notifications sanitized = neutralizeMentions(sanitized); + // Remove XML comments to prevent content hiding + sanitized = removeXmlComments(sanitized); + // Remove ANSI escape sequences BEFORE removing control characters + sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); // Remove control characters (except newlines and tabs) sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); - // XML character escaping - sanitized = sanitized - .replace(/&/g, "&") // Must be first to avoid double-escaping - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); // URI filtering - replace non-https protocols with "(redacted)" sanitized = sanitizeUrlProtocols(sanitized); // Domain filtering for HTTPS URIs @@ -1419,8 +1416,7 @@ jobs: lines.slice(0, maxLines).join("\n") + "\n[Content truncated due to line count]"; } - // Remove ANSI escape sequences - sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); + // ANSI escape sequences already removed earlier in the function // Neutralize common bot trigger phrases sanitized = neutralizeBotTriggers(sanitized); // Trim excessive whitespace @@ -1432,10 +1428,12 @@ jobs: */ function sanitizeUrlDomains(s) { return s.replace( - /\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f]+)/gi, - (match, domain) => { + /\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi, + (match) => { + // Extract just the URL part after https:// + const urlAfterProtocol = match.slice(8); // Remove 'https://' // Extract the hostname part (before first slash, colon, or other delimiter) - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase(); // Check if this domain or any parent domain is in the allowlist const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); @@ -1454,9 +1452,10 @@ jobs: * @returns {string} The string with non-https protocols redacted */ function sanitizeUrlProtocols(s) { - // Match both protocol:// and protocol: patterns + // Match protocol:// patterns (URLs) and standalone protocol: patterns that look like URLs + // Avoid matching command line flags like -v:10 or z3 -memory:high return s.replace( - /\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, + /\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { // Allow https (case insensitive), redact everything else return protocol.toLowerCase() === "https" ? match : "(redacted)"; @@ -1475,6 +1474,16 @@ jobs: (_m, p1, p2) => `${p1}\`@${p2}\`` ); } + /** + * Removes XML comments to prevent content hiding + * @param {string} s - The string to process + * @returns {string} The string with XML comments removed + */ + function removeXmlComments(s) { + // Remove XML/HTML comments including malformed ones that might be used to hide content + // Matches: and and variations + return s.replace(//g, "").replace(//g, ""); + } /** * Neutralizes bot trigger phrases by wrapping them in backticks * @param {string} s - The string to process @@ -1508,13 +1517,13 @@ jobs: switch (itemType) { case "create-issue": return 1; // Only one issue allowed - case "add-issue-comment": + case "add-comment": return 1; // Only one comment allowed case "create-pull-request": return 1; // Only one pull request allowed case "create-pull-request-review-comment": return 10; // Default to 10 review comments allowed - case "add-issue-labels": + case "add-labels": return 5; // Only one labels operation allowed case "update-issue": return 1; // Only one issue update allowed @@ -1729,10 +1738,10 @@ jobs: ); } break; - case "add-issue-comment": + case "add-comment": if (!item.body || typeof item.body !== "string") { errors.push( - `Line ${i + 1}: add-issue-comment requires a 'body' string field` + `Line ${i + 1}: add-comment requires a 'body' string field` ); continue; } @@ -1767,10 +1776,10 @@ jobs: ); } break; - case "add-issue-labels": + case "add-labels": if (!item.labels || !Array.isArray(item.labels)) { errors.push( - `Line ${i + 1}: add-issue-labels requires a 'labels' array field` + `Line ${i + 1}: add-labels requires a 'labels' array field` ); continue; } @@ -1780,7 +1789,7 @@ jobs: ) ) { errors.push( - `Line ${i + 1}: add-issue-labels labels array must contain only strings` + `Line ${i + 1}: add-labels labels array must contain only strings` ); continue; } @@ -3019,11 +3028,11 @@ jobs: pull-requests: write timeout-minutes: 10 outputs: - comment_id: ${{ steps.create_comment.outputs.comment_id }} - comment_url: ${{ steps.create_comment.outputs.comment_url }} + comment_id: ${{ steps.add_comment.outputs.comment_id }} + comment_url: ${{ steps.add_comment.outputs.comment_url }} steps: - name: Add Issue Comment - id: create_comment + id: add_comment uses: actions/github-script@v8 env: GITHUB_AW_AGENT_OUTPUT: ${{ needs.pr-fix.outputs.output }} @@ -3058,15 +3067,15 @@ jobs: core.info("No valid items found in agent output"); return; } - // Find all add-issue-comment items + // Find all add-comment items const commentItems = validatedOutput.items.filter( - /** @param {any} item */ item => item.type === "add-issue-comment" + /** @param {any} item */ item => item.type === "add-comment" ); if (commentItems.length === 0) { - core.info("No add-issue-comment items found in agent output"); + core.info("No add-comment items found in agent output"); return; } - core.info(`Found ${commentItems.length} add-issue-comment item(s)`); + core.info(`Found ${commentItems.length} add-comment item(s)`); // If in staged mode, emit step summary instead of creating comments if (isStaged) { let summaryContent = "## 🎭 Staged Mode: Add Comments Preview\n\n"; @@ -3110,7 +3119,7 @@ jobs: for (let i = 0; i < commentItems.length; i++) { const commentItem = commentItems[i]; core.info( - `Processing add-issue-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` + `Processing add-comment item ${i + 1}/${commentItems.length}: bodyLength=${commentItem.body.length}` ); // Determine the issue/PR number and comment endpoint for this comment let issueNumber; diff --git a/.github/workflows/pr-fix.md b/.github/workflows/pr-fix.md index 2c46eb60a..146c9eb1f 100644 --- a/.github/workflows/pr-fix.md +++ b/.github/workflows/pr-fix.md @@ -14,7 +14,7 @@ safe-outputs: push-to-pr-branch: create-issue: title-prefix: "${{ github.workflow }}" - add-issue-comment: + add-comment: github-token: ${{ secrets.DSYME_GH_TOKEN}} tools: