Skip to content

Commit 1c4c824

Browse files
fix: preserve Markdown content in GitHub responses
Route Markdown and code-bearing response fields through a fidelity-preserving content path while retaining metadata sanitization for titles. Add exact converter and sanitizer coverage for useful Markdown and invisible-character filtering. Refs #2202 Refs #3165 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 69c5ab30-9815-4c07-8385-11a206e68f66
1 parent 2e69419 commit 1c4c824

10 files changed

Lines changed: 109 additions & 47 deletions

pkg/github/discussions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ func GetDiscussion(t translations.TranslationHelperFunc) inventory.ServerTool {
362362
response := map[string]any{
363363
"number": int(d.Number),
364364
"title": sanitize.Sanitize(string(d.Title)),
365-
"body": sanitize.Sanitize(string(d.Body)),
365+
"body": sanitize.Content(string(d.Body)),
366366
"url": string(d.URL),
367367
"closed": bool(d.Closed),
368368
"isAnswered": bool(d.IsAnswered),

pkg/github/discussions_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ func Test_GetDiscussion(t *testing.T) {
571571
expected: map[string]any{
572572
"number": float64(1),
573573
"title": sanitizedText,
574-
"body": sanitizedText,
574+
"body": sanitizedContentText,
575575
"url": "https://github.com/owner/repo/discussions/1",
576576
"closed": false,
577577
"isAnswered": false,

pkg/github/issues.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,9 @@ func GetSubIssues(ctx context.Context, client *github.Client, deps ToolDependenc
11181118
subIssues = filteredSubIssues
11191119
}
11201120

1121+
for _, subIssue := range subIssues {
1122+
sanitizeSubIssueTitleAndBody(subIssue)
1123+
}
11211124
r, err := json.Marshal(subIssues)
11221125
if err != nil {
11231126
return nil, fmt.Errorf("failed to marshal response: %w", err)
@@ -1708,6 +1711,7 @@ func AddSubIssue(ctx context.Context, client *github.Client, owner string, repo
17081711
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to add sub-issue", resp, body), nil
17091712
}
17101713

1714+
sanitizeSubIssueTitleAndBody(subIssue)
17111715
r, err := json.Marshal(subIssue)
17121716
if err != nil {
17131717
return nil, fmt.Errorf("failed to marshal response: %w", err)
@@ -1739,6 +1743,7 @@ func RemoveSubIssue(ctx context.Context, client *github.Client, owner string, re
17391743
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to remove sub-issue", resp, body), nil
17401744
}
17411745

1746+
sanitizeSubIssueTitleAndBody(subIssue)
17421747
r, err := json.Marshal(subIssue)
17431748
if err != nil {
17441749
return nil, fmt.Errorf("failed to marshal response: %w", err)
@@ -1788,6 +1793,7 @@ func ReprioritizeSubIssue(ctx context.Context, client *github.Client, owner stri
17881793
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to reprioritize sub-issue", resp, body), nil
17891794
}
17901795

1796+
sanitizeSubIssueTitleAndBody(subIssue)
17911797
r, err := json.Marshal(subIssue)
17921798
if err != nil {
17931799
return nil, fmt.Errorf("failed to marshal response: %w", err)
@@ -1998,7 +2004,19 @@ func sanitizeIssueTitleAndBody(issue *github.Issue) {
19982004
issue.Title = github.Ptr(sanitize.Sanitize(*issue.Title))
19992005
}
20002006
if issue.Body != nil {
2001-
issue.Body = github.Ptr(sanitize.Sanitize(*issue.Body))
2007+
issue.Body = github.Ptr(sanitize.Content(*issue.Body))
2008+
}
2009+
}
2010+
2011+
func sanitizeSubIssueTitleAndBody(issue *github.SubIssue) {
2012+
if issue == nil {
2013+
return
2014+
}
2015+
if issue.Title != nil {
2016+
issue.Title = github.Ptr(sanitize.Sanitize(*issue.Title))
2017+
}
2018+
if issue.Body != nil {
2019+
issue.Body = github.Ptr(sanitize.Content(*issue.Body))
20022020
}
20032021
}
20042022

pkg/github/minimal_types.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ type MinimalDiscussionComment struct {
204204
func newMinimalDiscussionComment(id string, body string, isAnswer bool) MinimalDiscussionComment {
205205
return MinimalDiscussionComment{
206206
ID: id,
207-
Body: sanitize.Sanitize(body),
207+
Body: sanitize.Content(body),
208208
IsAnswer: isAnswer,
209209
}
210210
}
@@ -797,7 +797,7 @@ func convertToMinimalPullRequestReview(review *github.PullRequestReview) Minimal
797797
m := MinimalPullRequestReview{
798798
ID: review.GetID(),
799799
State: review.GetState(),
800-
Body: sanitize.Sanitize(review.GetBody()),
800+
Body: sanitize.Content(review.GetBody()),
801801
HTMLURL: review.GetHTMLURL(),
802802
User: convertToMinimalUser(review.GetUser()),
803803
CommitID: review.GetCommitID(),
@@ -815,7 +815,7 @@ func convertToMinimalIssue(issue *github.Issue) MinimalIssue {
815815
m := MinimalIssue{
816816
Number: issue.GetNumber(),
817817
Title: sanitize.Sanitize(issue.GetTitle()),
818-
Body: sanitize.Sanitize(issue.GetBody()),
818+
Body: sanitize.Content(issue.GetBody()),
819819
State: issue.GetState(),
820820
StateReason: issue.GetStateReason(),
821821
Draft: issue.GetDraft(),
@@ -926,7 +926,7 @@ func fragmentWithoutFieldValuesToMinimalIssue(fragment issueFragmentWithoutField
926926
m := MinimalIssue{
927927
Number: int(fragment.Number),
928928
Title: sanitize.Sanitize(string(fragment.Title)),
929-
Body: sanitize.Sanitize(string(fragment.Body)),
929+
Body: sanitize.Content(string(fragment.Body)),
930930
State: string(fragment.State),
931931
Comments: int(fragment.Comments.TotalCount),
932932
CreatedAt: fragment.CreatedAt.Format(time.RFC3339),
@@ -1015,7 +1015,7 @@ func convertToMinimalIssuesResponseWithoutFieldValues(fragment issueQueryFragmen
10151015
func convertToMinimalIssueComment(comment *github.IssueComment) MinimalIssueComment {
10161016
m := MinimalIssueComment{
10171017
ID: comment.GetID(),
1018-
Body: sanitize.Sanitize(comment.GetBody()),
1018+
Body: sanitize.Content(comment.GetBody()),
10191019
HTMLURL: comment.GetHTMLURL(),
10201020
User: convertToMinimalUser(comment.GetUser()),
10211021
AuthorAssociation: comment.GetAuthorAssociation(),
@@ -1064,7 +1064,7 @@ func convertToMinimalFileContentResponse(resp *github.RepositoryContentResponse)
10641064

10651065
m.Commit = &MinimalFileCommit{
10661066
SHA: resp.Commit.GetSHA(),
1067-
Message: sanitize.Sanitize(resp.Commit.GetMessage()),
1067+
Message: sanitize.Content(resp.Commit.GetMessage()),
10681068
HTMLURL: resp.Commit.GetHTMLURL(),
10691069
}
10701070

@@ -1085,7 +1085,7 @@ func convertToMinimalPullRequest(pr *github.PullRequest) MinimalPullRequest {
10851085
m := MinimalPullRequest{
10861086
Number: pr.GetNumber(),
10871087
Title: sanitize.Sanitize(pr.GetTitle()),
1088-
Body: sanitize.Sanitize(pr.GetBody()),
1088+
Body: sanitize.Content(pr.GetBody()),
10891089
State: pr.GetState(),
10901090
Draft: pr.GetDraft(),
10911091
Merged: pr.GetMerged(),
@@ -1794,7 +1794,7 @@ func newMinimalCommitFromCore(sha, htmlURL string, commit *github.Commit, author
17941794

17951795
if commit != nil {
17961796
minimalCommit.Commit = &MinimalCommitInfo{
1797-
Message: sanitize.Sanitize(commit.GetMessage()),
1797+
Message: sanitize.Content(commit.GetMessage()),
17981798
}
17991799

18001800
if commit.Author != nil {
@@ -2000,7 +2000,7 @@ func convertToMinimalPullRequestCommits(commits []*github.RepositoryCommit) []Mi
20002000
}
20012001

20022002
if commit.Commit != nil {
2003-
minimalCommit.Message = sanitize.Sanitize(commit.Commit.GetMessage())
2003+
minimalCommit.Message = sanitize.Content(commit.Commit.GetMessage())
20042004
minimalCommit.Author = convertToMinimalCommitAuthor(commit.Commit.Author)
20052005
}
20062006

@@ -2039,7 +2039,7 @@ func convertToMinimalRelease(release *github.RepositoryRelease) MinimalRelease {
20392039
ID: release.GetID(),
20402040
TagName: release.GetTagName(),
20412041
Name: sanitize.Sanitize(release.GetName()),
2042-
Body: sanitize.Sanitize(release.GetBody()),
2042+
Body: sanitize.Content(release.GetBody()),
20432043
HTMLURL: release.GetHTMLURL(),
20442044
Prerelease: release.GetPrerelease(),
20452045
Draft: release.GetDraft(),
@@ -2095,7 +2095,7 @@ func convertToMinimalWorkflowRun(workflowRun *github.WorkflowRun) MinimalWorkflo
20952095

20962096
if headCommit := workflowRun.GetHeadCommit(); headCommit != nil && headCommit.GetMessage() != "" {
20972097
minimalRun.HeadCommit = &MinimalWorkflowRunHeadCommit{
2098-
Message: sanitize.Sanitize(headCommit.GetMessage()),
2098+
Message: sanitize.Content(headCommit.GetMessage()),
20992099
}
21002100
}
21012101

@@ -2280,7 +2280,7 @@ func convertToMinimalReviewThread(thread reviewThreadNode) MinimalReviewThread {
22802280

22812281
func convertToMinimalReviewComment(c reviewCommentNode) MinimalReviewComment {
22822282
m := MinimalReviewComment{
2283-
Body: sanitize.Sanitize(string(c.Body)),
2283+
Body: sanitize.Content(string(c.Body)),
22842284
Path: string(c.Path),
22852285
Author: string(c.Author.Login),
22862286
HTMLURL: c.URL.String(),

pkg/github/projects.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ func convertToMinimalStatusUpdate(node statusUpdateNode) MinimalProjectStatusUpd
266266

267267
return MinimalProjectStatusUpdate{
268268
ID: fmt.Sprintf("%v", node.ID),
269-
Body: sanitize.Sanitize(derefString(node.Body)),
269+
Body: sanitize.Content(derefString(node.Body)),
270270
Status: derefString(node.Status),
271271
CreatedAt: node.CreatedAt.Time.Format(time.RFC3339),
272272
StartDate: derefString(node.StartDate),

pkg/github/repositories.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2233,6 +2233,7 @@ func GetLatestRelease(t translations.TranslationHelperFunc) inventory.ServerTool
22332233
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get latest release", resp, body), nil, nil
22342234
}
22352235

2236+
sanitizeReleaseNameAndBody(release)
22362237
r, err := json.Marshal(release)
22372238
if err != nil {
22382239
return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
@@ -2319,6 +2320,7 @@ func GetReleaseByTag(t translations.TranslationHelperFunc) inventory.ServerTool
23192320
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get release by tag", resp, body), nil, nil
23202321
}
23212322

2323+
sanitizeReleaseNameAndBody(release)
23222324
r, err := json.Marshal(release)
23232325
if err != nil {
23242326
return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
@@ -2338,6 +2340,18 @@ func GetReleaseByTag(t translations.TranslationHelperFunc) inventory.ServerTool
23382340
)
23392341
}
23402342

2343+
func sanitizeReleaseNameAndBody(release *github.RepositoryRelease) {
2344+
if release == nil {
2345+
return
2346+
}
2347+
if release.Name != nil {
2348+
release.Name = github.Ptr(sanitize.Sanitize(*release.Name))
2349+
}
2350+
if release.Body != nil {
2351+
release.Body = github.Ptr(sanitize.Content(*release.Body))
2352+
}
2353+
}
2354+
23412355
// ListStarredRepositories creates a tool to list starred repositories for the authenticated user or a specified user.
23422356
func ListStarredRepositories(t translations.TranslationHelperFunc) inventory.ServerTool {
23432357
return NewTool(
@@ -2981,7 +2995,7 @@ func GetFileBlame(t translations.TranslationHelperFunc) inventory.ServerTool {
29812995
SHA: sha,
29822996
// Sanitized after truncation so the headline is cut at the author's real
29832997
// first line break rather than one introduced by sanitization.
2984-
MessageHeadline: sanitize.Sanitize(headline),
2998+
MessageHeadline: sanitize.Content(headline),
29852999
CommittedDate: r.Commit.CommittedDate.Format("2006-01-02T15:04:05Z"),
29863000
Author: BlameAuthor{
29873001
Name: string(r.Commit.Author.Name),

pkg/github/repositories_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6200,7 +6200,7 @@ func Test_GetFileBlame(t *testing.T) {
62006200
var br BlameResult
62016201
require.NoError(t, json.Unmarshal([]byte(result), &br))
62026202
require.Contains(t, br.Commits, "badc0ffee0000")
6203-
assert.Equal(t, sanitizedText, br.Commits["badc0ffee0000"].MessageHeadline)
6203+
assert.Equal(t, sanitizedContentText, br.Commits["badc0ffee0000"].MessageHeadline)
62046204
assert.NotContains(t, result, "<script>")
62056205
assert.NotContains(t, result, "Long body that should not appear")
62066206
},

0 commit comments

Comments
 (0)