Skip to content

Commit 67d6012

Browse files
Implement content filtering for issues, PRs, and comments
Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
1 parent 09c5a0a commit 67d6012

File tree

7 files changed

+677
-10
lines changed

7 files changed

+677
-10
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,41 @@ docker run -i --rm \
219219
ghcr.io/github/github-mcp-server
220220
```
221221

222+
## Content Filtering
223+
224+
The GitHub MCP Server includes a content filtering feature that removes invisible characters and hidden content from GitHub issues, PRs, and comments. This helps prevent potential security risks and ensures better readability of content.
225+
226+
### What Gets Filtered
227+
228+
- **Invisible Unicode Characters**: Zero-width spaces, zero-width joiners, zero-width non-joiners, bidirectional marks, and other invisible Unicode characters
229+
- **HTML Comments**: Comments that might contain hidden information
230+
- **Hidden HTML Elements**: Script, style, iframe, and other potentially dangerous HTML elements
231+
- **Collapsed Sections**: Details/summary elements that might hide content
232+
- **Very Small Text**: Content with extremely small font size
233+
234+
### Controlling Content Filtering
235+
236+
Content filtering is enabled by default. You can disable it using the `--disable-content-filtering` flag:
237+
238+
```bash
239+
github-mcp-server --disable-content-filtering
240+
```
241+
242+
Or using the environment variable:
243+
244+
```bash
245+
GITHUB_DISABLE_CONTENT_FILTERING=1 github-mcp-server
246+
```
247+
248+
When using Docker, you can set the environment variable:
249+
250+
```bash
251+
docker run -i --rm \
252+
-e GITHUB_PERSONAL_ACCESS_TOKEN=<your-token> \
253+
-e GITHUB_DISABLE_CONTENT_FILTERING=1 \
254+
ghcr.io/github/github-mcp-server
255+
```
256+
222257
## GitHub Enterprise Server
223258

224259
The flag `--gh-host` and the environment variable `GITHUB_HOST` can be used to set

internal/ghmcp/server.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
9494
OnBeforeInitialize: []server.OnBeforeInitializeFunc{beforeInit},
9595
}
9696

97-
ghServer := github.NewServer(cfg.Version, server.WithHooks(hooks))
97+
ghServer := github.NewServerWithConfig(github.ServerConfig{
98+
Version: cfg.Version,
99+
DisableContentFiltering: cfg.DisableContentFiltering,
100+
}, server.WithHooks(hooks))
98101

99102
enabledToolsets := cfg.EnabledToolsets
100103
if cfg.DynamicToolsets {

pkg/github/filtering.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package github
2+
3+
import (
4+
"github.com/github/github-mcp-server/pkg/filtering"
5+
"github.com/google/go-github/v69/github"
6+
)
7+
8+
// ContentFilteringConfig holds configuration for content filtering
9+
type ContentFilteringConfig struct {
10+
// DisableContentFiltering disables all content filtering when true
11+
DisableContentFiltering bool
12+
}
13+
14+
// DefaultContentFilteringConfig returns the default content filtering configuration
15+
func DefaultContentFilteringConfig() *ContentFilteringConfig {
16+
return &ContentFilteringConfig{
17+
DisableContentFiltering: false,
18+
}
19+
}
20+
21+
// FilterIssue applies content filtering to issue bodies and titles
22+
func FilterIssue(issue *github.Issue, cfg *ContentFilteringConfig) *github.Issue {
23+
if issue == nil {
24+
return nil
25+
}
26+
27+
// Don't modify the original issue, create a copy
28+
filteredIssue := *issue
29+
30+
// Filter the body if present
31+
if issue.Body != nil {
32+
filteredBody := filtering.FilterContent(*issue.Body, &filtering.Config{
33+
DisableContentFiltering: cfg.DisableContentFiltering,
34+
})
35+
filteredIssue.Body = github.Ptr(filteredBody)
36+
}
37+
38+
// Filter the title if present
39+
if issue.Title != nil {
40+
filteredTitle := filtering.FilterContent(*issue.Title, &filtering.Config{
41+
DisableContentFiltering: cfg.DisableContentFiltering,
42+
})
43+
filteredIssue.Title = github.Ptr(filteredTitle)
44+
}
45+
46+
return &filteredIssue
47+
}
48+
49+
// FilterIssues applies content filtering to a list of issues
50+
func FilterIssues(issues []*github.Issue, cfg *ContentFilteringConfig) []*github.Issue {
51+
if issues == nil {
52+
return nil
53+
}
54+
55+
filteredIssues := make([]*github.Issue, len(issues))
56+
for i, issue := range issues {
57+
filteredIssues[i] = FilterIssue(issue, cfg)
58+
}
59+
60+
return filteredIssues
61+
}
62+
63+
// FilterPullRequest applies content filtering to pull request bodies and titles
64+
func FilterPullRequest(pr *github.PullRequest, cfg *ContentFilteringConfig) *github.PullRequest {
65+
if pr == nil {
66+
return nil
67+
}
68+
69+
// Don't modify the original PR, create a copy
70+
filteredPR := *pr
71+
72+
// Filter the body if present
73+
if pr.Body != nil {
74+
filteredBody := filtering.FilterContent(*pr.Body, &filtering.Config{
75+
DisableContentFiltering: cfg.DisableContentFiltering,
76+
})
77+
filteredPR.Body = github.Ptr(filteredBody)
78+
}
79+
80+
// Filter the title if present
81+
if pr.Title != nil {
82+
filteredTitle := filtering.FilterContent(*pr.Title, &filtering.Config{
83+
DisableContentFiltering: cfg.DisableContentFiltering,
84+
})
85+
filteredPR.Title = github.Ptr(filteredTitle)
86+
}
87+
88+
return &filteredPR
89+
}
90+
91+
// FilterPullRequests applies content filtering to a list of pull requests
92+
func FilterPullRequests(prs []*github.PullRequest, cfg *ContentFilteringConfig) []*github.PullRequest {
93+
if prs == nil {
94+
return nil
95+
}
96+
97+
filteredPRs := make([]*github.PullRequest, len(prs))
98+
for i, pr := range prs {
99+
filteredPRs[i] = FilterPullRequest(pr, cfg)
100+
}
101+
102+
return filteredPRs
103+
}
104+
105+
// FilterIssueComment applies content filtering to issue comment bodies
106+
func FilterIssueComment(comment *github.IssueComment, cfg *ContentFilteringConfig) *github.IssueComment {
107+
if comment == nil {
108+
return nil
109+
}
110+
111+
// Don't modify the original comment, create a copy
112+
filteredComment := *comment
113+
114+
// Filter the body if present
115+
if comment.Body != nil {
116+
filteredBody := filtering.FilterContent(*comment.Body, &filtering.Config{
117+
DisableContentFiltering: cfg.DisableContentFiltering,
118+
})
119+
filteredComment.Body = github.Ptr(filteredBody)
120+
}
121+
122+
return &filteredComment
123+
}
124+
125+
// FilterIssueComments applies content filtering to a list of issue comments
126+
func FilterIssueComments(comments []*github.IssueComment, cfg *ContentFilteringConfig) []*github.IssueComment {
127+
if comments == nil {
128+
return nil
129+
}
130+
131+
filteredComments := make([]*github.IssueComment, len(comments))
132+
for i, comment := range comments {
133+
filteredComments[i] = FilterIssueComment(comment, cfg)
134+
}
135+
136+
return filteredComments
137+
}
138+
139+
// FilterPullRequestComment applies content filtering to pull request comment bodies
140+
func FilterPullRequestComment(comment *github.PullRequestComment, cfg *ContentFilteringConfig) *github.PullRequestComment {
141+
if comment == nil {
142+
return nil
143+
}
144+
145+
// Don't modify the original comment, create a copy
146+
filteredComment := *comment
147+
148+
// Filter the body if present
149+
if comment.Body != nil {
150+
filteredBody := filtering.FilterContent(*comment.Body, &filtering.Config{
151+
DisableContentFiltering: cfg.DisableContentFiltering,
152+
})
153+
filteredComment.Body = github.Ptr(filteredBody)
154+
}
155+
156+
return &filteredComment
157+
}
158+
159+
// FilterPullRequestComments applies content filtering to a list of pull request comments
160+
func FilterPullRequestComments(comments []*github.PullRequestComment, cfg *ContentFilteringConfig) []*github.PullRequestComment {
161+
if comments == nil {
162+
return nil
163+
}
164+
165+
filteredComments := make([]*github.PullRequestComment, len(comments))
166+
for i, comment := range comments {
167+
filteredComments[i] = FilterPullRequestComment(comment, cfg)
168+
}
169+
170+
return filteredComments
171+
}
172+
173+
// FilterPullRequestReview applies content filtering to pull request review bodies
174+
func FilterPullRequestReview(review *github.PullRequestReview, cfg *ContentFilteringConfig) *github.PullRequestReview {
175+
if review == nil {
176+
return nil
177+
}
178+
179+
// Don't modify the original review, create a copy
180+
filteredReview := *review
181+
182+
// Filter the body if present
183+
if review.Body != nil {
184+
filteredBody := filtering.FilterContent(*review.Body, &filtering.Config{
185+
DisableContentFiltering: cfg.DisableContentFiltering,
186+
})
187+
filteredReview.Body = github.Ptr(filteredBody)
188+
}
189+
190+
return &filteredReview
191+
}
192+
193+
// FilterPullRequestReviews applies content filtering to a list of pull request reviews
194+
func FilterPullRequestReviews(reviews []*github.PullRequestReview, cfg *ContentFilteringConfig) []*github.PullRequestReview {
195+
if reviews == nil {
196+
return nil
197+
}
198+
199+
filteredReviews := make([]*github.PullRequestReview, len(reviews))
200+
for i, review := range reviews {
201+
filteredReviews[i] = FilterPullRequestReview(review, cfg)
202+
}
203+
204+
return filteredReviews
205+
}

0 commit comments

Comments
 (0)