{"repo_url":"https://github.com/github/github-mcp-server","overall_status":"completed","scanned_at":"2026-10-22T05:22:59.184Z","scan_summary":{"total_components":1,"worst_case_severity":"HIGH","repo_trust_score":{"score":33,"tier":"critical","capability_band":"extreme","score_confidence":"medium","score_interval":{"low":9,"high":57},"factors":["ATR-M0004 HIGH","ATR-M0009 HIGH","ATR-C0004 MEDIUM","ATR-C0005 MEDIUM","ATR-C0006 MEDIUM","ATR-C0007 HIGH","ATR-C0012 MEDIUM","ATR-C0013 HIGH"]}},"components":[{"artifact_version":"2.0","component":{"component_type":"mcp-server","component_name":"mcp-server","component_path":".","metadata":{"transports":["stdio","streamable_http"]}},"scan_provenance":{"scanned_at":"2026-10-22T05:22:59.184Z","resolved_ref":"b98d115bbb080e9555466a96656ec4df605aea21","resolved_ver":null},"repo_context":{"repo_url":"https://github.com/github/github-mcp-server","languages":["Go","TypeScript","JavaScript"],"transports_detected":[],"deployment_artifacts":[],"provenance":{"publisher":"github","publisher_verified":true,"signed_build":true,"dependency_cve_count":10,"maintenance_active":true,"declared_policy":false}},"component_findings":[{"rule_id":"ATR-M0001","rule_version":"1.2.0","rule_name":"Tool Definition Integrity & Versioning (ETDI-style)","finding":"SECURE","severity":"HIGH","realized_severity":null,"confidence":"high","rationale":"All 116+ MCP tools are statically compiled Go code registered via AllTools() in pkg/github/tools.go; there is no runtime fetch or hot-reload of tool manifests from a URL or external file. Releases are built and published via CI workflows carrying signed/attested build provenance, and a dedicated MCP Server Diff workflow runs on every PR to flag unexpected tool schema changes. However, pkg/translations/translations.go does read a github-mcp-server-config.json file from the current working directory and supports environment-variable overrides for tool description text, so not every piece of tool metadata is strictly immutable at runtime, though no attacker-controlled path to reach or modify that local config/environment at runtime was established. The diff workflow's presence demonstrates comparison reporting on schema changes; it does not by itself demonstrate that human approval or branch protection is mandatorily enforced before a changed definition can ship.","vulnerability_details":null,"fix_guidance":"Continue requiring the mcp-diff CI gate and signed release artifacts; no action needed.","why_this_matters":"Prevents rug-pull tool poisoning.","context_note":null,"within_declared_contract":true,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"pkg/github/tools.go","start_line":217,"end_line":257,"snippet":"func AllTools(t translations.TranslationHelperFunc, opts ...ToolOption) []inventory.ServerTool {\n\tcfg := newToolConfig(opts)\n\treturn withCSVOutput([]inventory.ServerTool{\n\t\t// Context tools\n\t\tGetMe(t),\n\t\tGetTeams(t),\n\t\tGetTeamMembers(t),\n\n\t\t// Repository tools\n\t\tSearchRepositories(t),\n\t\tGetFileContents(t),\n\t\t..."},{"path":".github/workflows/mcp-diff.yml","start_line":1,"end_line":20,"snippet":"name: MCP Server Diff\n\non:\n  pull_request:\n  push:\n    branches: [main]\n    tags: ['v*']\n\npermissions:\n  contents: read\n\njobs:\n  mcp-diff:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check out code\n        uses: actions/checkout@..."}],"affected_tools":null,"component_name":"mcp-server"},{"rule_id":"ATR-M0002","rule_version":"1.1.0","rule_name":"Unrestricted Tool Permissions (Overprivileged Tools)","finding":"SECURE","severity":"HIGH","realized_severity":null,"confidence":"high","rationale":"Tools are scoped using GitHub's own OAuth-scope model: pkg/scopes/scopes.go defines RequireAll/PublicRead/DynamicChallenge helpers that gate each tool's visibility/challenge behavior by the OAuth scopes it needs. pkg/http/handler.go's PATScopeFilter narrows the visible tool inventory to the caller's actual PAT scopes, but this filtering is advisory defense-in-depth rather than a universal, fail-closed control: it skips filtering for non-classic tokens and returns the inventory unchanged when a scope-fetch call errors. The control that actually bounds privilege regardless of this filtering is that every tool call is ultimately executed against GitHub's REST/GraphQL API using the caller's own token, so GitHub's server-side permission system, not the scope-filter narrowing, is the final enforcement boundary. Read-only mode (--read-only / X-MCP-Readonly) still enforces an upper bound on tool visibility that per-request headers cannot widen.","vulnerability_details":null,"fix_guidance":"No action needed; continue enforcing per-tool scope gating.","why_this_matters":"Limits blast radius and privilege escalation.","context_note":null,"within_declared_contract":true,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"pkg/scopes/scopes.go","start_line":136,"end_line":164,"snippet":"func RequireAll(required ...Scope) inventory.ScopeAccess {\n\trequired = append([]Scope(nil), required...)\n\trequiredScopes := scopeStrings(required)\n\treturn inventory.ScopeAccess{\n\t\tScopes: append([]string(nil), requiredScopes...),\n\t\tVisible: func(activeScopes []string) bool {\n\t\t\treturn HasAll(activeScopes, required...)\n\t\t},\n\t\tChallenge: func(_ map[string]any, activeScopes []string) []string {\n\t\t\tif HasAll(activeScopes, required...) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn append([]string(nil), requiredScopes...)\n\t\t},\n\t}\n}"},{"path":"pkg/http/handler.go","start_line":470,"end_line":497,"snippet":"func PATScopeFilter(b *inventory.Builder, r *http.Request, fetcher scopes.FetcherInterface) *inventory.Builder {\n\tctx := r.Context()\n\ttokenInfo, ok := ghcontext.GetTokenInfo(ctx)\n\tif !ok || tokenInfo == nil {\n\t\treturn b\n\t}\n\tif tokenInfo.TokenType == utils.TokenTypePersonalAccessToken {\n\t\texistingScopes, ok := ghcontext.GetTokenScopes(ctx)\n\t\tif ok {\n\t\t\treturn b.WithFilter(github.CreateToolScopeFilter(existingScopes))\n\t\t}\n\t\t..."}],"affected_tools":null,"component_name":"mcp-server"},{"rule_id":"ATR-M0003","rule_version":"1.3.1","rule_name":"CORS / Origin / Host Validation (DNS Rebinding Defense)","finding":"VULNERABLE","severity":"HIGH","realized_severity":"MEDIUM","confidence":"medium","rationale":"SetCorsHeaders unconditionally sets 'Access-Control-Allow-Origin: *' with no Origin allowlist or validation anywhere in the request path, and the HTTP server binds to all interfaces by default when --listen-host is unset -- together these independently satisfy the rule's missing-Origin-validation-plus-all-interface-binding impact condition. Authentication is enforced via an explicit Authorization bearer header rather than cookies or other ambient credentials, which a cross-origin page cannot cause a browser to attach automatically; this mitigates classic ambient-credential CSRF/DNS-rebinding exploitation, since a rebound-origin page cannot forge the victim's bearer token into its requests without already possessing it. This mitigation reduces exploitability but does not establish a demonstrated browser-token-theft or DNS-rebinding exploit chain -- no explicit Origin or Host allowlist exists to close the gap this rule evaluates.","vulnerability_details":"No Origin or Host header validation is performed anywhere in the HTTP request path before dispatching to MCP tool handlers that have host-side write/delete effects on GitHub, and the listener binds to all interfaces by default. A browser that can be tricked (e.g. via DNS rebinding) into treating the server's address as same-origin could issue cross-origin requests that the server will process, limited in practice by the requirement that the caller already supply a valid bearer token.","fix_guidance":"Add an explicit Origin allowlist (or at minimum validate the Host header matches the expected deployment host) in front of the MCP routes, in addition to the existing bearer-token requirement, to provide defense-in-depth against DNS rebinding for any client that does cache/attach tokens automatically.","why_this_matters":"Prevents DNS rebinding/browser hijack of local or remote MCP.","context_note":"[capability_by_design|relief:-40%|ineligible:within_declared_contract] within_declared_contract is ineligible for relief on ATR-M0003 because the rule declares that signal orthogonal to its risk (discount_ineligible_signals); the reported behaviour is within the tool's declared capability, and that does not reduce this finding's severity. Realized severity MEDIUM (from HIGH: 40% context relief -- mitigation_present -40%).","within_declared_contract":false,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"pkg/http/middleware/cors.go","start_line":33,"end_line":46,"snippet":"// SetCorsHeaders is middleware that sets CORS headers to allow browser-based\n// MCP clients to connect from any origin. This is safe because the server\n// authenticates via bearer tokens (not cookies), so cross-origin requests\n// cannot exploit ambient credentials.\nfunc SetCorsHeaders(h http.Handler) http.Handler {\n\tfixedAllowHeaders := strings.Join(corsAllowedRequestHeaders, \", \")\n\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n\t\tw.Header().Set(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\")\n\t\tw.Header().Set(\"Access-Control-Max-Age\", \"86400\")\n\t\tw.Header().Add(\"Access-Control-Expose-Headers\", \"Mcp-Session-Id, WWW-Authenticate\")\n\t\tw.Header().Set(\"Access-Control-Allow-Headers\", fixedAllowHeaders)"},{"path":"pkg/http/server.go","start_line":273,"end_line":281,"snippet":"func resolveListenAddress(host string, port int) string {\n\tif host == \"\" {\n\t\treturn fmt.Sprintf(\":%d\", port)\n\t}\n\treturn net.JoinHostPort(host, strconv.Itoa(port))\n}"},{"path":"pkg/http/handler.go","start_line":203,"end_line":227,"snippet":"func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tinv, err := h.inventoryFactoryFunc(r)\n\t...\n\tghServer, err := h.githubMcpServerFactory(r, h.deps, invToUse, &github.MCPServerConfig{ ... })"}],"affected_tools":null,"component_name":"mcp-server"},{"rule_id":"ATR-M0004","rule_version":"1.2.0","rule_name":"Binding to 0.0.0.0 Without Auth/TLS","finding":"VULNERABLE","severity":"HIGH","realized_severity":"HIGH","confidence":"high","rationale":"The HTTP server binds to all interfaces by default when --listen-host is unset (resolveListenAddress returns ':8082'), and the default self-hosted deployment serves this listener via plaintext ListenAndServe with no TLS termination enforced in-process or required by configuration. Every request is gated by mandatory bearer-token authentication (ExtractUserToken middleware returns 401 with a WWW-Authenticate challenge when the Authorization header is missing), so an unauthenticated caller cannot invoke any tool merely by reaching the port. However, this check does not protect the bearer credential itself in transit: a caller connecting to this default, all-interface, non-TLS listener from a remote network position can have their Authorization header intercepted on-path, since no TLS or loopback restriction is enforced by default for the self-hosted binary. The documented hosted endpoint (https://api.githubcopilot.com/mcp/) is a separate, TLS-terminated deployment and does not protect operators running the default self-hosted configuration directly.","vulnerability_details":null,"fix_guidance":"Default --listen-host to 127.0.0.1 so the server does not bind to all interfaces without an explicit operator opt-in, and/or require (not merely document) that the process sit behind a TLS-terminating reverse proxy before accepting bearer-token-bearing requests; refuse to start in all-interface mode without TLS unless explicitly overridden.","why_this_matters":"LAN/Internet exposure enables remote exploitation.","context_note":"[capability_by_design|relief:none|ineligible:within_declared_contract] within_declared_contract is ineligible for relief on ATR-M0004 because the rule declares that signal orthogonal to its risk (discount_ineligible_signals); the reported behaviour is within the tool's declared capability, and that does not reduce this finding's severity. No context relief applies; realized severity equals HIGH.","within_declared_contract":null,"transport_gated":null,"mitigation_present":null,"evidence":[{"path":"pkg/http/server.go","start_line":276,"end_line":281,"snippet":"func resolveListenAddress(host string, port int) string {\n\tif host == \"\" {\n\t\treturn fmt.Sprintf(\":%d\", port)\n\t}\n\treturn net.JoinHostPort(host, strconv.Itoa(port))\n}"},{"path":"pkg/http/server.go","start_line":225,"end_line":230,"snippet":"\taddr := resolveListenAddress(cfg.ListenHost, cfg.Port)\n\thttpSvr := http.Server{\n\t\tAddr:              addr,\n\t\tHandler:           r,\n\t\tReadHeaderTimeout: 60 * time.Second,\n\t}"},{"path":"pkg/http/server.go","start_line":247,"end_line":250,"snippet":"\tlogger.Info(\"HTTP server listening\", \"addr\", addr)\n\tif err := httpSvr.ListenAndServe(); err != nil && err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"HTTP server error: %w\", err)\n\t}"},{"path":"pkg/utils/token.go","start_line":44,"end_line":49,"snippet":"func ParseAuthorizationHeader(req *http.Request) (tokenType TokenType, token string, _ error) {\n\tauthHeader := req.Header.Get(httpheaders.AuthorizationHeader)\n\tif authHeader == \"\" {\n\t\treturn 0, \"\", ErrMissingAuthorizationHeader\n\t}\n"},{"path":"pkg/http/middleware/token.go","start_line":26,"end_line":36,"snippet":"\t\t\ttokenType, token, err := utils.ParseAuthorizationHeader(r)\n\t\t\tif err != nil {\n\t\t\t\t// For missing Authorization header, return 401 with WWW-Authenticate header per MCP spec\n\t\t\t\tif errors.Is(err, utils.ErrMissingAuthorizationHeader) {\n\t\t\t\t\tsendAuthChallenge(w, r, oauthCfg)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// For other auth errors (bad format, unsupported), return 400\n\t\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}"}],"affected_tools":null,"component_name":"mcp-server"},{"rule_id":"ATR-M0005","rule_version":"1.4.0","rule_name":"OAuth 2.1 + PKCE Correctness (MCP Authorization)","finding":"SECURE","severity":"CRITICAL","realized_severity":null,"confidence":"high","rationale":"Remote (HTTP) transport: the server acts strictly as an OAuth 2.0 Protected Resource (RFC 9728) and never implements token issuance itself -- it serves /.well-known/oauth-protected-resource metadata pointing clients at GitHub's own authorization server, which is the MCP-spec-compliant delegation pattern. Local (stdio) transport: internal/oauth/flow.go implements authorization-code plus PKCE with S256 challenge, a cryptographically random state parameter validated on callback (a 'state mismatch' check), and a local callback listener; native CLI runs bind this callback listener to loopback, while the Docker-container code path explicitly binds it to all interfaces when running in a container, accompanied by an explicit warning instructing operators to publish the port on loopback only. State validation and PKCE verification are enforced regardless of which interface the callback listener binds to, so the container-mode binding does not itself defeat the CSRF/code-interception protections those checks provide.","vulnerability_details":null,"fix_guidance":"No action needed.","why_this_matters":"Misconfig enables token theft and confused-deputy attacks.","context_note":null,"within_declared_contract":true,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"internal/oauth/flow.go","start_line":73,"end_line":108,"snippet":"func (m *Manager) beginPKCE(prompter Prompter) (*flowPlan, error) {\n\tstate, err := randomState()\n\t...\n\tverifier := oauth2.GenerateVerifier()\n\t...\n\tauthURL := oc.AuthCodeURL(state, oauth2.S256ChallengeOption(verifier))\n\trun := func(ctx context.Context) (*oauth2.Token, error) {\n\t\tcode, err := cs.wait(ctx)\n\t\t...\n\t\ttok, err := oc.Exchange(ctx, code, oauth2.VerifierOption(verifier))\n\t\t..."},{"path":"internal/oauth/callback.go","start_line":96,"end_line":100,"snippet":"\t\tif q.Get(\"state\") != expectedState {\n\t\t\tcs.report(callbackResult{err: fmt.Errorf(\"state mismatch (possible CSRF)\")})\n\t\t\trenderError(w, \"state mismatch\")\n\t\t\treturn\n\t\t}"},{"path":"pkg/http/oauth/oauth.go","start_line":105,"end_line":136,"snippet":"func (h *AuthHandler) metadataHandler() http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t...\n\t\tmetadata := &oauthex.ProtectedResourceMetadata{\n\t\t\tResource:               resourceURL,\n\t\t\tAuthorizationServers:   []string{authorizationServerURL},\n\t\t\t..."}],"affected_tools":null,"component_name":"mcp-server"},{"rule_id":"ATR-M0006","rule_version":"1.2.0","rule_name":"Tool Name Collision / Spoofing","finding":"SECURE","severity":"MEDIUM","realized_severity":null,"confidence":"medium","rationale":"Enumerated all 116+ registered tool names (e.g. get_file_contents, list_issues, search_code, create_pull_request). None are homographs of, or identical to, a specifically-named tool from another widely-deployed MCP server or client built-in. Names are generic GitHub-domain verbs, which the rule's own negative indicator treats as not itself a positive indicator absent a named collision.","vulnerability_details":null,"fix_guidance":"No action required; consider a defensive namespace prefix only if multi-server collision reports emerge.","why_this_matters":"Prevents malicious tool shadowing.","context_note":null,"within_declared_contract":true,"transport_gated":false,"mitigation_present":false,"evidence":[{"path":"pkg/github/tools.go","start_line":217,"end_line":257,"snippet":"func AllTools(t translations.TranslationHelperFunc, opts ...ToolOption) []inventory.ServerTool {\n\tcfg := newToolConfig(opts)\n\t\tSearchRepositories(t), GetFileContents(t), ListCommits(t), SearchCode(t), ..."}],"affected_tools":null,"component_name":"mcp-server"},{"rule_id":"ATR-M0007","rule_version":"1.2.0","rule_name":"Prompt/Metadata Injection in Tool Descriptions","finding":"SECURE","severity":"MEDIUM","realized_severity":null,"confidence":"medium","rationale":"Searched all tool-description strings in pkg/github/*.go for zero-width/invisible Unicode characters and for common injection phrasing -- no matches found. Tool descriptions are short, functional, English-language strings describing the GitHub operation performed. Separately, the server actively sanitizes untrusted content returned from the GitHub API before it reaches the model, which is a related but distinct control from tool-description integrity.","vulnerability_details":null,"fix_guidance":"No action needed; keep descriptions free of hidden directives as part of code review.","why_this_matters":"Prevents covert model manipulation.","context_note":null,"within_declared_contract":true,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"pkg/sanitize/sanitize.go","start_line":22,"end_line":44,"snippet":"func Sanitize(input string) string {\n\tfiltered := FilterCodeFenceMetadata(FilterInvisibleCharacters(input))\n\tnormalized := FilterHTMLTags(filtered)\n\tif normalized == filtered {\n\t\treturn normalized\n\t}\n\treturn FilterCodeFenceMetadata(FilterInvisibleCharacters(normalized))\n}"}],"affected_tools":null,"component_name":"mcp-server"},{"rule_id":"ATR-M0008","rule_version":"1.2.0","rule_name":"CORS Misuse: Wildcard with Credentials","finding":"SECURE","severity":"HIGH","realized_severity":null,"confidence":"high","rationale":"SetCorsHeaders sets 'Access-Control-Allow-Origin: *' but never sets 'Access-Control-Allow-Credentials: true' anywhere in the codebase. Authentication uses an explicit Authorization bearer header rather than cookies, so the dangerous wildcard-origin-plus-credentials combination this rule targets does not exist.","vulnerability_details":null,"fix_guidance":"No action needed; do not introduce Allow-Credentials:true while Allow-Origin remains '*'.","why_this_matters":"Prevents credentialed cross-origin abuse.","context_note":null,"within_declared_contract":true,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"pkg/http/middleware/cors.go","start_line":37,"end_line":54,"snippet":"func SetCorsHeaders(h http.Handler) http.Handler {\n\tfixedAllowHeaders := strings.Join(corsAllowedRequestHeaders, \", \")\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Access-Control-Allow-Origin\", \"*\")\n\t\tw.Header().Set(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\")\n\t\tw.Header().Set(\"Access-Control-Max-Age\", \"86400\")\n\t\tw.Header().Add(\"Access-Control-Expose-Headers\", \"Mcp-Session-Id, WWW-Authenticate\")\n\t\tw.Header().Set(\"Access-Control-Allow-Headers\", fixedAllowHeaders)\n\t\t..."},{"path":"pkg/http/middleware/cors_test.go","start_line":71,"end_line":71,"snippet":"assert.Empty(t, rr.Header().Get(\"Access-Control-Allow-Credentials\"))"}],"affected_tools":null,"component_name":"mcp-server"},{"rule_id":"ATR-M0009","rule_version":"1.3.0","rule_name":"Transport Security: TLS Everywhere (Remote)","finding":"VULNERABLE","severity":"HIGH","realized_severity":"HIGH","confidence":"medium","rationale":"RunHTTPServer binds to all interfaces by default via an address derived from an unset --listen-host and serves the connection via plaintext ListenAndServe -- there is no TLS/loopback requirement enforced, and no TLS-terminating proxy is required by the executable itself. Outbound client traffic to the GitHub API does enforce HTTPS, but that outbound enforcement does not secure the inbound MCP transport that this rule evaluates. The documented hosted endpoint (https://api.githubcopilot.com/mcp/) is a separately TLS-terminated deployment and does not establish TLS protection for this executable's own default, self-hosted startup path; a caller connecting directly to the default HTTP listener transmits its bearer Authorization header in cleartext, which an on-path attacker could intercept.","vulnerability_details":null,"fix_guidance":"Require TLS for the HTTP listener by default (or refuse to serve bearer-token-protected routes over plaintext without an explicit operator override), and/or make a TLS-terminating reverse proxy mandatory rather than merely documented for any non-loopback binding.","why_this_matters":"MitM can steal tokens and tamper responses.","context_note":"[capability_by_design|relief:none|ineligible:within_declared_contract] within_declared_contract is ineligible for relief on ATR-M0009 because the rule declares that signal orthogonal to its risk (discount_ineligible_signals); the reported behaviour is within the tool's declared capability, and that does not reduce this finding's severity. No context relief applies; realized severity equals HIGH.","within_declared_contract":null,"transport_gated":null,"mitigation_present":null,"evidence":[{"path":"cmd/github-mcp-server/main.go","start_line":229,"end_line":232,"snippet":"\t\t\t}\n\n\t\t\treturn ghhttp.RunHTTPServer(httpConfig)\n\t\t},"},{"path":"pkg/http/server.go","start_line":225,"end_line":230,"snippet":"\taddr := resolveListenAddress(cfg.ListenHost, cfg.Port)\n\thttpSvr := http.Server{\n\t\tAddr:              addr,\n\t\tHandler:           r,\n\t\tReadHeaderTimeout: 60 * time.Second,\n\t}"},{"path":"pkg/http/server.go","start_line":247,"end_line":250,"snippet":"\tlogger.Info(\"HTTP server listening\", \"addr\", addr)\n\tif err := httpSvr.ListenAndServe(); err != nil && err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"HTTP server error: %w\", err)\n\t}"},{"path":"pkg/http/server.go","start_line":276,"end_line":281,"snippet":"func resolveListenAddress(host string, port int) string {\n\tif host == \"\" {\n\t\treturn fmt.Sprintf(\":%d\", port)\n\t}\n\treturn net.JoinHostPort(host, strconv.Itoa(port))\n}"},{"path":"pkg/http/middleware/token.go","start_line":26,"end_line":36,"snippet":"\t\t\ttokenType, token, err := utils.ParseAuthorizationHeader(r)\n\t\t\tif err != nil {\n\t\t\t\t// For missing Authorization header, return 401 with WWW-Authenticate header per MCP spec\n\t\t\t\tif errors.Is(err, utils.ErrMissingAuthorizationHeader) {\n\t\t\t\t\tsendAuthChallenge(w, r, oauthCfg)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// For other auth errors (bad format, unsupported), return 400\n\t\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}"}],"affected_tools":null,"component_name":"mcp-server"},{"rule_id":"ATR-M0010","rule_version":"1.4.0","rule_name":"No Authentication or Authorization Checks (General)","finding":"SECURE","severity":"CRITICAL","realized_severity":null,"confidence":"high","rationale":"The HTTP listener requires an Authorization header on every request before tool dispatch: ExtractUserToken middleware parses and validates the header's format/prefix and denies (401 plus WWW-Authenticate challenge) any request with a missing or malformed Authorization header, fail-closed by default; it runs after the request body-size limiting middleware rather than first in the chain. This middleware validates token format/prefix rather than verifying the credential's secret value itself -- the caller's own token is threaded through context and the real authorization/credential check happens when that token is used to call the GitHub API, so every tool action's actual authorization is enforced by GitHub against the caller's own scopes and secret, not by a locally implemented secret-comparison step. There is no shared or ambient service-account identity applied across callers; each request's own credential is what GitHub ultimately verifies.","vulnerability_details":null,"fix_guidance":"No action needed; continue to fail closed on missing/invalid Authorization headers.","why_this_matters":"Prevents unauthorized tool use and data access.","context_note":null,"within_declared_contract":true,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"pkg/http/handler.go","start_line":138,"end_line":151,"snippet":"func (h *Handler) RegisterMiddleware(r chi.Router) {\n\tr.Use(\n\t\tmiddleware.WithMaxBodySize(h.maxRequestBodyBytes()),\n\t\tmiddleware.ExtractUserToken(h.oauthCfg),\n\t\tmiddleware.WithRequestConfig,\n\t\tmiddleware.WithMCPParse(),\n\t\tmiddleware.WithPATScopes(h.logger, h.scopeFetcher),\n\t)\n\tif h.config.ScopeChallenge {\n\t\tr.Use(middleware.WithScopeChallenge(h.oauthCfg, h.scopeFetcher))\n\t}\n}"},{"path":"pkg/http/middleware/token.go","start_line":26,"end_line":36,"snippet":"\t\t\ttokenType, token, err := utils.ParseAuthorizationHeader(r)\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, utils.ErrMissingAuthorizationHeader) {\n\t\t\t\t\tsendAuthChallenge(w, r, oauthCfg)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}"}],"affected_tools":null,"component_name":"mcp-server"},{"rule_id":"ATR-M0011","rule_version":"1.2.0","rule_name":"OAuth Proxy Per-Client Consent (Confused Deputy Defense)","finding":"NOT_APPLICABLE","severity":"CRITICAL","realized_severity":null,"confidence":"high","rationale":"This server is not an OAuth proxy. The HTTP transport only publishes OAuth Protected Resource Metadata pointing MCP clients directly at GitHub's own authorization server -- it never issues, proxies, or mints bridge authorization codes on a client's behalf, and exposes no dynamic MCP-client registration endpoint. The stdio flow is a direct, single-tenant OAuth client rather than a multi-tenant proxy forwarding to a distinct upstream IdP for dynamically registered third-party clients.","vulnerability_details":null,"fix_guidance":"No action needed; if a future feature adds dynamic client registration or a bridge-code proxy flow, re-evaluate this rule.","why_this_matters":"Shared upstream client + dynamic MCP-client registration + no per-client consent enables attacker-registered MCP clients to receive bridge codes carrying victim upstream tokens — the documented MCP Confused Deputy attack.","context_note":null,"within_declared_contract":null,"transport_gated":false,"mitigation_present":null,"evidence":[{"path":"pkg/http/oauth/oauth.go","start_line":105,"end_line":136,"snippet":"func (h *AuthHandler) metadataHandler() http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t...\n\t\tmetadata := &oauthex.ProtectedResourceMetadata{\n\t\t\tResource:               resourceURL,\n\t\t\tAuthorizationServers:   []string{authorizationServerURL},\n\t\t\tResourceName:           \"GitHub MCP Server\",\n\t\t\t...\n\t\tauth.ProtectedResourceMetadataHandler(metadata).ServeHTTP(w, r)\n\t})\n}"}],"affected_tools":null,"component_name":"mcp-server"}],"repo_findings_inherited":[{"rule_id":"ATR-C0001","rule_version":"1.2.0","rule_name":"Unrestricted Network Fetch / SSRF","finding":"SECURE","severity":"HIGH","realized_severity":null,"confidence":"high","rationale":"The repo exposes no tool that fetches an arbitrary attacker-supplied URL/host. The only outbound HTTP fetch of a dynamic URL is downloadLogContent(), which fetches a short-lived download URL returned by an authenticated GitHub API call, not a user-supplied host. The raw-content client builds URLs by joining a fixed, server-configured base host with owner/repo/ref/path, explicitly rejecting path-traversal segments. All other GitHub API calls go through a fixed-base-URL SDK client. No generic fetch-URL style tool exists.","vulnerability_details":null,"fix_guidance":"No change required; if a URL-fetching tool is added in the future, enforce scheme/host allowlists and block RFC1918/metadata ranges before adding it.","why_this_matters":"Enables internal pivoting and data exfiltration.","context_note":null,"within_declared_contract":true,"transport_gated":null,"mitigation_present":true,"evidence":[{"path":"pkg/github/actions.go","start_line":163,"end_line":177,"snippet":"func downloadLogContent(ctx context.Context, logURL string, tailLines int, maxLines int) (string, int, *http.Response, error) {\n\tprof := profiler.New(nil, profiler.IsProfilingEnabled())\n\tfinish := prof.Start(ctx, \"log_buffer_processing\")\n\n\thttpResp, err := http.Get(logURL) //nolint:gosec\n\tif err != nil {\n\t\treturn \"\", 0, httpResp, fmt.Errorf(\"failed to download logs: %w\", err)\n\t}\n\tdefer func() { _ = httpResp.Body.Close() }()"},{"path":"pkg/raw/raw.go","start_line":88,"end_line":96,"snippet":"func (c *Client) refURL(owner, repo, ref, path string) (string, error) {\n\tif ref == \"\" {\n\t\tref = \"HEAD\"\n\t}\n\tif err := rejectPathTraversal(owner, repo, ref, path); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn c.url.JoinPath(owner, repo, ref, path).String(), nil\n}"},{"path":"pkg/github/actions.go","start_line":167,"end_line":167,"snippet":null,"vulnerability":{"severity":"MEDIUM"}}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0002","rule_version":"1.3.2","rule_name":"Static Secrets","finding":"SECURE","severity":"HIGH","realized_severity":null,"confidence":"high","rationale":"Gitleaks (full git history scan) returned 0 findings. Manual grep for API_KEY/SECRET/PASSWORD/TOKEN literal assignments across Go/YAML/JSON/env files found no committed secret values. The only credential-adjacent code declares empty OAuth client ID/secret package vars populated only at build time via ldflags from build secrets mounted during the Docker build, and these are documented as public OAuth client identifiers kept out of source.","vulnerability_details":null,"fix_guidance":"No change required.","why_this_matters":"Plaintext secrets are easily exfiltrated.","context_note":null,"within_declared_contract":null,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"internal/buildinfo/buildinfo.go","start_line":1,"end_line":18},{"path":"Dockerfile","start_line":28,"end_line":35}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0003","rule_version":"1.3.0","rule_name":"Runtime Command Injection via Process Execution","finding":"SECURE","severity":"CRITICAL","realized_severity":null,"confidence":"high","rationale":"All exec.Command call sites were enumerated: openBrowser() invokes a fixed platform launcher with an internally-constructed OAuth URL as a single argv element, with the URL produced by the server's own OAuth flow code rather than a remote attacker, and argv-vector execution prevents shell metacharacter injection regardless. The mcpcurl developer CLI builds a command from an operator-supplied string but is a standalone developer/test tool, not the shipped MCP server runtime. No exec/shell invocation was found reachable from the actual server runtime's tool-call handling path.","vulnerability_details":null,"fix_guidance":"No change required for the shipped server runtime.","why_this_matters":"Unsanitized command construction in shipped runtime code can give a remote attacker arbitrary code execution with the application's privileges.","context_note":null,"within_declared_contract":true,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"internal/oauth/env.go","start_line":23,"end_line":37,"snippet":"func openBrowser(url string) error {\n\tvar cmd *exec.Cmd\n\tswitch runtime.GOOS {\n\tcase \"linux\":\n\t\tif os.Getenv(\"DISPLAY\") == \"\" && os.Getenv(\"WAYLAND_DISPLAY\") == \"\" {\n\t\t\treturn errNoDisplay\n\t\t}\n\t\tcmd = exec.Command(\"xdg-open\", url)\n\tcase \"darwin\":\n\t\tcmd = exec.Command(\"open\", url)\n\tcase \"windows\":\n\t\tcmd = exec.Command(\"rundll32\", \"url.dll,FileProtocolHandler\", url)"},{"path":"cmd/mcpcurl/main.go","start_line":381,"end_line":388,"snippet":"func executeServerCommand(cmdStr, jsonRequest string) (string, error) {\n\tcmdParts := strings.Fields(cmdStr)\n\tif len(cmdParts) == 0 {\n\t\treturn \"\", fmt.Errorf(\"empty command\")\n\t}\n\n\tcmd := exec.Command(cmdParts[0], cmdParts[1:]...) //nolint:gosec //mcpcurl is a test command that needs to execute arbitrary shell commands"},{"path":"internal/oauth/env.go","start_line":34,"end_line":34,"snippet":null,"vulnerability":{"severity":"MEDIUM"}},{"path":"internal/oauth/env.go","start_line":32,"end_line":32,"snippet":null,"vulnerability":{"severity":"MEDIUM"}},{"path":"internal/oauth/env.go","start_line":30,"end_line":30,"snippet":null,"vulnerability":{"severity":"MEDIUM"}},{"path":"cmd/mcpcurl/main.go","start_line":388,"end_line":388,"snippet":null,"vulnerability":{"severity":"MEDIUM"}}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0004","rule_version":"1.2.0","rule_name":"DoS Protections: Timeouts, Concurrency, Streaming Limits","finding":"VULNERABLE","severity":"MEDIUM","realized_severity":"MEDIUM","confidence":"high","rationale":"The HTTP server sets ReadHeaderTimeout (60s), which bounds only the time to receive request headers. The request body is read via io.ReadAll in the handler path with no accompanying read deadline or per-request timeout, so a client that completes headers and then sends a body slowly, staying under the configured max-body-size, can hold the connection and its associated goroutine open indefinitely. Neither the max-body-size limit nor the batch-operation semaphores bound this slow-body read path, and no request-level or body-read deadline is configured distinct from the server's long-lived streaming response policy.","vulnerability_details":null,"fix_guidance":"Add an explicit read deadline (e.g. a timeout handler, SetReadDeadline, or a per-request context deadline applied before io.ReadAll) for request bodies, separate from the timeout used for long-lived streaming responses, so slow-body connections are closed after a bounded interval.","why_this_matters":"Resource exhaustion can crash or stall MCP.","context_note":"[capability_by_design|relief:none|ineligible:within_declared_contract] within_declared_contract is ineligible for relief on ATR-C0004 because the rule declares that signal orthogonal to its risk (discount_ineligible_signals); the reported behaviour is within the tool's declared capability, and that does not reduce this finding's severity. No context relief applies; realized severity equals MEDIUM.","within_declared_contract":null,"transport_gated":null,"mitigation_present":null,"evidence":[{"path":"pkg/http/server.go","start_line":213,"end_line":230,"snippet":"\tr := newHTTPRouter(\n\t\tfunc(r chi.Router) {\n\t\t\t// Register Middleware First, needs to be before route registration\n\t\t\thandler.RegisterMiddleware(r)\n\t\t\t// Register MCP server routes\n\t\t\thandler.RegisterRoutes(r)\n\t\t},\n\t\toauthHandler.RegisterRoutes,\n\t)\n\tlogger.Info(\"MCP endpoints registered\", \"baseURL\", cfg.BaseURL)\n\tlogger.Info(\"OAuth protected resource endpoints registered\", \"baseURL\", cfg.BaseURL)\n\n\taddr := resolveListenAddress(cfg.ListenHost, cfg.Port)\n\thttpSvr := http.Server{\n\t\tAddr:              addr,\n\t\tHandler:           r,\n\t\tReadHeaderTimeout: 60 * time.Second,\n\t}"},{"path":"pkg/http/handler.go","start_line":138,"end_line":146,"snippet":"func (h *Handler) RegisterMiddleware(r chi.Router) {\n\tr.Use(\n\t\t// Must run first: bounds the body before anything downstream reads it.\n\t\tmiddleware.WithMaxBodySize(h.maxRequestBodyBytes()),\n\t\tmiddleware.ExtractUserToken(h.oauthCfg),\n\t\tmiddleware.WithRequestConfig,\n\t\tmiddleware.WithMCPParse(),\n\t\tmiddleware.WithPATScopes(h.logger, h.scopeFetcher),\n\t)"},{"path":"pkg/http/middleware/mcp_parse.go","start_line":54,"end_line":66,"snippet":"\t\t\t// Only parse POST requests (MCP uses JSON-RPC over POST)\n\t\t\tif r.Method != http.MethodPost {\n\t\t\t\tnext.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Read the request body\n\t\t\tbody, err := io.ReadAll(r.Body)\n\t\t\tif err != nil {\n\t\t\t\tif isMaxBytesError(err) {\n\t\t\t\t\twriteRequestTooLarge(w)\n\t\t\t\t\treturn\n\t\t\t\t}"},{"path":"pkg/utils/token.go","start_line":64,"end_line":68,"snippet":"\t\tif strings.HasPrefix(token, prefix) {\n\t\t\treturn tokenType, token, nil\n\t\t}\n\t}\n"}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0005","rule_version":"1.2.0","rule_name":"Logging Hygiene (No Secrets/PII/Tokens) & Audit Trail","finding":"VULNERABLE","severity":"MEDIUM","realized_severity":"MEDIUM","confidence":"medium","rationale":"When command/transport logging is enabled (opt-in), the IOLogger logs every incoming and outgoing JSON-RPC message at Info level, including raw tool inputs and results, without redaction or scrubbing. This can include private tool inputs/results and potentially PII or secrets present in repository content. The log file is created with mode 0600 and the feature is opt-in, which reduces exposure, but the logging still writes unscrubbed payloads at a non-debug level, which does not meet the bar of ensuring sensitive content is scrubbed even when verbose logging is enabled. Scope-name logging on the auth path remains clean, recording only scope names rather than secrets.","vulnerability_details":null,"fix_guidance":"Redact or scrub tool inputs/results before writing them to the IOLogger, or gate raw payload logging to an explicit debug level with documented scrubbing, rather than logging raw JSON-RPC bytes at Info level.","why_this_matters":"Prevents sensitive data leakage; enables forensics.","context_note":"[capability_by_design|relief:none|ineligible:within_declared_contract] within_declared_contract is ineligible for relief on ATR-C0005 because the rule declares that signal orthogonal to its risk (discount_ineligible_signals); the reported behaviour is within the tool's declared capability, and that does not reduce this finding's severity. No context relief applies; realized severity equals MEDIUM.","within_declared_contract":null,"transport_gated":null,"mitigation_present":null,"evidence":[{"path":"internal/ghmcp/server.go","start_line":404,"end_line":415,"snippet":"\t\tin = os.Stdin\n\t\tout = os.Stdout\n\n\t\tif cfg.EnableCommandLogging {\n\t\t\tloggedIO := mcplog.NewIOLogger(in, out, logger)\n\t\t\tin, out = loggedIO, loggedIO\n\t\t}\n\n\t\t// enable GitHub errors in the context\n\t\tctx := errors.ContextWithGitHubErrors(ctx)\n\t\terrC <- ghServer.Run(ctx, &mcp.IOTransport{Reader: in, Writer: out})\n\t}()"},{"path":"pkg/log/io.go","start_line":28,"end_line":47,"snippet":"// Read reads data from the underlying io.Reader and logs it.\nfunc (l *IOLogger) Read(p []byte) (n int, err error) {\n\tif l.reader == nil {\n\t\treturn 0, io.EOF\n\t}\n\tn, err = l.reader.Read(p)\n\tif n > 0 {\n\t\tl.logger.Info(\"[stdin]: received bytes\", \"count\", n, \"data\", string(p[:n]))\n\t}\n\treturn n, err\n}\n\n// Write writes data to the underlying io.Writer and logs it.\nfunc (l *IOLogger) Write(p []byte) (n int, err error) {\n\tif l.writer == nil {\n\t\treturn 0, io.ErrClosedPipe\n\t}\n\tl.logger.Info(\"[stdout]: sending bytes\", \"count\", len(p), \"data\", string(p))\n\treturn l.writer.Write(p)\n}"},{"path":"internal/ghmcp/server.go","start_line":328,"end_line":338,"snippet":"\tif cfg.LogFilePath != \"\" {\n\t\tfile, err := os.OpenFile(cfg.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to open log file: %w\", err)\n\t\t}\n\t\tlogOutput = file\n\t\tslogHandler = slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelDebug})\n\t} else {\n\t\tlogOutput = os.Stderr\n\t\tslogHandler = slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelInfo})\n\t}"}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0006","rule_version":"1.2.0","rule_name":"Container/Runtime Hardening","finding":"VULNERABLE","severity":"MEDIUM","realized_severity":"MEDIUM","confidence":"high","rationale":"The final runtime stage of the Dockerfile is FROM gcr.io/distroless/base-debian12 with no USER directive and no securityContext hardening, so the ENTRYPOINT process runs as root inside the container. If the server process were compromised via any future vulnerability, it would execute as root within the container, maximizing blast radius for container-breakout-adjacent primitives. The distroless base ships no shell/package manager, narrowing but not eliminating impact.","vulnerability_details":null,"fix_guidance":"Use gcr.io/distroless/base-debian12:nonroot (or add an explicit non-root USER/numeric UID) as the final base image so the ENTRYPOINT does not run as root; add a HEALTHCHECK and run with a read-only root filesystem at deployment time.","why_this_matters":"Reduces blast radius of exploits.","context_note":"[capability_by_design|relief:none|ineligible:within_declared_contract] within_declared_contract is ineligible for relief on ATR-C0006 because the rule declares that signal orthogonal to its risk (discount_ineligible_signals); the reported behaviour is within the tool's declared capability, and that does not reduce this finding's severity. No context relief applies; realized severity equals MEDIUM.","within_declared_contract":false,"transport_gated":false,"mitigation_present":false,"evidence":[{"path":"Dockerfile","start_line":37,"end_line":52,"snippet":"# Make a stage to run the app\nFROM gcr.io/distroless/base-debian12@sha256:fabbf1c0c357a3d42550111351daed089b20a2c954df13ee2fcff60602515e84\n\nLABEL io.modelcontextprotocol.server.name=\"io.github.github/github-mcp-server\"\n\nWORKDIR /server\nCOPY --from=build /bin/github-mcp-server .\nEXPOSE 8082\nENTRYPOINT [\"/server/github-mcp-server\"]\nCMD [\"stdio\"]"},{"path":"Dockerfile","start_line":1,"end_line":10,"snippet":"FROM node:26-alpine@sha256:e88a35be04478413b7c71c455cd9865de9b9360e1f43456be5951032d7ac1a66 AS ui-build\nWORKDIR /app\nCOPY ui/package*.json ./ui/\nRUN cd ui && npm ci\nCOPY ui/ ./ui/\n# Create output directory and build - vite outputs directly to pkg/github/ui_dist/\nRUN mkdir -p ./pkg/github/ui_dist && \\\n    cd ui && npm run build\n\nFROM golang:1.27.3-alpine@sha256:f2c872bcee949b7be9c490efd02b48a8a2fbf947cdf533f1c58525ec9d8170fd AS build"}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0007","rule_version":"1.3.0","rule_name":"Dependency Risk & Supply Chain","finding":"VULNERABLE","severity":"HIGH","realized_severity":"HIGH","confidence":"high","rationale":"Trivy identified five CVEs affecting golang.org/x/net v0.59.0, but the only direct imports found in the codebase are the html and httpguts packages, not the HTTP/2 implementation; the advisories establish vulnerable dependency presence rather than a demonstrated HTTP/2 exploitation path reachable from this code. A more concrete supply-chain risk is the release workflow (.github/workflows/registry-releaser.yml), which clones and builds an unversioned/unpinned publisher repository and executes it with OIDC publishing authority, meaning any compromise of that external source could execute arbitrary code with release-publishing credentials. Mutable version tags on actions/cache and actions/setup-node in build-ui/action.yml are a related integrity weakness; the absence of a Dependabot cooldown is a hardening gap rather than demonstrated compromise.","vulnerability_details":null,"fix_guidance":"Bump golang.org/x/net to >=0.60.0; pin actions/cache and actions/setup-node to immutable commit SHAs; add dependabot cooldown entries for gomod/npm/docker ecosystems; and pin the registry-releaser workflow's cloned publisher source to a fixed, verified commit or tag rather than building and executing it unpinned with OIDC publish authority.","why_this_matters":"Prevents typosquat and malicious update compromise.","context_note":"[capability_by_design|relief:none|ineligible:within_declared_contract] [readers_disagree:mitigation_present] within_declared_contract is ineligible for relief on ATR-C0007 because the rule declares that signal orthogonal to its risk (discount_ineligible_signals); the reported behaviour is within the tool's declared capability, and that does not reduce this finding's severity. The scan reported mitigation_present but the independent adjudication did not corroborate it; relief (-40%) requires both readers, so none is applied for that signal. No context relief applies; realized severity equals HIGH.","within_declared_contract":false,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"go.mod","start_line":23,"end_line":24,"snippet":"golang.org/x/net v0.59.0\ngolang.org/x/oauth2 v0.37.0"},{"path":"go.mod","start_line":1,"end_line":3,"snippet":"module github.com/github/github-mcp-server/v2\n\ngo 1.27.3"},{"path":".github/actions/build-ui/action.yml","start_line":9,"end_line":24,"snippet":"    - name: Cache UI artifacts\n      id: cache-ui\n      uses: actions/cache@v6\n      with:\n        path: |\n          pkg/github/ui_dist/get-me.html\n          pkg/github/ui_dist/issue-write.html\n          pkg/github/ui_dist/pr-write.html\n          pkg/github/ui_dist/pr-edit.html\n        key: ui-dist-v3-${{ hashFiles(...) }}\n        enableCrossOsArchive: true\n\n    - name: Set up Node.js\n      if: steps.cache-ui.outputs.cache-hit != 'true'\n      uses: actions/setup-node@v7"},{"path":"go.mod","start_line":0,"end_line":0,"snippet":null,"vulnerability":{"vulnerability_id":"CVE-2026-78669","pkg_name":"golang.org/x/net","severity":"HIGH","installed_version":"v0.59.0","fixed_version":"0.60.0"}},{"path":"go.mod","start_line":0,"end_line":0,"snippet":null,"vulnerability":{"vulnerability_id":"CVE-2026-78659","pkg_name":"golang.org/x/net","severity":"MEDIUM","installed_version":"v0.59.0","fixed_version":"0.60.0"}},{"path":"go.mod","start_line":0,"end_line":0,"snippet":null,"vulnerability":{"vulnerability_id":"CVE-2026-78660","pkg_name":"golang.org/x/net","severity":"MEDIUM","installed_version":"v0.59.0","fixed_version":"0.60.0"}},{"path":"go.mod","start_line":0,"end_line":0,"snippet":null,"vulnerability":{"vulnerability_id":"CVE-2026-78663","pkg_name":"golang.org/x/net","severity":"MEDIUM","installed_version":"v0.59.0","fixed_version":"0.60.0"}},{"path":"go.mod","start_line":0,"end_line":0,"snippet":null,"vulnerability":{"vulnerability_id":"CVE-2026-97032","pkg_name":"golang.org/x/net","severity":"MEDIUM","installed_version":"v0.59.0","fixed_version":"0.60.0"}},{"path":".github/dependabot.yml","start_line":6,"end_line":25,"snippet":"updates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"npm\"\n    directory: \"/ui\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"docker\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    cooldown:\n      default-days: 7"}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0008","rule_version":"1.2.0","rule_name":"Missing or Inadequate Input Validation","finding":"SECURE","severity":"HIGH","realized_severity":null,"confidence":"high","rationale":"Tool parameters are declared with JSON Schema and fetched via typed helper functions that enforce type conversion and return errors on mismatch rather than silently coercing. Pagination and other numeric params go through dedicated schema builders with declared min/max constraints. The MCP SDK performs schema validation of incoming tool-call arguments against each tool's declared InputSchema before the handler runs.","vulnerability_details":null,"fix_guidance":"No change required.","why_this_matters":"Frontline control to prevent multiple classes of vulns.","context_note":null,"within_declared_contract":null,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"pkg/github/params.go","start_line":196,"end_line":210,"snippet":"// OptionalParam is a helper function that can be used to fetch a requested parameter from the request.\nfunc OptionalParam[T any](args map[string]any, p string) (T, error) {"}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0009","rule_version":"1.2.0","rule_name":"Insecure Deserialization","finding":"SECURE","severity":"HIGH","realized_severity":null,"confidence":"medium","rationale":"The codebase uses standard encoding/json, which can invoke custom UnmarshalJSON methods during decoding; the custom UnmarshalJSON implementations present in this codebase were inspected and only perform safe, bounded field decoding, with no code-executing or constructor-invoking behavior. Batch mutation handling does build a struct type dynamically via reflect.StructOf, but the fields of that struct have fixed, statically-determined types -- the dynamic construction selects among known result shapes rather than resolving an attacker-supplied type name into an arbitrary executable constructor. No pickle-equivalent, gob, or yaml.Unmarshal-into-interface{} pattern capable of executing code on load exists in the codebase.","vulnerability_details":null,"fix_guidance":"No change required; continue avoiding yaml.Unmarshal/gob for untrusted input.","why_this_matters":"Can lead to arbitrary code execution.","context_note":null,"within_declared_contract":null,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"pkg/inventory/typed_schema.go","start_line":130,"end_line":134,"snippet":"if err := json.Unmarshal(encoded, schema); err != nil {\n    return nil, fmt.Errorf(...)\n}"}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0010","rule_version":"1.2.0","rule_name":"Lack of Output Encoding/Sanitization","finding":"SECURE","severity":"MEDIUM","realized_severity":null,"confidence":"medium","rationale":"The two non-test direct ResponseWriter.Write call sites were reviewed: one sets an explicit JSON content type before writing, avoiding content-type sniffing/HTML-rendering risk, and the other's error response always begins with a fixed, non-attacker-controlled prefix, which prevents content-sniffing from ever classifying the response as HTML even though no explicit Content-Type is set. Separately, sanitize.Content, used for third-party GitHub content such as issue/PR bodies, only filters invisible/zero-width/bidi Unicode characters -- it does not strip HTML tags or code-fence metadata; sanitize.PlainText is the function that additionally applies HTML stripping and code-fence filtering, used for title-type fields. The UI renders content via React text interpolation rather than raw HTML insertion, providing an additional safe output context.","vulnerability_details":null,"fix_guidance":"Defense-in-depth: explicitly set 'Content-Type: text/plain; charset=utf-8' before writing error bodies that echo request-derived values, to remove reliance on content-sniffing prefix behavior.","why_this_matters":"Prevents UI-level injection and operator compromise.","context_note":null,"within_declared_contract":null,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"pkg/http/handler.go","start_line":203,"end_line":212,"snippet":"func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tinv, err := h.inventoryFactoryFunc(r)\n\tif err != nil {\n\t\tif errors.Is(err, inventory.ErrUnknownTools) {\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\tif _, writeErr := w.Write([]byte(err.Error())); writeErr != nil {\n\t\t\t\th.logger.Error(\"failed to write response\", \"error\", writeErr)\n\t\t\t}\n\t\t\treturn\n\t\t}"},{"path":"pkg/inventory/builder.go","start_line":13,"end_line":14,"snippet":"// ErrUnknownTools is returned when tools specified via WithTools() are not recognized.\nErrUnknownTools = errors.New(\"unknown tools specified in WithTools\")"}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0011","rule_version":"1.4.0","rule_name":"Path Traversal & Symlink Bypass (Filesystem Tools)","finding":"SECURE","severity":"HIGH","realized_severity":null,"confidence":"high","rationale":"No MCP tool handler accepts a caller-supplied path and resolves it against the local filesystem; GitHub-facing file tools operate through the GitHub API against a remote repository, not local disk. The raw-content client explicitly rejects path-traversal segments, including recursively percent-decoded '..', before constructing its URL. Local-filesystem reads from a variable path exist only for operator-controlled purposes: a test-only snapshot helper, private-key loading for GitHub App authentication, CLI documentation-generation commands, and configured log file writes -- none of these accept a network/request-controlled path; they are populated from local CLI flags or operator configuration, not from MCP tool-call arguments.","vulnerability_details":null,"fix_guidance":"No change required.","why_this_matters":"Traversal exposes arbitrary host files; can lead to code execution.","context_note":null,"within_declared_contract":null,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":"pkg/raw/raw.go","start_line":44,"end_line":61,"snippet":"func rejectSegment(segment string) error {\n\tif segment == \"\" {\n\t\treturn nil\n\t}\n\tif segment == \"..\" {\n\t\treturn errPathTraversal\n\t}\n\tdecoded, err := url.PathUnescape(segment)\n\tif err != nil || decoded == segment {\n\t\treturn nil\n\t}\n\tfor subsegment := range strings.SplitSeq(decoded, \"/\") {\n\t\tif err := rejectSegment(subsegment); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}"},{"path":"internal/toolsnaps/toolsnaps.go","start_line":34,"end_line":34,"snippet":null,"vulnerability":{"severity":"MEDIUM"}}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0012","rule_version":"1.2.0","rule_name":"Denial of Wallet / Cost Controls","finding":"VULNERABLE","severity":"MEDIUM","realized_severity":"MEDIUM","confidence":"medium","rationale":"The server exposes actions_run_trigger, which allows a caller with the requisite repository credential to dispatch or rerun GitHub Actions workflows repeatedly, consuming billable runner time. The handler validates parameters and requires appropriate repo scope before allowing the call, but there is no per-user or per-task dispatch quota or spending check layered on top of that access control. GitHub's API rate limits bound request throughput, not the monetary cost or duration of workflows that are launched, so they do not function as a cost control for this capability. This is a missing cost-control layer on an intentionally exposed capability, not an authentication or authorization bypass.","vulnerability_details":null,"fix_guidance":"Add a per-user or per-task quota/rate limit on actions_run_trigger dispatch/rerun calls, and consider requiring explicit confirmation or a cost-awareness flag before dispatching workflows, to prevent runaway billable CI usage.","why_this_matters":"Prevents cost-based disruption.","context_note":"[capability_by_design|relief:none|ineligible:within_declared_contract] within_declared_contract is ineligible for relief on ATR-C0012 because the rule declares that signal orthogonal to its risk (discount_ineligible_signals); the reported behaviour is within the tool's declared capability, and that does not reduce this finding's severity. No context relief applies; realized severity equals MEDIUM.","within_declared_contract":null,"transport_gated":null,"mitigation_present":null,"evidence":[{"path":"pkg/github/tools.go","start_line":330,"end_line":334,"snippet":"\t\t// Actions tools\n\t\tActionsList(t),\n\t\tActionsGet(t),\n\t\tActionsRunTrigger(t),\n\t\tActionsGetJobLogs(t),"},{"path":"pkg/github/actions.go","start_line":481,"end_line":486,"snippet":"\t\tToolsetMetadataActions,\n\t\tmcp.Tool{\n\t\t\tName:         \"actions_run_trigger\",\n\t\t\tOutputSchema: actionsRunTriggerOutputSchema(),\n\t\t\tDescription:  t(\"TOOL_ACTIONS_RUN_TRIGGER_DESCRIPTION\", \"Trigger GitHub Actions workflow operations, including running, re-running, cancelling workflow runs, and deleting workflow run logs.\"),\n\t\t\tAnnotations: &mcp.ToolAnnotations{"},{"path":"pkg/github/actions.go","start_line":528,"end_line":544,"snippet":"\t\tscopes.RequireAll(scopes.Repo),\n\t\tfunc(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args ActionsRunTriggerInput) (*mcp.CallToolResult, *ActionsRunTriggerOutput, error) {\n\t\t\towner, repo, method := args.Owner, args.Repo, args.Method\n\t\t\tworkflowID, ref, runID, inputs := args.WorkflowID, args.Ref, args.RunID, args.Inputs\n\n\t\t\t// Validate required parameters based on action type\n\t\t\tif method == actionsMethodRunWorkflow {\n\t\t\t\tif workflowID == \"\" {\n\t\t\t\t\treturn utils.NewToolResultError(\"workflow_id is required for run_workflow action\"), nil, nil\n\t\t\t\t}\n\t\t\t\tif ref == \"\" {\n\t\t\t\t\treturn utils.NewToolResultError(\"ref is required for run_workflow action\"), nil, nil\n\t\t\t\t}\n\t\t\t} else if runID == 0 {\n\t\t\t\treturn utils.NewToolResultError(\"missing required parameter: run_id\"), nil, nil\n\t\t\t}\n"},{"path":"pkg/github/actions.go","start_line":550,"end_line":556,"snippet":"\t\t\tswitch method {\n\t\t\tcase actionsMethodRunWorkflow:\n\t\t\t\treturn runWorkflow(ctx, client, owner, repo, workflowID, ref, inputs)\n\t\t\tcase actionsMethodRerunWorkflowRun:\n\t\t\t\treturn rerunWorkflowRun(ctx, client, owner, repo, int64(runID))\n\t\t\tcase actionsMethodRerunFailedJobs:\n\t\t\t\treturn rerunFailedJobs(ctx, client, owner, repo, int64(runID))"},{"path":"pkg/github/actions.go","start_line":893,"end_line":909,"snippet":"func runWorkflow(ctx context.Context, client *github.Client, owner, repo, workflowID, ref string, inputs map[string]any) (*mcp.CallToolResult, *ActionsRunTriggerOutput, error) {\n\tevent := github.CreateWorkflowDispatchEventRequest{\n\t\tRef:    ref,\n\t\tInputs: inputs,\n\t}\n\n\tvar resp *github.Response\n\tvar err error\n\tvar workflowType ActionsWorkflowType\n\n\tif workflowIDInt, parseErr := strconv.ParseInt(workflowID, 10, 64); parseErr == nil {\n\t\t_, resp, err = client.Actions.CreateWorkflowDispatchEventByID(ctx, owner, repo, workflowIDInt, event)\n\t\tworkflowType = \"workflow_id\"\n\t} else {\n\t\t_, resp, err = client.Actions.CreateWorkflowDispatchEventByFileName(ctx, owner, repo, workflowID, event)\n\t\tworkflowType = \"workflow_file\"\n\t}"},{"path":"pkg/github/actions_types.go","start_line":326,"end_line":341,"snippet":"\tif usage.Billable == nil {\n\t\treturn out\n\t}\n\tfor runner, bill := range *usage.Billable {\n\t\titem := ActionsRunnerUsage{Runner: runner}\n\t\tif bill != nil {\n\t\t\titem.TotalMS, item.Jobs = bill.TotalMS, bill.Jobs\n\t\t\tfor _, job := range bill.JobRuns {\n\t\t\t\tif job != nil {\n\t\t\t\t\titem.JobRuns = append(item.JobRuns, ActionsJobRunUsage{\n\t\t\t\t\t\tJobID: job.JobID, DurationMS: job.DurationMS,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tout.Billable = append(out.Billable, item)"}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0013","rule_version":"1.1.0","rule_name":"Tool Output Sanitization & Injection Containment","finding":"VULNERABLE","severity":"HIGH","realized_severity":"HIGH","confidence":"medium","rationale":"Third-party/user-authored content returned by tools (issue bodies, PR bodies, comments, discussions, etc.) is passed through sanitize.Content before being placed into tool results, but sanitize.Content only filters invisible/zero-width/bidi Unicode characters -- it does not strip HTML or code-fence metadata; only sanitize.PlainText, used for title-type fields, performs that additional filtering. An all-ASCII issue body containing a fabricated role/instruction marker passes through sanitize.Content unchanged. The IFC untrusted-content labeling mechanism provides a real mitigation but is feature-gated and is omitted when a visibility lookup fails, so it does not provide a mandatory, universally-applied containment policy. The JSON structural boundary around tool results is a real but partial mitigation that does not, by itself, neutralize crafted instruction-like text from being included verbatim in model-visible output, though this does not demonstrate that a downstream model actually executes an injected instruction.","vulnerability_details":null,"fix_guidance":"Apply instruction/role-marker-aware filtering, not only invisible-character stripping, to all third-party content routed through sanitize.Content, and make IFC untrusted-content labeling mandatory (fail-closed) rather than omitted on visibility-lookup failure, so externally authored text cannot masquerade as system/tool-level instructions.","why_this_matters":"Tool outputs are a primary prompt-injection vector. Third-party content (web pages, files, API responses) may contain text crafted to override instructions or impersonate system/tool messages. Without a host-enforced envelope, sanitization, and an explicit untrusted-return signal, the model can be coerced into executing attacker-controlled directives, leaking secrets, or chaining further tool calls on behalf of the adversary.","context_note":"[capability_by_design|relief:none|ineligible:within_declared_contract] within_declared_contract is ineligible for relief on ATR-C0013 because the rule declares that signal orthogonal to its risk (discount_ineligible_signals); the reported behaviour is within the tool's declared capability, and that does not reduce this finding's severity. No context relief applies; realized severity equals HIGH.","within_declared_contract":null,"transport_gated":null,"mitigation_present":null,"evidence":[{"path":"pkg/github/minimal_types.go","start_line":814,"end_line":819,"snippet":"func convertToMinimalIssue(issue *github.Issue) MinimalIssue {\n\tm := MinimalIssue{\n\t\tNumber:            issue.GetNumber(),\n\t\tTitle:             sanitize.PlainText(issue.GetTitle()),\n\t\tBody:              sanitize.Content(issue.GetBody()),\n\t\tState:             issue.GetState(),"},{"path":"pkg/sanitize/sanitize.go","start_line":46,"end_line":48,"snippet":"func Content(input string) string {\n\treturn FilterInvisibleCharacters(input)\n}"},{"path":"pkg/sanitize/sanitize.go","start_line":174,"end_line":183,"snippet":"\t\t}\n\t}\n\treturn input\n}\n\n// filterInvisibleFrom resumes FilterInvisibleCharacters at start, the first byte\n// that could need filtering. It buffers output only once a rune actually\n// changes, so input that turns out to be clean is still returned as-is.\nfunc filterInvisibleFrom(input string, start int) string {\n\tvar ("},{"path":"pkg/github/server.go","start_line":222,"end_line":229,"snippet":"func MarshalledTextResult(v any) *mcp.CallToolResult {\n\tdata, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn utils.NewToolResultErrorFromErr(\"failed to marshal text result to json\", err)\n\t}\n\n\treturn utils.NewToolResultText(string(data))\n}"},{"path":"pkg/github/ifc_labels.go","start_line":156,"end_line":170,"snippet":"\treturn func(r *mcp.CallToolResult) *mcp.CallToolResult {\n\t\tif r == nil || r.IsError || !deps.IsFeatureEnabled(ctx, FeatureFlagIFCLabels) {\n\t\t\treturn r\n\t\t}\n\t\tif !known {\n\t\t\tp, err := FetchRepoIsPrivate(ctx, client, owner, repo)\n\t\t\tif err != nil {\n\t\t\t\treturn r\n\t\t\t}\n\t\t\tisPrivate = p\n\t\t\tknown = true\n\t\t}\n\t\tsetIFCLabel(r, labelFn(isPrivate))\n\t\treturn r\n\t}"}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-C0014","rule_version":"1.2.0","rule_name":"CI/CD Workflow Script Injection","finding":"SECURE","severity":"HIGH","realized_severity":null,"confidence":"medium","rationale":"The registry-releaser workflow interpolates github.ref_type and github.ref_name directly into shell run blocks, but it only triggers on push of 'v*' tags or manual workflow_dispatch, both of which require the actor to already hold repository push/write privileges; no protected-environment boundary or lower-privilege trigger exists that would let a less-privileged actor reach this workflow, and the actor pushing the tag can already supply the checked-out executable code that the same job runs. This does not establish that every repository writer necessarily holds all release or secrets privileges beyond what this specific workflow exposes. Other workflows use the safe env-indirection pattern: issue-labeler.yml and docker-publish.yml assign untrusted values to env vars and reference them as quoted shell variables rather than interpolating them directly, and the license-check workflow's PR-number interpolation is a GitHub-enforced numeric value. No issue_comment or pull_request_target workflow interpolates untrusted event content into a shell run block.","vulnerability_details":null,"fix_guidance":"Defense-in-depth: refactor registry-releaser.yml to assign github.ref_type/github.ref_name to env vars and reference them as quoted shell variables, consistent with the pattern already used elsewhere in the repo.","why_this_matters":"CI/CD script injection can execute attacker-controlled commands with workflow tokens, repository secrets, trusted-runner access, artifact or cache authority, and release or deployment privileges.","context_note":null,"within_declared_contract":null,"transport_gated":false,"mitigation_present":true,"evidence":[{"path":".github/workflows/registry-releaser.yml","start_line":5,"end_line":14,"snippet":"on:\n  push:\n    tags: [\"v*\"]  # Triggers on version tags like v1.0.0\n  workflow_dispatch:  # Allow manual triggering\n...\n      - name: Wait for Docker image\n        run: |\n          if [[ \"${{ github.ref_type }}\" == \"tag\" ]]; then\n            TAG=\"${{ github.ref_name }}\"\n          else"},{"path":".github/workflows/issue-labeler.yml","start_line":1,"end_line":20,"snippet":"on:\n  issues:\n    types: [reopened, opened]\njobs:\n  label_issues:\n    permissions:\n      issues: write\n    steps:\n      - name: Add AI review label to issue\n        run: gh issue edit \"$NUMBER\" --add-label \"$LABELS\"\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          GH_REPO: ${{ github.repository }}\n          NUMBER: ${{ github.event.issue.number }}\n          LABELS: \"request ai review\""},{"path":".github/workflows/registry-releaser.yml","start_line":32,"end_line":39,"snippet":"      - name: Wait for Docker image\n        run: |\n          if [[ \"${{ github.ref_type }}\" == \"tag\" ]]; then\n            TAG=\"${{ github.ref_name }}\"\n          else\n            TAG=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\\.[0-9]+\\.[0-9]+$' | head -n1)\n          fi\n          IMAGE=\"ghcr.io/github/github-mcp-server:$TAG\""},{"path":".github/workflows/issue-labeler.yml","start_line":1,"end_line":19,"snippet":"name: Label issues for AI review\non:\n  issues:\n    types:\n      - reopened\n      - opened\njobs:\n  label_issues:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n    steps:\n      - name: Add AI review label to issue\n        run: gh issue edit \"$NUMBER\" --add-label \"$LABELS\"\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          GH_REPO: ${{ github.repository }}\n          NUMBER: ${{ github.event.issue.number }}\n          LABELS: \"request ai review\""}],"affected_tools":null,"component_name":null},{"rule_id":"ATR-R0001","rule_version":"1.3.0","rule_name":"Weaponization: Malicious-Purpose Artifact (Offensive Capability by Design)","finding":"SECURE","severity":"CRITICAL","realized_severity":null,"confidence":"high","rationale":"This repository is the official github/github-mcp-server: its declared purpose is to let an AI agent interact with GitHub repositories, issues, PRs, CI/CD, and code analysis via the GitHub API. No bundled script, tool description, or instruction body directs generation of malware, exploits, credential theft, or AV/EDR evasion, and no capability evades oversight/attribution. This is unambiguously a defensive/productivity developer tool, not offensive tooling.","vulnerability_details":null,"fix_guidance":null,"why_this_matters":"An artifact whose purpose is to weaponize the agent -- to produce malware, steal credentials, evade defenses, or run attacks at scale -- is dangerous regardless of how securely it is built; posture controls (auth, input validation, least privilege) cannot make a weapon safe, which is why a confirmed weaponization finding caps the trust verdict to `blocked` rather than being averaged against a good posture score. Catching malicious PURPOSE is a distinct safety control, and it must be conservative because offensive capability is dual-use: legitimate pentest, CTF, defensive, and research work must abstain (UNKNOWN), never block -- unless the artifact itself hides from the people who install, run or audit it.","context_note":null,"within_declared_contract":null,"transport_gated":false,"mitigation_present":null,"evidence":[{"path":"README.md","start_line":3,"end_line":13,"snippet":"# GitHub MCP Server\n\nThe GitHub MCP Server connects AI tools directly to GitHub's platform. This gives AI agents, assistants, and chatbots the ability to read repositories and code files, manage issues and PRs, analyze code, and automate workflows. All through natural language interactions.\n\n### Use Cases\n\n- Repository Management: Browse and query code, search files, analyze commits, and understand project structure across any repository you have access to.\n- Issue & PR Automation: Create, update, and manage issues and pull requests. Let AI help triage bugs, review code changes, and maintain project boards.\n- CI/CD & Workflow Intelligence: Monitor GitHub Actions workflow runs, analyze build failures, manage releases, and get insights into your development pipeline.\n- Code Analysis: Examine security findings, review Dependabot alerts, understand code patterns, and get comprehensive insights into your codebase.\n- Team Collaboration: Access discussions, manage notifications, analyze team activity, and streamline processes for your team."}],"affected_tools":null,"component_name":null}],"component_tools":[{"tool_name":"actions_get","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"actions_list","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"actions_run_trigger","tool_description":null,"types":["write","cost-impact","destructive"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"add_comment_to_pending_review","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"add_issue_comment","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"add_issue_comment_reaction","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"add_issue_reaction","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"add_pull_request_review_comment","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"add_pull_request_review_comment_reaction","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"add_reply_to_pull_request_comment","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"assign_copilot_to_issue","tool_description":null,"types":["write","cost-impact"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"assign_copilot_to_issue_with_intent","tool_description":null,"types":["write","cost-impact"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"create_branch","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"create_gist","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"create_issue","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"create_or_update_file","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"create_pull_request","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"create_pull_request_review","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"create_repository","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"create_repository_ruleset","tool_description":null,"types":["admin-config"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"custom_properties_read","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"custom_properties_write","tool_description":null,"types":["admin-config"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"delete_file","tool_description":null,"types":["write","destructive"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"delete_pending_pull_request_review","tool_description":null,"types":["write","destructive"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"delete_repository","tool_description":null,"types":["destructive","admin-config"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"discussion_comment_write","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"dismiss_notification","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"find_duplicate","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"fork_repository","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_code_quality_finding","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_code_scanning_alert","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_commit","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_dependabot_alert","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_discussion","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_discussion_comments","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_file_blame","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_file_contents","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_gist","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_global_security_advisory","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_job_logs","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_label","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_latest_release","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_me","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_notification_details","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_release_by_tag","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_repository_tree","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_secret_scanning_alert","tool_description":null,"types":["read-only","secrets-access"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_tag","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_team_members","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"get_teams","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"issue_dependency_read","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"issue_dependency_write","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"issue_read","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"issue_write","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"label_write","tool_description":null,"types":["write","destructive"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_branches","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_code_scanning_alerts","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_commits","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_dependabot_alerts","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_discussion_categories","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_discussions","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_gists","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_global_security_advisories","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_issue_fields","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_issue_types","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_issues","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_label","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_notifications","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_org_repository_security_advisories","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_pull_requests","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_releases","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_repository_collaborators","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_repository_security_advisories","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_secret_scanning_alerts","tool_description":null,"types":["read-only","secrets-access"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_starred_repositories","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"list_tags","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"manage_notification_subscription","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"manage_repository_notification_subscription","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"mark_all_notifications_read","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"merge_pull_request","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"projects_get","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"projects_list","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"projects_write","tool_description":null,"types":["write","destructive"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"pull_request_read","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"pull_request_review_write","tool_description":null,"types":["write","destructive"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"push_files","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"remove_issue_comment_reaction","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"remove_issue_reaction","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"remove_pull_request_review_comment_reaction","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"repository_ruleset_read","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"request_copilot_review","tool_description":null,"types":["write","cost-impact"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"request_pull_request_reviewers","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"resolve_review_thread","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"search_code","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"search_commits","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"search_issues","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"search_orgs","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"search_pull_requests","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"search_repositories","tool_description":null,"types":["read-only","untrusted-output"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"search_users","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"set_issue_fields","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"star_repository","tool_description":null,"types":["social"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"sub_issue_write","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"submit_pending_pull_request_review","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"ui_get","tool_description":null,"types":["read-only"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"unresolve_review_thread","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"unstar_repository","tool_description":null,"types":["social"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"update_gist","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"update_issue_assignees","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"update_issue_comment","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"update_issue_labels","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"update_issue_state","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"update_issue_type","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"update_pull_request","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"update_pull_request_branch","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"},{"tool_name":"update_pull_request_draft_state","tool_description":null,"types":["write"],"status":"IMPLEMENTED","component_name":"mcp-server"}],"summary":{"high_level_risks":["Direct dependency golang.org/x/net v0.59.0 carries an unpatched HIGH-severity CVE (CVE-2026-78669, HTTP/2 DoS) per Trivy -- fix available in v0.60.0.","Final Docker runtime stage (gcr.io/distroless/base-debian12) has no USER directive and runs as root (confirmed by Trivy DS-0002 and Semgrep dockerfile.security.missing-user); narrower blast radius due to distroless having no shell, but still a hardening gap.","HTTP transport binds to all interfaces by default and sets Access-Control-Allow-Origin:* with no Origin/Host allowlist (ATR-M0003); mitigated in practice by mandatory bearer-token auth, but no defense-in-depth Origin check exists.","Supply-chain hygiene gaps: mutable (non-SHA) action tags in .github/actions/build-ui/action.yml, and missing Dependabot cooldown for gomod/npm/docker ecosystems.","mcpcurl (cmd/mcpcurl) is a developer CLI that intentionally executes operator-supplied shell commands (Semgrep dangerous-exec-command); it is a local test/dev tool, not part of the deployed MCP server's attack surface."],"top_remediations":["Bump golang.org/x/net to >=0.60.0 and re-run go mod tidy to resolve CVE-2026-78669 and related CVEs.","Add a non-root USER directive to the final Docker stage (e.g. gcr.io/distroless/base-debian12:nonroot).","Add an Origin/Host allowlist (or strict same-site check) to the HTTP transport as defense-in-depth alongside the existing bearer-token requirement.","Default --listen-host to 127.0.0.1 for local/dev usage and document that production deployments must sit behind a TLS-terminating proxy.","Pin all GitHub Actions to full commit SHAs and add Dependabot cooldown blocks for gomod/npm/docker ecosystems.","Set explicit Content-Type on all HTTP error responses that echo request-derived strings, and refactor registry-releaser.yml to use env-indirection for github.ref_type/github.ref_name consistent with the rest of the repo."]},"rollup":{"component_findings_count":{"vulnerable":3,"secure":7,"not_applicable":1},"repo_findings_inherited_count":{"vulnerable":6,"secure":9,"not_applicable":0},"component_tools_count":{"read_only":60,"untrusted_output":28,"write":51,"cost_impact":4,"destructive":7,"admin_config":3,"secrets_access":2,"social":2},"worst_case_severity":"HIGH"},"trust_scores":{"repo_level":{"score":33,"tier":"critical","capability_band":"extreme","score_confidence":"medium","score_interval":{"low":9,"high":57},"factors":["ATR-M0004 HIGH","ATR-M0009 HIGH","ATR-C0004 MEDIUM","ATR-C0005 MEDIUM","ATR-C0006 MEDIUM","ATR-C0007 HIGH","ATR-C0012 MEDIUM","ATR-C0013 HIGH"]},"component_level":{"score":38,"tier":"critical","capability_band":"extreme","score_confidence":"medium","score_interval":{"low":9,"high":67},"factors":["ATR-M0004 HIGH","ATR-M0009 HIGH"]}}}]}