Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions cmd/github-mcp-server/generate_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
Expand Down Expand Up @@ -73,8 +74,8 @@ func generateReadmeDocs(readmePath string) error {
toolsDoc := generateToolsDoc(tsg)

// Read the current README.md
// #nosec G304 - readmePath is controlled by command line flag, not user input
content, err := os.ReadFile(readmePath)
cleanReadmePath := filepath.Clean(readmePath)
content, err := os.ReadFile(cleanReadmePath) //#nosec G304 -- path is controlled by CLI flag
if err != nil {
return fmt.Errorf("failed to read README.md: %w", err)
}
Expand All @@ -86,7 +87,7 @@ func generateReadmeDocs(readmePath string) error {
updatedContent = replaceSection(updatedContent, "START AUTOMATED TOOLS", "END AUTOMATED TOOLS", toolsDoc)

// Write back to file
err = os.WriteFile(readmePath, []byte(updatedContent), 0600)
err = os.WriteFile(cleanReadmePath, []byte(updatedContent), 0600) //nolint:gosec // G703: path is sanitized and controlled by CLI flag
if err != nil {
return fmt.Errorf("failed to write README.md: %w", err)
}
Expand All @@ -96,7 +97,8 @@ func generateReadmeDocs(readmePath string) error {
}

func generateRemoteServerDocs(docsPath string) error {
content, err := os.ReadFile(docsPath) //#nosec G304
cleanDocsPath := filepath.Clean(docsPath)
content, err := os.ReadFile(cleanDocsPath) //#nosec G304 -- path is controlled by CLI flag
if err != nil {
return fmt.Errorf("failed to read docs file: %w", err)
}
Expand All @@ -117,7 +119,7 @@ func generateRemoteServerDocs(docsPath string) error {

newContent := contentStr[:startIndex] + startMarker + "\n" + toolsetsDoc + "\n" + endMarker + contentStr[endIndex+len(endMarker):]

return os.WriteFile(docsPath, []byte(newContent), 0600) //#nosec G306
return os.WriteFile(cleanDocsPath, []byte(newContent), 0600) //nolint:gosec // G306,G703: path is sanitized and controlled by CLI flag
}

func generateToolsetsDoc(tsg *toolsets.ToolsetGroup) string {
Expand Down Expand Up @@ -340,14 +342,14 @@ func generateRemoteToolsetsDoc() string {
installLink := fmt.Sprintf("[Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-%s&config=%s)", name, installConfig)
readonlyInstallLink := fmt.Sprintf("[Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-%s&config=%s)", name, readonlyConfig)

buf.WriteString(fmt.Sprintf("| %-14s | %-48s | %-53s | %-218s | %-110s | %-288s |\n",
fmt.Fprintf(&buf, "| %-14s | %-48s | %-53s | %-218s | %-110s | %-288s |\n",
formattedName,
description,
apiURL,
installLink,
fmt.Sprintf("[read-only](%s)", readonlyURL),
readonlyInstallLink,
))
)
}

return buf.String()
Expand Down
10 changes: 7 additions & 3 deletions pkg/github/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin

if returnContent {
// Download and return the actual log content
content, originalLength, httpResp, err := downloadLogContent(url.String(), tailLines) //nolint:bodyclose // Response body is closed in downloadLogContent, but we need to return httpResp
content, originalLength, httpResp, err := downloadLogContent(ctx, url.String(), tailLines) //nolint:bodyclose // Response body is closed in downloadLogContent, but we need to return httpResp
if err != nil {
// To keep the return value consistent wrap the response as a GitHub Response
ghRes := &github.Response{
Expand All @@ -770,8 +770,12 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin
}

// downloadLogContent downloads the actual log content from a GitHub logs URL
func downloadLogContent(logURL string, tailLines int) (string, int, *http.Response, error) {
httpResp, err := http.Get(logURL) //nolint:gosec // URLs are provided by GitHub API and are safe
func downloadLogContent(ctx context.Context, logURL string, tailLines int) (string, int, *http.Response, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, logURL, nil)
if err != nil {
return "", 0, nil, fmt.Errorf("failed to create log download request: %w", err)
}
httpResp, err := http.DefaultClient.Do(req)
if err != nil {
return "", 0, httpResp, fmt.Errorf("failed to download logs: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/github/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -686,15 +686,15 @@
sb.WriteString("\n\n")
sb.WriteString("This tool can help with the following outcomes:\n")
for _, outcome := range d.outcomes {
sb.WriteString(fmt.Sprintf("- %s\n", outcome))
fmt.Fprintf(&sb, "- %s\n", outcome)
}
}

if len(d.referenceLinks) > 0 {
sb.WriteString("\n\n")
sb.WriteString("More information can be found at:\n")
for _, link := range d.referenceLinks {
sb.WriteString(fmt.Sprintf("- %s\n", link))
fmt.Fprintf(&sb, "- %s\n", link)
}
}

Expand Down Expand Up @@ -758,11 +758,11 @@

type suggestedActorsQuery struct {
Repository struct {
SuggestedActors struct {

Check warning on line 761 in pkg/github/issues.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested anonymous struct into a named type for better readability and reusability.

See more on https://sonarcloud.io/project/issues?id=COG-GTM_github-mcp-server&issues=AZ2qegxdwKlAIwY9nRp8&open=AZ2qegxdwKlAIwY9nRp8&pullRequest=139
Nodes []struct {
Bot botAssignee `graphql:"... on Bot"`
}
PageInfo struct {

Check warning on line 765 in pkg/github/issues.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested anonymous struct into a named type for better readability and reusability.

See more on https://sonarcloud.io/project/issues?id=COG-GTM_github-mcp-server&issues=AZ2qegxdwKlAIwY9nRp9&open=AZ2qegxdwKlAIwY9nRp9&pullRequest=139
HasNextPage bool
EndCursor string
}
Expand Down Expand Up @@ -809,9 +809,9 @@
// assign copilot is to use replaceActorsForAssignable which requires the full list.
var getIssueQuery struct {
Repository struct {
Issue struct {

Check warning on line 812 in pkg/github/issues.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested anonymous struct into a named type for better readability and reusability.

See more on https://sonarcloud.io/project/issues?id=COG-GTM_github-mcp-server&issues=AZ2qegxdwKlAIwY9nRp-&open=AZ2qegxdwKlAIwY9nRp-&pullRequest=139
ID githubv4.ID
Assignees struct {

Check warning on line 814 in pkg/github/issues.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested anonymous struct into a named type for better readability and reusability.

See more on https://sonarcloud.io/project/issues?id=COG-GTM_github-mcp-server&issues=AZ2qegxdwKlAIwY9nRp_&open=AZ2qegxdwKlAIwY9nRp_&pullRequest=139
Nodes []struct {
ID githubv4.ID
}
Expand Down
10 changes: 8 additions & 2 deletions pkg/github/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,10 @@ func ManageNotificationSubscription(getClient GetClientFn, t translations.Transl
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
return mcp.NewToolResultError(fmt.Sprintf("failed to %s notification subscription: %s", action, string(body))), nil
}

Expand Down Expand Up @@ -507,7 +510,10 @@ func ManageRepositoryNotificationSubscription(getClient GetClientFn, t translati

// Handle non-2xx status codes
if resp != nil && (resp.StatusCode < 200 || resp.StatusCode >= 300) {
body, _ := io.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
return mcp.NewToolResultError(fmt.Sprintf("failed to %s repository subscription: %s", action, string(body))), nil
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/github/secret_scanning.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func GetSecretScanningAlert(getClient GetClientFn, t translations.TranslationHel
return mcp.NewToolResultError(fmt.Sprintf("failed to get alert: %s", string(body))), nil
}

r, err := json.Marshal(alert)
r, err := json.Marshal(alert) //nolint:gosec // G117: Secret field is intentionally included as this tool exposes GitHub's secret scanning API data
if err != nil {
return nil, fmt.Errorf("failed to marshal alert: %w", err)
}
Expand Down Expand Up @@ -153,7 +153,7 @@ func ListSecretScanningAlerts(getClient GetClientFn, t translations.TranslationH
return mcp.NewToolResultError(fmt.Sprintf("failed to list alerts: %s", string(body))), nil
}

r, err := json.Marshal(alerts)
r, err := json.Marshal(alerts) //nolint:gosec // G117: Secret field is intentionally included as this tool exposes GitHub's secret scanning API data
if err != nil {
return nil, fmt.Errorf("failed to marshal alerts: %w", err)
}
Expand Down