Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 12, 2026

User description

OAuth1 signature calculation was double-encoding special characters (!, *, ', (, )) in URL paths, causing 401 authentication failures. When URL segments containing these characters were added via AddUrlSegment(), they were encoded by Uri.EscapeDataString() during URL construction, then encoded again during signature base generation.

Changes

  • OAuthTools.ConstructRequestUrl: Decode url.AbsolutePath with Uri.UnescapeDataString() before returning. This prevents double-encoding since UrlEncodeRelaxed will encode the decoded path once correctly.

  • Tests: Added coverage for RFC 3986 special characters in both direct paths and URL segment parameters to verify single encoding.

Example

var client = new RestClient("https://api.example.com");
var request = new RestRequest("resource/{id}", Method.Get);
request.AddUrlSegment("id", "item!123");  // Previously: "item%2521123" in signature (double-encoded)
                                          // Now: "item%21123" in signature (correct)

var authenticator = OAuth1Authenticator.ForProtectedResource(
    consumerKey, consumerSecret, accessToken, accessTokenSecret
);

The fix applies to all RFC 3986 special characters: !, *, ', (, ).

Original prompt

This section details on the original issue you should resolve

<issue_title>OAuth1 - Path Segments with Special Char "!" - Results in Auth Error</issue_title>
<issue_description>A clear and concise description of what the bug is.

When using the reserved character '!', in a path segment, it returns a 401 unauthorized error.
Example:
https://.suitetalk.api.netsuite.com/services/rest/record/v1/purchaseOrder//**!**transform/vendorBill

Note: this is similar to Issue#2126. However, in that case, they were using special characters in the query parameters. However, here we are using reserved characters in the URL path segments.

To Reproduce
Note that when using postman, this end point works: https://.suitetalk.api.netsuite.com/services/rest/record/v1/purchaseOrder/30356/**!**transform/vendorBill

In C#, I am using the RestSharp library to handle authentication.
Note that we are already successfully using RestSharp to call many different NetSuite endpoints using OAuth1 authentication.
However, for this particular end point, we believe the "!" in the URL may not be handled properly.
This seems to be the only difference in this particular case.

This is the code to reproduce the issue:

private static RestRequest GenerateRestRequest(OAuthCredentials oAuthCredentials, string urlPathSegments, Method requestMethodType)
{
    var authenticator = OAuth1Authenticator.ForAccessToken(
        oAuthCredentials.ConsumerKey,
        oAuthCredentials.ConsumerSecret,
        oAuthCredentials.TokenID,
        oAuthCredentials.TokenSecret,
        OAuthSignatureMethod.HmacSha256
        );

    authenticator.Realm = oAuthCredentials.AccountID;

    RestRequest request = new RestRequest(Url.Combine(oAuthCredentials.CompanyURL, urlPathSegments), requestMethodType)
    {
        Authenticator = authenticator
    };

    return request;
}

public static async Task<RestResponse> NetSuiteTransformPOToVendorBill(this RestClient restClient, OAuthCredentials oAuthCredentials, NetSuiteVendorBill netSuitePOToVendorBillTransform, string NetSuitePOInternalID)
{
    string segmentStr = "{segment}";
    RestRequest request = GenerateRestRequest(oAuthCredentials, $"services/rest/record/v1/purchaseOrder/{NetSuitePOInternalID}/{segmentStr}/vendorBill", Method.Post);

    // I believe the "!" mark in this segment may not be handled correctly when generating the nonce or signature
    request.AddUrlSegment("segment", "!transform", false); 
    // Note: I have tried setting encoding to "true" as well
    // request.AddUrlSegment("segment", "!transform", true);

    request.AddHeader("Content-Type", "application/json");
    request.AddHeader("prefer", "transient");
    string serializedNetSuiteVendorBill = JsonConvert.SerializeObject(netSuitePOToVendorBillTransform);

    request.AddJsonBody(serializedNetSuiteVendorBill);

    return await restClient.ExecuteAsync(request);
}

Expected behavior
Returns Status: 204 No Content in Postman

Stack trace
error="token_rejected",
error_description="Invalid login attempt."
"title":"Unauthorized"
"status":401

Desktop (please complete the following information):

  • OS: Windows 11 Pro
  • .NET version .NET 6.0
  • Version RestSharp 110.2.0

Additional context

  • It works through postman
  • A NetSuite consultant we work has custom code that can hit this end point successfully (i.e. they don't use any open source libraries)
    </issue_description>

Comments on the Issue (you are @copilot in this section)

@alexeyzimarev There was a change released in v111 to handle special characters differently. However, the test that checks the encoding according to RFC 3986 is failing now.

It would be good to add expected and actual signature strings in OAuth1 issues, otherwise it turns to a guess game.</comment_new>
<comment_new>@alexeyzimarev
You don't have to provide real credentials. If you look at the tests I added, I used fixed values from Twitter OAuth1 docs that specifies values for everything, then it shows how the signature base will look like, and what the resulting signature will be. That page in their docs allowed me to create a reliable set of tests to verify that the code generates the correct signature base, and the actual signature is correct.

Could you please write a comment if there's any remaining issue now with the latest version? If yes, it'd be nice to have a repro code. </comment_new>


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.


PR Type

Bug fix


Description

  • Fix OAuth1 double-encoding of RFC 3986 special characters in URL paths

  • Decode path before signature calculation to prevent double-encoding

  • Add comprehensive test coverage for special characters in paths

  • Ensure single encoding of characters like !, *, ', (, )


Diagram Walkthrough

flowchart LR
  A["URL with encoded path<br/>e.g., value%21"] -->|Uri.UnescapeDataString| B["Decoded path<br/>e.g., value!"]
  B -->|UrlEncodeRelaxed| C["Single-encoded path<br/>e.g., value%21"]
  C -->|Signature calculation| D["Correct OAuth1 signature"]
  E["Before: Double-encoded<br/>value%2521"] -.->|Issue| F["401 Unauthorized"]
Loading

File Walkthrough

Relevant files
Bug fix
OAuthTools.cs
Decode path to prevent double-encoding in OAuth1                 

src/RestSharp/Authenticators/OAuth/OAuthTools.cs

  • Decode url.AbsolutePath using Uri.UnescapeDataString() in
    ConstructRequestUrl method
  • Prevents double-encoding when path contains already-encoded special
    characters
  • Added detailed security comment explaining why decoding is safe
  • Decoded path is immediately re-encoded by UrlEncodeRelaxed before use
+13/-1   
Tests
OAuth1SignatureTests.cs
Add tests for RFC 3986 special characters in paths             

test/RestSharp.Tests/Auth/OAuth1SignatureTests.cs

  • Added test for exclamation mark in path segments
  • Added parameterized test for all RFC 3986 special characters (!, *, ',
    (, ))
  • Added test for URL segments with special characters to verify no
    double-encoding
  • Tests verify proper single encoding in signature base and absence of
    %25 (double-encoded %)
+71/-0   

Co-authored-by: alexeyzimarev <2821205+alexeyzimarev@users.noreply.github.com>
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jan 12, 2026

Deploying restsharp with  Cloudflare Pages  Cloudflare Pages

Latest commit: fa6c0d4
Status: ✅  Deploy successful!
Preview URL: https://03763a0e.restsharp.pages.dev
Branch Preview URL: https://copilot-fix-oauth1-auth-erro.restsharp.pages.dev

View logs

Co-authored-by: alexeyzimarev <2821205+alexeyzimarev@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix OAuth1 auth error with path segments containing special character Fix OAuth1 double-encoding of RFC 3986 special characters in URL paths Jan 12, 2026
Copilot AI requested a review from alexeyzimarev January 12, 2026 15:18
@alexeyzimarev alexeyzimarev marked this pull request as ready for review January 23, 2026 11:42
@qodo-free-for-open-source-projects
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Unvalidated URL decoding

Description: Using Uri.UnescapeDataString() on url.AbsolutePath without validation could potentially
allow path traversal or injection attacks if the URL is constructed from untrusted
sources, as it decodes all percent-encoded sequences including directory traversal
sequences like %2e%2e%2f (../) OAuthTools.cs [167-167]

Referred Code
var decodedPath = Uri.UnescapeDataString(url.AbsolutePath);
Ticket Compliance
🟡
🎫 #2126
🔴 OAuth 1.0 authentication should work correctly with special characters like é, è, ç in
parameter values
Using AddParameter or AddQueryParameter with special characters should not result in 401
Unauthorized errors
The request should return the desired search result when special characters are used in
filter parameters
Codebase Duplication Compliance
🟢
No codebase code duplication found New Components Detected:
- Handles_path_with_exclamation_mark
- Encodes_RFC3986_special_chars_in_path
- Handles_url_segment_with_RFC3986_special_chars
Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-free-for-open-source-projects
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Security
Prevent potential path traversal issues

To prevent potential path traversal vulnerabilities, avoid using
Uri.UnescapeDataString on the entire path. Instead, split the path into
segments, unescape each segment individually, and then rejoin them.

src/RestSharp/Authenticators/OAuth/OAuthTools.cs [166-169]

 // - There is no direct user input involved in this internal OAuth signature calculation
-var decodedPath = Uri.UnescapeDataString(url.AbsolutePath);
+var decodedPath = string.Join("/", url.AbsolutePath.Split('/').Select(Uri.UnescapeDataString));
 
 return $"{url.Scheme}://{url.Host}{port}{decodedPath}";
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical path traversal vulnerability introduced by using Uri.UnescapeDataString on the entire url.AbsolutePath, which could unescape encoded path separators like %2F.

High
  • More

@alexeyzimarev alexeyzimarev merged commit 13b0425 into dev Jan 23, 2026
10 checks passed
@alexeyzimarev alexeyzimarev deleted the copilot/fix-oauth1-auth-error branch January 23, 2026 11:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OAuth1 - Path Segments with Special Char "!" - Results in Auth Error

2 participants