{"record":{"id":"6b5b7e46126f362a","repo":"googleapis/mcp-toolbox","slug":"failed-to-decode-google-tokeninfo-response-w","errorCode":null,"errorMessage":"failed to decode Google tokeninfo response: %w","messagePattern":"failed to decode Google tokeninfo response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/google/google.go","lineNumber":214,"sourceCode":"\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, &auth.MCPAuthError{Code: http.StatusUnauthorized, Message: fmt.Sprintf(\"Google token validation failed with status: %d\", resp.StatusCode), ScopesRequired: a.ScopesRequired}\n\t}\n\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read Google tokeninfo response: %w\", err)\n\t}\n\n\tvar tokenInfo struct {\n\t\tAud   string `json:\"aud\"`\n\t\tAzp   string `json:\"azp\"`\n\t\tScope string `json:\"scope\"`\n\t}\n\tif err := json.Unmarshal(body, &tokenInfo); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode Google tokeninfo response: %w\", err)\n\t}\n\n\taud := tokenInfo.Aud\n\tif aud == \"\" {\n\t\taud = tokenInfo.Azp\n\t}\n\n\taudLimit := a.Audience\n\tif audLimit == \"\" {\n\t\taudLimit = a.ClientID\n\t}\n\n\tif audLimit != \"\" && aud != audLimit {\n\t\treturn nil, &auth.MCPAuthError{Code: http.StatusUnauthorized, Message: \"audience validation failed\", ScopesRequired: a.ScopesRequired}\n\t}\n\n\tif len(a.ScopesRequired) > 0 {\n\t\ttokenScopes := strings.Fields(tokenInfo.Scope)","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/auth/google/google.go#L196-L232","documentation":"ValidateMCPAuth unmarshals the tokeninfo response body into a struct with aud/azp/scope fields. If the body is not valid JSON (empty body, HTML error page from a proxy, truncated response), json.Unmarshal fails and the error is wrapped with this message.","triggerScenarios":"Google returns a 200 with non-JSON or empty body; an intercepting proxy or captive portal injects HTML; response was truncated at the 1 MiB limit mid-JSON.","commonSituations":"Corporate proxies returning login pages with 200 status; rare Google-side malformed responses; misconfigured service mesh altering bodies.","solutions":["Log/inspect the raw response body to see what was actually returned","Bypass or fix the intercepting proxy so oauth2.googleapis.com responses arrive unmodified","Retry; if persistent, check Google tokeninfo endpoint status","Capture the wrapped inner error for the exact JSON parse position"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"claims, err := svc.ValidateMCPAuth(ctx, h)\nif err != nil && strings.Contains(err.Error(), \"failed to decode Google tokeninfo response\") {\n    // likely proxy/HTML injection; do not retry blindly — log body and fail auth\n    return fmt.Errorf(\"tokeninfo returned non-JSON: %w\", err)\n}","preventionTips":["Disable captive-portal/HTML injection on the egress path for API hosts","Use a dedicated HTTP client with TLS verification; avoid mutating proxies","Check Content-Type of upstream responses in gateway logs","Monitor for proxy software injecting error pages with 200 status"],"tags":["go","auth","google","json","network"],"backgroundTag":"json-decode-failed","analyzedSha":"8cc6e09de2ad7b8bffc77751799585a1401a48eb","analyzedAt":"2026-09-05T01:10:36.887Z","contentChangedAt":"2026-09-05T01:10:36.887Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}