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(