{"record":{"id":"3992ef4a207b0b4f","repo":"git-ecosystem/git-credential-manager","slug":"extra-query-parameter-kvp-key-would-override-r","errorCode":null,"errorMessage":"Extra query parameter '{kvp.Key}' would override required standard OAuth parameters.","messagePattern":"Extra query parameter '(.+?)' would override required standard OAuth parameters\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Core/Authentication/OAuth/OAuth2Client.cs","lineNumber":139,"sourceCode":"                [OAuth2Constants.AuthorizationEndpoint.PkceChallengeMethodParameter] =\r\n                    OAuth2Constants.AuthorizationEndpoint.PkceChallengeMethodS256,\r\n                [OAuth2Constants.AuthorizationEndpoint.PkceChallengeParameter] = codeChallenge\r\n            };\r\n\r\n            // Only send the parameter when requesting a non-default mode to keep the request unchanged otherwise.\r\n            if (_responseMode != OAuth2ResponseMode.Default)\r\n            {\r\n                queryParams[OAuth2Constants.AuthorizationEndpoint.ResponseModeParameter] =\r\n                    _responseMode.GetParameterValue();\r\n            }\r\n\r\n            if (extraQueryParams?.Count > 0)\r\n            {\r\n                foreach (var kvp in extraQueryParams)\r\n                {\r\n                    if (queryParams.ContainsKey(kvp.Key))\r\n                    {\r\n                        throw new ArgumentException(\r\n                            $\"Extra query parameter '{kvp.Key}' would override required standard OAuth parameters.\",\r\n                            nameof(extraQueryParams));\r\n                    }\r\n\r\n                    queryParams[kvp.Key] = kvp.Value;\r\n                }\r\n            }\r\n\r\n            Uri redirectUri = null;\r\n            if (_redirectUri != null)\r\n            {\r\n                redirectUri = browser.UpdateRedirectUri(_redirectUri);\r\n\r\n                // We must use the .OriginalString property here over .ToString() because OAuth requires the redirect\r\n                // URLs to be compared exactly, respecting missing/present trailing slashes, byte-for-byte.\r\n                queryParams[OAuth2Constants.RedirectUriParameter] = redirectUri.OriginalString;\r\n            }\r\n\r","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/git-ecosystem/git-credential-manager/blob/e8ce762cd04b4100ae637b5fbf39ef9d0a96561e/src/Core/Authentication/OAuth/OAuth2Client.cs#L121-L157","documentation":"In OAuth2Client.GetAuthorizationCodeAsync, any extra query parameters supplied by the caller are merged into the standard OAuth2 authorization request parameters. If an extra parameter's key collides with a required standard parameter (e.g. client_id, redirect_uri, response_type, scope, state, code_challenge), merging would silently corrupt the flow, so the library throws ArgumentException instead.","triggerScenarios":"Calling GetAuthorizationCodeAsync with an extraQueryParams dictionary containing a key already present in the computed standard query parameters — for example passing 'redirect_uri', 'state', 'client_id', or 'scope' as an extra parameter.","commonSituations":"Configuration files that let users append arbitrary OAuth query parameters which accidentally include standard names; copying a full authorization URL's query string into extraQueryParams; SDK or wrapper code forwarding all options blindly.","solutions":["Remove keys from extraQueryParams that collide with standard OAuth2 authorization parameters.","Only pass truly custom/extension parameters (e.g. 'prompt', 'domain_hint', tenant-specific extras) in extraQueryParams.","Pre-filter the dictionary: drop any key that duplicates client_id, redirect_uri, response_type, scope, state, code_challenge, code_challenge_method before the call.","Check config that feeds extraQueryParams for full copied URLs and strip their standard query strings."],"exampleFix":"// before\nawait client.GetAuthorizationCodeAsync(endpoints, clientId, redirectUri, scopes, state, codeChallenge,\n    new Dictionary<string,string> { [\"state\"] = \"my-state\" }); // throws\n// after\nvar extra = new Dictionary<string,string> { [\"domain_hint\"] = \"contoso.com\" }; // no standard keys\nawait client.GetAuthorizationCodeAsync(endpoints, clientId, redirectUri, scopes, state, codeChallenge, extra);","handlingStrategy":"validation","validationCode":"static readonly HashSet<string> StandardParams = new(StringComparer.Ordinal)\n    { \"client_id\", \"redirect_uri\", \"response_type\", \"scope\", \"state\", \"code_challenge\", \"code_challenge_method\" };\nbool conflicts = extra?.Keys.Any(StandardParams.Contains) == true;\nif (conflicts) throw new ArgumentException(\"extraQueryParams may not override standard OAuth parameters.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    result = await client.GetAuthorizationCodeAsync(endpoints, clientId, redirectUri, scopes, state, verifier, extra);\n}\ncatch (ArgumentException ex) when (ex.ParamName == \"extraQueryParams\")\n{\n    // strip conflicting keys and retry once\n    var safe = extra.Where(kvp => !StandardParams.Contains(kvp.Key)).ToDictionary(k => k.Key, v => v.Value);\n    result = await client.GetAuthorizationCodeAsync(endpoints, clientId, redirectUri, scopes, state, verifier, safe);\n}","preventionTips":["Whitelist only known-safe extension parameters in configuration.","Never paste a full authorization URL's query string into extraQueryParams.","Keep standard parameters as explicit method arguments, never extras.","Add a unit test asserting no standard key appears in extras."],"tags":["oauth2","query-parameters","argument-conflict","authorization-code"],"backgroundTag":"conflicting-config-options","analyzedSha":"e8ce762cd04b4100ae637b5fbf39ef9d0a96561e","analyzedAt":"2026-09-11T17:15:08.753Z","contentChangedAt":"2026-09-11T17:15:08.753Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}