diff --git a/pkg/github/__toolsnaps__/search_code.snap b/pkg/github/__toolsnaps__/search_code.snap
index 313c2f4c..f6cce354 100644
--- a/pkg/github/__toolsnaps__/search_code.snap
+++ b/pkg/github/__toolsnaps__/search_code.snap
@@ -32,6 +32,9 @@
},
"sort": {
"description": "Sort field ('indexed' only)",
+ "enum": [
+ "indexed"
+ ],
"type": "string"
}
},
diff --git a/pkg/github/__toolsnaps__/search_code_ff_fields_param.snap b/pkg/github/__toolsnaps__/search_code_ff_fields_param.snap
index 00d46867..eafec0a6 100644
--- a/pkg/github/__toolsnaps__/search_code_ff_fields_param.snap
+++ b/pkg/github/__toolsnaps__/search_code_ff_fields_param.snap
@@ -46,6 +46,9 @@
},
"sort": {
"description": "Sort field ('indexed' only)",
+ "enum": [
+ "indexed"
+ ],
"type": "string"
}
},
diff --git a/pkg/github/actions.go b/pkg/github/actions.go
index c16efa0f..598039e2 100644
--- a/pkg/github/actions.go
+++ b/pkg/github/actions.go
@@ -84,7 +84,7 @@ func handleFailedJobLogs(ctx context.Context, client *github.Client, owner, repo
// Continue with other jobs even if one fails
jobResult = map[string]any{
"job_id": job.GetID(),
- "job_name": job.GetName(),
+ "job_name": sanitizeOutputText(job.GetName()),
"error": err.Error(),
}
// Enable reporting of status codes and error causes
@@ -139,7 +139,7 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin
"job_id": jobID,
}
if jobName != "" {
- result["job_name"] = jobName
+ result["job_name"] = sanitizeOutputText(jobName)
}
if returnContent {
@@ -788,7 +788,7 @@ func getWorkflow(ctx context.Context, client *github.Client, owner, repo, resour
}
defer func() { _ = resp.Body.Close() }()
- r, err := json.Marshal(workflow)
+ r, err := json.Marshal(sanitizedWorkflowCopy(workflow))
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal workflow: %w", err)
}
@@ -802,7 +802,7 @@ func getWorkflowRun(ctx context.Context, client *github.Client, owner, repo stri
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get workflow run", resp, err), nil, nil
}
defer func() { _ = resp.Body.Close() }()
- r, err := json.Marshal(workflowRun)
+ r, err := json.Marshal(sanitizedWorkflowRunCopy(workflowRun))
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal workflow run: %w", err)
}
@@ -815,7 +815,7 @@ func getWorkflowJob(ctx context.Context, client *github.Client, owner, repo stri
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get workflow job", resp, err), nil, nil
}
defer func() { _ = resp.Body.Close() }()
- r, err := json.Marshal(workflowJob)
+ r, err := json.Marshal(sanitizedWorkflowJobCopy(workflowJob))
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal workflow job: %w", err)
}
@@ -834,7 +834,7 @@ func listWorkflows(ctx context.Context, client *github.Client, owner, repo strin
}
defer func() { _ = resp.Body.Close() }()
- r, err := json.Marshal(workflows)
+ r, err := json.Marshal(sanitizedWorkflowsCopy(workflows))
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal workflows: %w", err)
}
@@ -884,7 +884,7 @@ func listWorkflowRuns(ctx context.Context, client *github.Client, args map[strin
}
defer func() { _ = resp.Body.Close() }()
- r, err := json.Marshal(workflowRuns)
+ r, err := json.Marshal(sanitizedWorkflowRunsCopy(workflowRuns))
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal workflow runs: %w", err)
}
@@ -919,7 +919,7 @@ func listWorkflowJobs(ctx context.Context, client *github.Client, args map[strin
}
response := map[string]any{
- "jobs": workflowJobs,
+ "jobs": sanitizedWorkflowJobsCopy(workflowJobs),
}
defer func() { _ = resp.Body.Close() }()
diff --git a/pkg/github/actions_test.go b/pkg/github/actions_test.go
index 4ed9c87d..1a03a1a9 100644
--- a/pkg/github/actions_test.go
+++ b/pkg/github/actions_test.go
@@ -114,22 +114,66 @@ func Test_ActionsList_ListWorkflows(t *testing.T) {
}
}
+func unsafeWorkflowRunFixture() *github.WorkflowRun {
+ return &github.WorkflowRun{
+ ID: github.Ptr(int64(12345)),
+ Name: github.Ptr(baselineUnsafeText),
+ DisplayTitle: github.Ptr(baselineUnsafeText),
+ HeadBranch: github.Ptr("feature/exactkeep\u200B ```go onclick=alert(1)\nfmt.Println(\"x\")\n```"
+
+func TestSanitizationIssueOutputPaths(t *testing.T) {
+ t.Run("rest get issue sanitizes title and body", func(t *testing.T) {
+ mockIssue := &gogithub.Issue{
+ Number: gogithub.Ptr(42),
+ Title: gogithub.Ptr(baselineUnsafeText),
+ Body: gogithub.Ptr(baselineUnsafeText),
+ State: gogithub.Ptr("open"),
+ HTMLURL: gogithub.Ptr("https://github.com/owner/repo/issues/42"),
+ User: &gogithub.User{Login: gogithub.Ptr("author")},
+ }
+ client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
+ GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue),
+ }))
+ deps := BaseDeps{
+ Client: client,
+ GQLClient: defaultGQLClient,
+ RepoAccessCache: stubRepoAccessCache(nil, 15*time.Minute),
+ Flags: stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
+ }
+ serverTool := IssueRead(translations.NullTranslationHelper)
+ handler := serverTool.Handler(deps)
+
+ result, err := handler(ContextWithDeps(context.Background(), deps), &mcp.CallToolRequest{
+ Params: createMCPRequest(map[string]any{
+ "method": "get",
+ "owner": "owner",
+ "repo": "repo",
+ "issue_number": float64(42),
+ }).Params,
+ })
+
+ require.NoError(t, err)
+ require.False(t, result.IsError)
+ var issue MinimalIssue
+ require.NoError(t, json.Unmarshal([]byte(getTextResult(t, result).Text), &issue))
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), issue.Title)
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), issue.Body)
+ assert.Equal(t, baselineUnsafeText, mockIssue.GetTitle())
+ assert.Equal(t, baselineUnsafeText, mockIssue.GetBody())
+ })
+
+ t.Run("rest search issue sanitizes embedded display fields without mutating source object", func(t *testing.T) {
+ sourceIssue := &gogithub.Issue{
+ Number: gogithub.Ptr(42),
+ Title: gogithub.Ptr(baselineUnsafeText),
+ Body: gogithub.Ptr(baselineUnsafeText),
+ State: gogithub.Ptr("open"),
+ Labels: []*gogithub.Label{{
+ Name: gogithub.Ptr(baselineUnsafeText),
+ Description: gogithub.Ptr(baselineUnsafeText),
+ }},
+ Milestone: &gogithub.Milestone{
+ Title: gogithub.Ptr(baselineUnsafeText),
+ Description: gogithub.Ptr(baselineUnsafeText),
+ },
+ }
+ searchHit := SearchIssueResult{Issue: sourceIssue}
+
+ raw, err := json.Marshal(searchHit)
+ require.NoError(t, err)
+ var issue map[string]any
+ require.NoError(t, json.Unmarshal(raw, &issue))
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), issue["title"])
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), issue["body"])
+ labels := issue["labels"].([]any)
+ label := labels[0].(map[string]any)
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), label["name"])
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), label["description"])
+ milestone := issue["milestone"].(map[string]any)
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), milestone["title"])
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), milestone["description"])
+ assert.Equal(t, baselineUnsafeText, sourceIssue.GetTitle())
+ assert.Equal(t, baselineUnsafeText, sourceIssue.GetBody())
+ assert.Equal(t, baselineUnsafeText, sourceIssue.Labels[0].GetName())
+ assert.Equal(t, baselineUnsafeText, sourceIssue.Milestone.GetTitle())
+ })
+
+ t.Run("graphql issue fragment conversion sanitizes title and body", func(t *testing.T) {
+ now := githubv4.DateTime{Time: time.Date(2026, 7, 3, 9, 0, 0, 0, time.UTC)}
+ fragment := IssueFragment{
+ Number: githubv4.Int(42),
+ Title: githubv4.String(baselineUnsafeText),
+ Body: githubv4.String(baselineUnsafeText),
+ State: githubv4.String("OPEN"),
+ CreatedAt: now,
+ UpdatedAt: now,
+ }
+ fragment.Author.Login = githubv4.String("author")
+ fragment.Labels.Nodes = append(fragment.Labels.Nodes, struct {
+ Name githubv4.String
+ ID githubv4.String
+ Description githubv4.String
+ }{Name: githubv4.String(baselineUnsafeText)})
+
+ issue := fragmentToMinimalIssue(fragment)
+
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), issue.Title)
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), issue.Body)
+ assert.Equal(t, []string{sanitize.Sanitize(baselineUnsafeText)}, issue.Labels)
+ })
+}
+
+func TestSanitizationPullRequestOutputPaths(t *testing.T) {
+ t.Run("rest get pull request sanitizes title and body without mutating source object", func(t *testing.T) {
+ mockPR := &gogithub.PullRequest{
+ Number: gogithub.Ptr(42),
+ Title: gogithub.Ptr(baselineUnsafeText),
+ Body: gogithub.Ptr(baselineUnsafeText),
+ State: gogithub.Ptr("open"),
+ HTMLURL: gogithub.Ptr("https://github.com/owner/repo/pull/42"),
+ User: &gogithub.User{Login: gogithub.Ptr("author")},
+ Labels: []*gogithub.Label{{
+ Name: gogithub.Ptr(baselineUnsafeText),
+ }},
+ Milestone: &gogithub.Milestone{
+ Title: gogithub.Ptr(baselineUnsafeText),
+ },
+ Head: &gogithub.PullRequestBranch{
+ Ref: gogithub.Ptr("feature"),
+ SHA: gogithub.Ptr("abc123"),
+ Repo: &gogithub.Repository{
+ FullName: gogithub.Ptr("owner/repo"),
+ Description: gogithub.Ptr(baselineUnsafeText),
+ },
+ },
+ }
+ client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
+ GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPR),
+ }))
+ deps := BaseDeps{
+ Client: client,
+ GQLClient: githubv4.NewClient(githubv4mock.NewMockedHTTPClient()),
+ RepoAccessCache: stubRepoAccessCache(nil, 5*time.Minute),
+ Flags: stubFeatureFlags(map[string]bool{"lockdown-mode": false}),
+ }
+ serverTool := PullRequestRead(translations.NullTranslationHelper)
+ handler := serverTool.Handler(deps)
+
+ result, err := handler(ContextWithDeps(context.Background(), deps), &mcp.CallToolRequest{
+ Params: createMCPRequest(map[string]any{
+ "method": "get",
+ "owner": "owner",
+ "repo": "repo",
+ "pullNumber": float64(42),
+ }).Params,
+ })
+
+ require.NoError(t, err)
+ require.False(t, result.IsError)
+ var pr MinimalPullRequest
+ require.NoError(t, json.Unmarshal([]byte(getTextResult(t, result).Text), &pr))
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), pr.Title)
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), pr.Body)
+ assert.Equal(t, []string{sanitize.Sanitize(baselineUnsafeText)}, pr.Labels)
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), pr.Milestone)
+ require.NotNil(t, pr.Head)
+ require.NotNil(t, pr.Head.Repo)
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), pr.Head.Repo.Description)
+ assert.Equal(t, baselineUnsafeText, mockPR.GetTitle())
+ assert.Equal(t, baselineUnsafeText, mockPR.GetBody())
+ assert.Equal(t, baselineUnsafeText, mockPR.Labels[0].GetName())
+ assert.Equal(t, baselineUnsafeText, mockPR.Milestone.GetTitle())
+ assert.Equal(t, baselineUnsafeText, mockPR.Head.Repo.GetDescription())
+ })
+
+ t.Run("rest list pull requests sanitizes title and body without mutating source objects", func(t *testing.T) {
+ mockPRs := []*gogithub.PullRequest{
+ {
+ Number: gogithub.Ptr(42),
+ Title: gogithub.Ptr(baselineUnsafeText),
+ Body: gogithub.Ptr(baselineUnsafeText),
+ State: gogithub.Ptr("open"),
+ HTMLURL: gogithub.Ptr("https://github.com/owner/repo/pull/42"),
+ },
+ {
+ Number: gogithub.Ptr(43),
+ Title: gogithub.Ptr("safe title"),
+ Body: gogithub.Ptr("safe body"),
+ State: gogithub.Ptr("closed"),
+ HTMLURL: gogithub.Ptr("https://github.com/owner/repo/pull/43"),
+ },
+ }
+ client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
+ GetReposPullsByOwnerByRepo: expectQueryParams(t, map[string]string{
+ "state": "all",
+ "sort": "created",
+ "direction": "desc",
+ "per_page": "30",
+ "page": "1",
+ }).andThen(mockResponse(t, http.StatusOK, mockPRs)),
+ }))
+ deps := BaseDeps{Client: client}
+ serverTool := ListPullRequests(translations.NullTranslationHelper)
+ handler := serverTool.Handler(deps)
+
+ result, err := handler(ContextWithDeps(context.Background(), deps), &mcp.CallToolRequest{
+ Params: createMCPRequest(map[string]any{
+ "owner": "owner",
+ "repo": "repo",
+ "state": "all",
+ "sort": "created",
+ "direction": "desc",
+ "perPage": float64(30),
+ "page": float64(1),
+ }).Params,
+ })
+
+ require.NoError(t, err)
+ require.False(t, result.IsError)
+ var prs []MinimalPullRequest
+ require.NoError(t, json.Unmarshal([]byte(getTextResult(t, result).Text), &prs))
+ require.Len(t, prs, 2)
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), prs[0].Title)
+ assert.Equal(t, sanitize.Sanitize(baselineUnsafeText), prs[0].Body)
+ assert.Equal(t, "safe title", prs[1].Title)
+ assert.Equal(t, "safe body", prs[1].Body)
+ assert.Equal(t, baselineUnsafeText, mockPRs[0].GetTitle())
+ assert.Equal(t, baselineUnsafeText, mockPRs[0].GetBody())
+ })
+}
+
+func TestSanitizationCollaborationTextPaths(t *testing.T) {
+ sanitizedUnsafeText := sanitize.Sanitize(baselineUnsafeText)
+
+ comment := convertToMinimalIssueComment(&gogithub.IssueComment{
+ ID: gogithub.Ptr(int64(1)),
+ Body: gogithub.Ptr(baselineUnsafeText),
+ HTMLURL: gogithub.Ptr("https://github.com/owner/repo/issues/1#issuecomment-1"),
+ })
+ assert.Equal(t, sanitizedUnsafeText, comment.Body)
+
+ review := convertToMinimalPullRequestReview(&gogithub.PullRequestReview{
+ ID: gogithub.Ptr(int64(2)),
+ State: gogithub.Ptr("COMMENTED"),
+ Body: gogithub.Ptr(baselineUnsafeText),
+ HTMLURL: gogithub.Ptr("https://github.com/owner/repo/pull/1#pullrequestreview-2"),
+ })
+ assert.Equal(t, sanitizedUnsafeText, review.Body)
+
+ reviewURL, err := url.Parse("https://github.com/owner/repo/pull/1#discussion_r1")
+ require.NoError(t, err)
+ reviewComment := convertToMinimalReviewComment(reviewCommentNode{
+ Body: baselineUnsafeText,
+ Path: "README.md",
+ URL: githubv4.URI{URL: reviewURL},
+ })
+ assert.Equal(t, sanitizedUnsafeText, reviewComment.Body)
+ assert.Equal(t, "README.md", reviewComment.Path)
+
+ discussion := fragmentToDiscussion(NodeFragment{
+ Number: githubv4.Int(1),
+ Title: githubv4.String(baselineUnsafeText),
+ URL: githubv4.String("https://github.com/owner/repo/discussions/1"),
+ CreatedAt: githubv4.DateTime{Time: time.Date(2026, 7, 3, 9, 0, 0, 0, time.UTC)},
+ UpdatedAt: githubv4.DateTime{Time: time.Date(2026, 7, 3, 9, 0, 0, 0, time.UTC)},
+ })
+ assert.Equal(t, sanitizedUnsafeText, discussion.GetTitle())
+
+ detail := discussionDetailFragment{
+ Number: githubv4.Int(1),
+ Title: githubv4.String(baselineUnsafeText),
+ Body: githubv4.String(baselineUnsafeText),
+ URL: githubv4.String("https://github.com/owner/repo/discussions/1"),
+ CreatedAt: githubv4.DateTime{Time: time.Date(2026, 7, 3, 9, 0, 0, 0, time.UTC)},
+ }
+ detail.Category.Name = githubv4.String(baselineUnsafeText)
+ detailResponse := discussionDetailResponse(detail)
+ assert.Equal(t, sanitizedUnsafeText, detailResponse["title"])
+ assert.Equal(t, sanitizedUnsafeText, detailResponse["body"])
+ assert.Equal(t, sanitizedUnsafeText, detailResponse["category"].(map[string]any)["name"])
+
+ discussionComment := convertToMinimalDiscussionComment(githubv4.ID("DC_1"), githubv4.String(baselineUnsafeText), githubv4.Boolean(true))
+ assert.Equal(t, sanitizedUnsafeText, discussionComment.Body)
+}
+
+func TestSanitizationProjectDisplayTextPaths(t *testing.T) {
+ sanitizedUnsafeText := sanitize.Sanitize(baselineUnsafeText)
+
+ project := convertToMinimalProject(&gogithub.ProjectV2{
+ Title: gogithub.Ptr(baselineUnsafeText),
+ Description: gogithub.Ptr(baselineUnsafeText),
+ ShortDescription: gogithub.Ptr(baselineUnsafeText),
+ })
+ require.NotNil(t, project)
+ require.NotNil(t, project.Title)
+ require.NotNil(t, project.Description)
+ require.NotNil(t, project.ShortDescription)
+ assert.Equal(t, sanitizedUnsafeText, *project.Title)
+ assert.Equal(t, sanitizedUnsafeText, *project.Description)
+ assert.Equal(t, sanitizedUnsafeText, *project.ShortDescription)
+
+ content := convertIssueToMinimalProjectItemContent(&gogithub.Issue{
+ Number: gogithub.Ptr(42),
+ Title: gogithub.Ptr(baselineUnsafeText),
+ State: gogithub.Ptr("open"),
+ Labels: []*gogithub.Label{{Name: gogithub.Ptr(baselineUnsafeText)}},
+ })
+ require.NotNil(t, content)
+ assert.Equal(t, sanitizedUnsafeText, content.Title)
+ assert.Equal(t, []string{sanitizedUnsafeText}, content.Labels)
+
+ prContent := convertPullRequestToMinimalProjectItemContent(&gogithub.PullRequest{
+ Number: gogithub.Ptr(43),
+ Title: gogithub.Ptr(baselineUnsafeText),
+ State: gogithub.Ptr("open"),
+ Labels: []*gogithub.Label{{Name: gogithub.Ptr(baselineUnsafeText)}},
+ })
+ require.NotNil(t, prContent)
+ assert.Equal(t, sanitizedUnsafeText, prContent.Title)
+ assert.Equal(t, []string{sanitizedUnsafeText}, prContent.Labels)
+
+ draftIssueContent := convertDraftIssueToMinimalProjectItemContent(&gogithub.ProjectV2DraftIssue{
+ Title: gogithub.Ptr(baselineUnsafeText),
+ })
+ require.NotNil(t, draftIssueContent)
+ assert.Equal(t, sanitizedUnsafeText, draftIssueContent.Title)
+
+ fields := convertToMinimalProjectItemFields([]*gogithub.ProjectV2ItemFieldValue{
+ {
+ ID: gogithub.Ptr(int64(1)),
+ Name: gogithub.Ptr(baselineUnsafeText),
+ DataType: gogithub.Ptr("text"),
+ Value: baselineUnsafeText,
+ },
+ })
+ require.Len(t, fields, 1)
+ assert.Equal(t, sanitizedUnsafeText, fields[0].Name)
+ assert.Equal(t, sanitizedUnsafeText, fields[0].Value)
+
+ option := minimalProjectFieldValue(&gogithub.ProjectV2FieldOption{
+ ID: gogithub.Ptr("option-id"),
+ Name: &gogithub.ProjectV2TextContent{Raw: gogithub.Ptr(baselineUnsafeText)},
+ })
+ assert.Equal(t, minimalProjectOptionValue{ID: "option-id", Name: sanitizedUnsafeText}, option)
+
+ body := githubv4.String(baselineUnsafeText)
+ status := githubv4.String("ON_TRACK")
+ statusUpdate := convertToMinimalStatusUpdate(statusUpdateNode{
+ ID: githubv4.ID("SU_1"),
+ Body: &body,
+ Status: &status,
+ CreatedAt: githubv4.DateTime{Time: time.Date(2026, 7, 3, 9, 0, 0, 0, time.UTC)},
+ })
+ assert.Equal(t, sanitizedUnsafeText, statusUpdate.Body)
+ assert.Equal(t, "ON_TRACK", statusUpdate.Status)
+}
+
+func TestSanitizeCommitMessage(t *testing.T) {
+ t.Run("preserves valid trailer addresses while sanitizing the message", func(t *testing.T) {
+ message := "Subject\n\n" +
+ "Co-authored-by: Copilot \n" +
+ "Signed-off-by: \"Copilot App\" <223556219+Copilot@users.noreply.github.com>"
+
+ assert.Equal(t, "Subject\n\n"+
+ "Co-authored-by: Copilot \n"+
+ "Signed-off-by: "Copilot App" <223556219+Copilot@users.noreply.github.com>",
+ sanitizeCommitMessage(message))
+ })
+
+ t.Run("does not restore address-like text outside a trailer", func(t *testing.T) {
+ message := "Contact Copilot "
+ assert.Equal(t, sanitizeOutputText(message), sanitizeCommitMessage(message))
+ })
+
+ t.Run("does not restore a trailer address with trailing HTML", func(t *testing.T) {
+ message := "Co-authored-by: Copilot "
+ assert.Equal(t, sanitizeOutputText(message), sanitizeCommitMessage(message))
+ })
+
+ t.Run("does not restore HTML smuggled through address syntax", func(t *testing.T) {
+ displayNameHTML := `Co-authored-by: ">
" `
+ sanitized := sanitizeCommitMessage(displayNameHTML)
+ assert.NotContains(t, sanitized, "onerror")
+ assert.NotContains(t, sanitized, "alert(1)")
+ assert.Contains(t, sanitized, "")
+
+ addressHTML := `Co-authored-by: Copilot <""@example.com>`
+ assert.Equal(t, sanitizeOutputText(addressHTML), sanitizeCommitMessage(addressHTML))
+ })
+
+ t.Run("avoids collisions with placeholder-like message text", func(t *testing.T) {
+ message := "GITHUBMCPCOMMITTRAILEREMAIL0PLACEHOLDER\n\nCo-authored-by: Copilot "
+ assert.Equal(t, message, sanitizeCommitMessage(message))
+ })
+}
+
+func TestSanitizationOutputBypassesAndOutliers(t *testing.T) {
+ sanitizedUnsafeText := sanitize.Sanitize(baselineUnsafeText)
+
+ repo := &gogithub.Repository{
+ ID: gogithub.Ptr(int64(1)),
+ Name: gogithub.Ptr("repo"),
+ FullName: gogithub.Ptr("owner/repo"),
+ Description: gogithub.Ptr(baselineUnsafeText),
+ HTMLURL: gogithub.Ptr("https://github.com/owner/repo"),
+ }
+ minimalRepo := convertToMinimalRepository(repo)
+ assert.Equal(t, sanitizedUnsafeText, minimalRepo.Description)
+ assert.Equal(t, baselineUnsafeText, repo.GetDescription())
+
+ searchResult := sanitizedRepositoriesSearchResultCopy(&gogithub.RepositoriesSearchResult{
+ Repositories: []*gogithub.Repository{repo},
+ })
+ require.Len(t, searchResult.Repositories, 1)
+ assert.Equal(t, sanitizedUnsafeText, searchResult.Repositories[0].GetDescription())
+ assert.Equal(t, baselineUnsafeText, repo.GetDescription())
+
+ release := &gogithub.RepositoryRelease{
+ Name: gogithub.Ptr(baselineUnsafeText),
+ Body: gogithub.Ptr(baselineUnsafeText),
+ }
+ minimalRelease := convertToMinimalRelease(release)
+ assert.Equal(t, sanitizedUnsafeText, minimalRelease.Name)
+ assert.Equal(t, sanitizedUnsafeText, minimalRelease.Body)
+ sanitizedRelease := sanitizedReleaseCopy(release)
+ assert.Equal(t, sanitizedUnsafeText, sanitizedRelease.GetName())
+ assert.Equal(t, sanitizedUnsafeText, sanitizedRelease.GetBody())
+ assert.Equal(t, baselineUnsafeText, release.GetName())
+ assert.Equal(t, baselineUnsafeText, release.GetBody())
+
+ securityAdvisory := &gogithub.SecurityAdvisory{
+ Summary: gogithub.Ptr(baselineUnsafeText),
+ Description: gogithub.Ptr(baselineUnsafeText),
+ CollaboratingTeams: []*gogithub.Team{{
+ Name: gogithub.Ptr(baselineUnsafeText),
+ Description: gogithub.Ptr(baselineUnsafeText),
+ Slug: gogithub.Ptr("team\u200B