{"record":{"id":"c40f9a8090040649","repo":"siyuan-note/siyuan","slug":"oauth-revocation-endpoint-must-use-https-or-loopba","errorCode":null,"errorMessage":"OAuth revocation endpoint must use HTTPS or loopback HTTP","messagePattern":"OAuth revocation endpoint must use HTTPS or loopback HTTP","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/client/oauth.go","lineNumber":741,"sourceCode":"\t\t\tfor _, credential := range credentials {\n\t\t\t\tif err := revokeOAuthCredential(ctx, client, credential); err != nil {\n\t\t\t\t\trevokeErr = errors.Join(revokeErr, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif revokeErr != nil {\n\t\t\t\tlogging.LogWarnf(\"mcp oauth: revoke credentials for server [%s] failed: %s\", serverID, revokeErr)\n\t\t\t}\n\t\t}()\n\t}\n\treturn nil\n}\n\nfunc revokeOAuthCredential(ctx context.Context, client *http.Client, credential oauthCredential) error {\n\tif credential.RevocationEndpoint == \"\" {\n\t\treturn nil\n\t}\n\tif !isSecureOAuthEndpoint(credential.RevocationEndpoint) {\n\t\treturn fmt.Errorf(\"OAuth revocation endpoint must use HTTPS or loopback HTTP\")\n\t}\n\tvar result error\n\tfor _, token := range []struct {\n\t\tvalue string\n\t\thint  string\n\t}{{credential.RefreshToken, \"refresh_token\"}, {credential.AccessToken, \"access_token\"}} {\n\t\tif token.value == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues := url.Values{\"token\": {token.value}, \"token_type_hint\": {token.hint}}\n\t\tapplyOAuthClientAuthentication(values, nil, credential)\n\t\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, credential.RevocationEndpoint, strings.NewReader(values.Encode()))\n\t\tif err != nil {\n\t\t\tresult = errors.Join(result, err)\n\t\t\tcontinue\n\t\t}\n\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\tapplyOAuthClientAuthentication(nil, req, credential)","sourceCodeStart":723,"sourceCodeEnd":759,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/mcp/client/oauth.go#L723-L759","documentation":"revokeOAuthCredential refuses to send OAuth token revocation requests to an endpoint that is neither HTTPS nor loopback HTTP. This guards refresh/access tokens from being leaked over unencrypted or non-local transport when an MCP client's OAuth server metadata supplies a weak revocation endpoint.","triggerScenarios":"An oauthCredential whose RevocationEndpoint URL uses plain http:// on a non-loopback host (or a scheme like ws://) is passed to revokeOAuthCredential via the OAuth credential cleanup path of the MCP client.","commonSituations":"A self-hosted OAuth provider behind plain HTTP on a LAN address; hand-edited or mocked server metadata with http:// URLs; a dev/test issuer accidentally configured in production.","solutions":["Change the revocation endpoint to an https:// URL in the OAuth server metadata or credential configuration","If testing locally, serve revocation on 127.0.0.1/localhost over plain HTTP, which is allowed","Verify the credential's RevocationEndpoint field is populated from trusted, current discovery metadata rather than stale config"],"exampleFix":"// before\ncredential.RevocationEndpoint = \"http://internal-idp.local/revoke\"\n// after\ncredential.RevocationEndpoint = \"https://internal-idp.local/revoke\"","handlingStrategy":"validation","validationCode":"func isRevocationEndpointSafe(raw string) bool {\n    u, err := url.Parse(raw)\n    if err != nil || u.Host == \"\" {\n        return false\n    }\n    if u.Scheme == \"https\" {\n        return true\n    }\n    host := u.Hostname()\n    return u.Scheme == \"http\" && (host == \"127.0.0.1\" || host == \"localhost\" || host == \"::1\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always provision OAuth metadata over HTTPS and audit http:// endpoints in configuration","Only exempt loopback hosts for local development","Keep discovery metadata fresh rather than hardcoding endpoint URLs"],"tags":["oauth","security","https"],"backgroundTag":"invalid-url","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}