Skip to content

Path parameters are interpolated into request URLs without percent-encoding or dot-segment rejection #248

Description

@Har1sh-k

What

Generated *Params classes build their URL by interpolating the path parameter directly, with no percent-encoding and no dot-segment check. Example, src/Anthropic/Models/Beta/Vaults/Credentials/CredentialRetrieveParams.cs:135:

public override Uri Url(ClientOptions options)
{
    var queryString = this.QueryString(options);
    return new UriBuilder(
        options.BaseUrl.ToString().TrimEnd('/')
            + string.Format("/v1/vaults/{0}/credentials/{1}", this.VaultID, this.CredentialID)
    )
    {
        Query = string.IsNullOrEmpty(queryString) ? "beta=true" : ("beta=true&" + queryString),
    }.Uri;
}

System.UriBuilder canonicalises per RFC 3986, so a ../ in the value silently changes which endpoint the request reaches.

106 parameter classes are affected. Note that a same-line search undercounts: rg -l 'string\.Format\("/v1/' returns 91, while rg -l -U 'string\.Format\(\s*"/v1/' returns 106, because 15 classes put the format string on the next line (e.g. MemoryRetrieveParams.cs:158, CertificateRetrieveParams.cs:139). None of the 106 calls any encoder.

Reproduction

var p = new CredentialRetrieveParams {
    VaultID = "vault_A",
    CredentialID = "../../vault_B/credentials/cred_x"
};
// p.Url(options).AbsolutePath
//   actual:   /v1/vaults/vault_B/credentials/cred_x
//   expected: something under /v1/vaults/vault_A/credentials/

Three input forms normalise, which is relevant if an application tries to filter them:

  • ../../vault_B/...
  • %2e%2e/%2e%2e/vault_B/... (survives a literal .. denylist)
  • ..\..\vault_B\... (.NET converts backslashes first)

A # in the value also truncates a fixed suffix, so at a template like /v1/files/{0}/content the request lands on the parent resource instead. Of 64 distinct path templates under src/Anthropic/Models, 42 have a fixed suffix after the placeholder (e.g. /v1/agents/{0}/archive, /v1/deployments/{0}/pause) and 22 end at the placeholder.

Uri.EscapeDataString is already used in this repository (12 occurrences, in Anthropic.Aws/AwsSigner.cs and Anthropic.Bedrock/MantleAwsSigner.cs for SigV4 canonicalisation), just not at these call sites. SecurityHelpers.ValidateProfileName (src/Anthropic/Credentials/SecurityHelpers.cs:41) also already rejects .., /, \ and nulls for credential profile names, so the pattern exists in the codebase.

Suggested change

A shared helper applied at the generated Url() call sites:

  1. Percent-encode each path parameter to the RFC 3986 section 3.3 pchar set, treating / as unsafe.
  2. After interpolation, reject any . or .. segment (literal or %2e-encoded) remaining in the assembled path.

Since these files are generated, this is presumably a template change rather than 106 edits.

Test coverage note

Url() output is asserted in many src/Anthropic.Tests/Models/**/*ParamsTest.cs files, so URL construction is already a tested concern, but every case uses a benign literal such as TunnelID = "tunnel_id". No test exercises ../, #, or %2e in a path parameter.

Verified against da57c281, src/Anthropic 12.42.0, .NET 8 (SDK 8.0.424).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions