Skip to content

Commit 04359ff

Browse files
timrogersCopilot
andcommitted
Validate classifier and IDs before calling the GitHub API
JSON schema constraints (enum, minimum) aren't enforced when tool arguments are unmarshalled, so an invalid classifier or a negative ID previously reached GitHub and surfaced as a confusing 404 or GraphQL error. Reject them up front with a clear argument error instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 07bf50e commit 04359ff

2 files changed

Lines changed: 56 additions & 4 deletions

File tree

‎pkg/github/comment_minimize.go‎

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package github
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"maps"
8+
"slices"
79
"strings"
810

911
ghErrors "github.com/github/github-mcp-server/pkg/errors"
@@ -61,7 +63,7 @@ var issueCommentVisibilityTarget = commentVisibilityTarget{
6163
},
6264
required: []string{"comment_id"},
6365
resolveNodeID: func(ctx context.Context, client *github.Client, owner, repo string, args map[string]any) (string, *mcp.CallToolResult) {
64-
commentID, err := RequiredBigInt(args, "comment_id")
66+
commentID, err := requiredPositiveBigInt(args, "comment_id")
6567
if err != nil {
6668
return "", utils.NewToolResultError(err.Error())
6769
}
@@ -88,7 +90,7 @@ var pullRequestReviewCommentVisibilityTarget = commentVisibilityTarget{
8890
},
8991
required: []string{"comment_id"},
9092
resolveNodeID: func(ctx context.Context, client *github.Client, owner, repo string, args map[string]any) (string, *mcp.CallToolResult) {
91-
commentID, err := RequiredBigInt(args, "comment_id")
93+
commentID, err := requiredPositiveBigInt(args, "comment_id")
9294
if err != nil {
9395
return "", utils.NewToolResultError(err.Error())
9496
}
@@ -124,7 +126,10 @@ var pullRequestReviewVisibilityTarget = commentVisibilityTarget{
124126
if err != nil {
125127
return "", utils.NewToolResultError(err.Error())
126128
}
127-
reviewID, err := RequiredBigInt(args, "review_id")
129+
if pullNumber < 1 {
130+
return "", utils.NewToolResultError("pullNumber must be greater than 0")
131+
}
132+
reviewID, err := requiredPositiveBigInt(args, "review_id")
128133
if err != nil {
129134
return "", utils.NewToolResultError(err.Error())
130135
}
@@ -204,6 +209,10 @@ func setCommentVisibility(ctx context.Context, deps ToolDependencies, target com
204209
if err != nil {
205210
return utils.NewToolResultError(err.Error())
206211
}
212+
classifier = strings.ToUpper(classifier)
213+
if !slices.Contains(commentClassifiers, any(classifier)) {
214+
return utils.NewToolResultError(fmt.Sprintf("invalid classifier %q: must be one of %v", classifier, commentClassifiers))
215+
}
207216
}
208217

209218
client, err := deps.GetClient(ctx)
@@ -223,7 +232,7 @@ func setCommentVisibility(ctx context.Context, deps ToolDependencies, target com
223232

224233
var result MinimizeCommentResult
225234
if hide {
226-
result, errResult = minimizeComment(ctx, gqlClient, nodeID, strings.ToUpper(classifier))
235+
result, errResult = minimizeComment(ctx, gqlClient, nodeID, classifier)
227236
} else {
228237
result, errResult = unminimizeComment(ctx, gqlClient, nodeID)
229238
}
@@ -238,6 +247,19 @@ func setCommentVisibility(ctx context.Context, deps ToolDependencies, target com
238247
return utils.NewToolResultText(string(r))
239248
}
240249

250+
// requiredPositiveBigInt reads a required ID argument. The schema's minimum is not enforced
251+
// when arguments are unmarshalled, so negative values are rejected here before any API call.
252+
func requiredPositiveBigInt(args map[string]any, p string) (int64, error) {
253+
v, err := RequiredBigInt(args, p)
254+
if err != nil {
255+
return 0, err
256+
}
257+
if v < 1 {
258+
return 0, fmt.Errorf("%s must be greater than 0", p)
259+
}
260+
return v, nil
261+
}
262+
241263
// nodeIDFromResponse turns the result of a REST lookup into a GraphQL node ID, since the
242264
// minimize mutations only accept node IDs.
243265
func nodeIDFromResponse(ctx context.Context, errMessage, nodeID string, resp *github.Response, err error) (string, *mcp.CallToolResult) {

‎pkg/github/comment_minimize_test.go‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,36 @@ func Test_HideAndUnhideComments(t *testing.T) {
282282
requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(1)},
283283
expectedErrMsg: "failed to unminimize comment",
284284
},
285+
{
286+
name: "invalid classifier is rejected before any API call",
287+
tool: GranularHideIssueComment(translations.NullTranslationHelper),
288+
requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(1), "classifier": "BOGUS"},
289+
expectedErrMsg: `invalid classifier "BOGUS"`,
290+
},
291+
{
292+
name: "negative issue comment_id",
293+
tool: GranularHideIssueComment(translations.NullTranslationHelper),
294+
requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(-1), "classifier": "SPAM"},
295+
expectedErrMsg: "comment_id must be greater than 0",
296+
},
297+
{
298+
name: "negative pull request review comment_id",
299+
tool: GranularUnhidePullRequestReviewComment(translations.NullTranslationHelper),
300+
requestArgs: map[string]any{"owner": "owner", "repo": "repo", "comment_id": float64(-2)},
301+
expectedErrMsg: "comment_id must be greater than 0",
302+
},
303+
{
304+
name: "negative review_id",
305+
tool: GranularUnhidePullRequestReview(translations.NullTranslationHelper),
306+
requestArgs: map[string]any{"owner": "owner", "repo": "repo", "pullNumber": float64(42), "review_id": float64(-3)},
307+
expectedErrMsg: "review_id must be greater than 0",
308+
},
309+
{
310+
name: "negative pullNumber",
311+
tool: GranularUnhidePullRequestReview(translations.NullTranslationHelper),
312+
requestArgs: map[string]any{"owner": "owner", "repo": "repo", "pullNumber": float64(-42), "review_id": float64(3)},
313+
expectedErrMsg: "pullNumber must be greater than 0",
314+
},
285315
{
286316
name: "missing owner",
287317
tool: GranularUnhideIssueComment(translations.NullTranslationHelper),

0 commit comments

Comments
 (0)