From d26308bed9fa7e5178b0a17a350dad8250323332 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 07:46:21 +0000 Subject: [PATCH 01/10] Initial plan From 5342f5a1b22d015590829ec02128b9d4b9eadd8a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 07:49:24 +0000 Subject: [PATCH 02/10] Address review feedback: sanitization, state management, and validation improvements Co-authored-by: DimaBir <28827735+DimaBir@users.noreply.github.com> --- .github/issue-assistant/src/security.js | 18 ++------ .github/workflows/issue-assistant.yml | 55 +++++++++++++++++++++---- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/.github/issue-assistant/src/security.js b/.github/issue-assistant/src/security.js index a4d3f9b9..60bdace6 100644 --- a/.github/issue-assistant/src/security.js +++ b/.github/issue-assistant/src/security.js @@ -227,21 +227,9 @@ async function validateRequest({ errors.push('Rate limit exceeded'); } - if (comment && maxBotResponses !== undefined) { - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number - }); - - const botComments = comments.filter(c => - c.body && c.body.includes('') - ); - - if (botComments.length >= maxBotResponses) { - errors.push('Maximum bot responses reached'); - } - } + // Note: Bot response count per issue is now validated in the conversation-state step + // of the workflow, not here. This avoids redundant validation and keeps state + // management centralized. return { shouldRespond: errors.length === 0, diff --git a/.github/workflows/issue-assistant.yml b/.github/workflows/issue-assistant.yml index d32a4f92..9228fba4 100644 --- a/.github/workflows/issue-assistant.yml +++ b/.github/workflows/issue-assistant.yml @@ -37,7 +37,6 @@ jobs: should_respond: ${{ (steps.conversation-state.outputs.should_respond == 'true' && steps.validation.outputs.validation_passed == 'true') ? 'true' : 'false' }} conversation_state: ${{ steps.conversation-state.outputs.state }} conversation_history: ${{ steps.conversation-state.outputs.history }} - sanitized_content: ${{ steps.validation.outputs.sanitized_content }} issue_type: ${{ steps.validation.outputs.issue_type }} wiki_context: ${{ steps.wiki.outputs.context }} @@ -96,7 +95,7 @@ jobs: } // 2. Terminal states - don't respond further - if (['resolved', 'escalated', 'closed'].includes(currentState)) { + if (['resolved', 'escalated'].includes(currentState)) { console.log(`Terminal state "${currentState}" - no more responses`); core.setOutput('should_respond', 'false'); core.setOutput('state', currentState); @@ -142,19 +141,40 @@ jobs: } // === BUILD CONVERSATION HISTORY === + // Simple sanitization function to prevent injection attacks + const sanitizeContent = (content, maxLength = 10000) => { + if (!content) return ''; + let sanitized = content + .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '') + .replace(/[^\S\r\n]+/g, ' ') + .replace(/\n{3,}/g, '\n\n') + .trim(); + if (sanitized.length > maxLength) { + sanitized = sanitized.substring(0, maxLength) + '... [truncated]'; + } + return sanitized; + }; + const history = []; - // Add issue body as first message + // Add issue body as first message (sanitize it) + const issueContent = `[Issue opened] ${issue.title}\n\n${issue.body || '(no description)'}`; history.push({ role: 'user', author: issueAuthor, - content: `[Issue opened] ${issue.title}\n\n${issue.body || '(no description)'}`, + content: sanitizeContent(issueContent), timestamp: issue.created_at }); - // Add all comments in order + // Add comments from issue author and bot only (filter out other users) for (const comment of comments) { const isBot = comment.body.includes('