diff --git a/tools/caretaker-agent/cloudrun/ingestion-service/app.test.ts b/tools/caretaker-agent/cloudrun/ingestion-service/app.test.ts index 9d1737be5ef..67739abca16 100644 --- a/tools/caretaker-agent/cloudrun/ingestion-service/app.test.ts +++ b/tools/caretaker-agent/cloudrun/ingestion-service/app.test.ts @@ -252,9 +252,12 @@ describe('Webhook Server Endpoint', () => { expect(sentData.body).toBe( '\nPlease fix this security bug\n', ); + expect(sentData.title).toBe( + '\nBugs everywhere\n', + ); }); - it('should escape untrusted_context tags in the issue body to prevent injection', async () => { + it('should escape untrusted_context tags in the issue body and title to prevent injection', async () => { mockVerifyGithubSignature.mockReturnValue(true); mockCreateIssue.mockResolvedValue(true); mockPublishMessage.mockResolvedValue('mock-msg-456'); @@ -263,7 +266,7 @@ describe('Webhook Server Endpoint', () => { action: 'opened', issue: { number: 2, - title: 'Injection test', + title: 'Injection test', body: 'Malicious attempt', }, repository: { @@ -282,6 +285,15 @@ describe('Webhook Server Endpoint', () => { expect(sentData.body).toBe( '\nMalicious \\ attempt\n', ); + expect(sentData.title).toBe( + '\nInjection \\ test\n', + ); + expect(mockCreateIssue).toHaveBeenCalledWith( + 'google', + 'gemini-cli', + 2, + 'Injection test', + ); }); it('should recover and publish to Pub/Sub on retry if issue is UNTRIAGED', async () => { diff --git a/tools/caretaker-agent/cloudrun/ingestion-service/app.ts b/tools/caretaker-agent/cloudrun/ingestion-service/app.ts index ac61099c776..71ad57121da 100644 --- a/tools/caretaker-agent/cloudrun/ingestion-service/app.ts +++ b/tools/caretaker-agent/cloudrun/ingestion-service/app.ts @@ -119,16 +119,23 @@ app.post('/webhook', limiter, async (req, res) => { ); const sanitizedBody = `\n${escapedBody}\n`; + const rawTitle = payload.issue.title || ''; + const escapedTitle = rawTitle.replace( + /<\/untrusted_context>/g, + '\\', + ); + const sanitizedTitle = `\n${escapedTitle}\n`; + const processedData = { issue_number: issueNumber, repository, sender: payload.sender?.login, body: sanitizedBody, - title: payload.issue.title, + title: sanitizedTitle, }; const [owner, repo] = repository.split('/'); - const title = processedData.title || ''; + const title = rawTitle; try { const created = await issuesStore.createIssue(