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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy