CVE-2024-32028 (published 2024-04-12T23:15Z)

OpenTelemetry dotnet is a dotnet telemetry framework. In affected versions of `OpenTelemetry.Instrumentation.Http` and `OpenTelemetry.Instrumentation.AspNetCore` the `url.full` writes attribute/tag on spans (`Activity`) when tracing is enabled for outgoing http requests and `OpenTelemetry.Instrumentation.AspNetCore` writes the `url.query` attribute/tag on spans (`Activity`) when tracing is enabled for incoming http requests. These attributes are defined by the Semantic Conventions for HTTP Spans. Up until version `1.8.1` the values written by `OpenTelemetry.Instrumentation.Http` & `OpenTelemetry.Instrumentation.AspNetCore` will pass-through the raw query string as was sent or received (respectively). This may lead to sensitive information (e.g. EUII - End User Identifiable Information, credentials, etc.) being leaked into telemetry backends (depending on the application(s) being instrumented) which could cause privacy and/or security incidents. Note: Older versions of `OpenTelemetry.Instrumentation.Http` & `OpenTelemetry.Instrumentation.AspNetCore` may use different tag names but have the same vulnerability. The `1.8.1` versions of `OpenTelemetry.Instrumentation.Http` & `OpenTelemetry.Instrumentation.AspNetCore` will now redact by default all values detected on transmitted or received query strings. Users are advised to upgrade. There are no known workarounds for this vulnerability.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/open-telemetry/opentelemetry-dotnet
commit_details: repo_url: https://github.com/open-telemetry/opentelemetry-dotnet
author: Vishwesh Bankwar
author_date: 2024-04-12 11:47:42-07:00
msg: [Instrumentation.Http][Instrumentation.AspNetCore] Fix `url.full` and `url.query` attribute values (#5532)
repository: repo_url: https://github.com/open-telemetry/opentelemetry-dotnet
repo_name: open-telemetry/opentelemetry-dotnet
description: The OpenTelemetry .NET Client
repo_language: C#
owner: open-telemetry
file_changes: filename: OpenTelemetry.sln
change_type: ModificationType.MODIFY
diff: @@ -270,6 +270,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A49299
 		src\Shared\PeerServiceResolver.cs = src\Shared\PeerServiceResolver.cs
 		src\Shared\PeriodicExportingMetricReaderHelper.cs = src\Shared\PeriodicExportingMetricReaderHelper.cs
 		src\Shared\PooledList.cs = src\Shared\PooledList.cs
+		src\Shared\RedactionHelper.cs = src\Shared\RedactionHelper.cs
 		src\Shared\RequestMethodHelper.cs = src\Shared\RequestMethodHelper.cs
 		src\Shared\ResourceSemanticConventions.cs = src\Shared\ResourceSemanticConventions.cs
 		src\Shared\SemanticConventions.cs = src\Shared\SemanticConventions.cs

programming_language: PowerShell
filename: AspNetCoreTraceInstrumentationOptions.cs
change_type: ModificationType.MODIFY
diff: @@ -32,6 +32,14 @@ public class AspNetCoreTraceInstrumentationOptions
         {
             this.EnableGrpcAspNetCoreSupport = enableGrpcInstrumentation;
         }
+
+        if (configuration.TryGetBoolValue(
+            AspNetCoreInstrumentationEventSource.Log,
+            "OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_DISABLE_URL_QUERY_REDACTION",
+            out var disableUrlQueryRedaction))
+        {
+            this.DisableUrlQueryRedaction = disableUrlQueryRedaction;
+        }
     }
 
     /// <summary>
@@ -94,4 +102,14 @@ public class AspNetCoreTraceInstrumentationOptions
     /// https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md.
     /// </remarks>
     internal bool EnableGrpcAspNetCoreSupport { get; set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether the url query value should be redacted or not.
+    /// </summary>
+    /// <remarks>
+    /// The query parameter values are redacted with value set as Redacted.
+    /// e.g. `?key1=value1` is set as `?key1=Redacted`.
+    /// The redaction can be disabled by setting this property to <see langword="true" />.
+    /// </remarks>
+    internal bool DisableUrlQueryRedaction { get; set; }
 }

programming_language: C#
filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,16 @@
 # Changelog
 
-## Unreleased
+## 1.8.1
+
+Released 2024-Apr-12
+
+* **Breaking Change**: Fixed tracing instrumentation so that by default any
+  values detected in the query string component of requests are replaced with
+  the text `Redacted` when building the `url.query` tag. For example,
+  `?key1=value1&key2=value2` becomes `?key1=Redacted&key2=Redacted`. You can
+  disable this redaction by setting the environment variable
+  `OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_DISABLE_URL_QUERY_REDACTION` to `true`.
+  ([#5532](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5532))
 
 ## 1.8.0
 

programming_language: Markdown
filename: HttpInListener.cs
change_type: ModificationType.MODIFY
diff: @@ -193,8 +193,14 @@ internal class HttpInListener : ListenerHandler
 
             if (request.QueryString.HasValue)
             {
-                // QueryString should be sanitized. see: https://github.com/open-telemetry/opentelemetry-dotnet/issues/4571
-                activity.SetTag(SemanticConventions.AttributeUrlQuery, request.QueryString.Value);
+                if (this.options.DisableUrlQueryRedaction)
+                {
+                    activity.SetTag(SemanticConventions.AttributeUrlQuery, request.QueryString.Value);
+                }
+                else
+                {
+                    activity.SetTag(SemanticConventions.AttributeUrlQuery, RedactionHelper.GetRedactedQueryString(request.QueryString.Value));
+                }
             }
 
             RequestMethodHelper.SetHttpMethodTag(activity, request.Method);

programming_language: C#
filename: OpenTelemetry.Instrumentation.AspNetCore.csproj
change_type: ModificationType.MODIFY
diff: @@ -17,6 +17,7 @@
     <Compile Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.GrpcNetClient\GrpcTagHelper.cs" Link="Includes\GrpcTagHelper.cs" />
     <Compile Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.GrpcNetClient\StatusCanonicalCode.cs" Link="Includes\StatusCanonicalCode.cs" />
     <Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
+    <Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
     <Compile Include="$(RepoRoot)\src\Shared\RequestMethodHelper.cs" Link="Includes\RequestMethodHelper.cs" />
     <Compile Include="$(RepoRoot)\src\Shared\Shims\NullableAttributes.cs" Link="Includes\Shims\NullableAttributes.cs" />
     <Compile Include="$(RepoRoot)\src\Shared\Options\*.cs" Link="Includes\Options\%(Filename).cs" />

programming_language: PowerShell
filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -75,7 +75,11 @@ for more details about each individual attribute:
 * `server.address`
 * `server.port`
 * `url.path`
-* `url.query`
+* `url.query` - By default, the values in the query component are replaced with
+  the text `Redacted`. For example, `?key1=value1&key2=value2` becomes
+  `?key1=Redacted&key2=Redacted`. You can disable this redaction by setting the
+  environment variable
+  `OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_DISABLE_URL_QUERY_REDACTION` to `true`.
 * `url.scheme`
 
 [Enrich Api](#enrich) can be used if any additional attributes are

programming_language: Markdown
filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,16 @@
 # Changelog
 
-## Unreleased
+## 1.8.1
+
+Released 2024-Apr-12
+
+* **Breaking Change**: Fixed tracing instrumentation so that by default any
+  values detected in the query string component of requests are replaced with
+  the text `Redacted` when building the `url.full` tag. For example,
+  `?key1=value1&key2=value2` becomes `?key1=Redacted&key2=Redacted`. You can
+  disable this redaction by setting the environment variable
+  `OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION` to `true`.
+  ([#5532](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5532))
 
 ## 1.8.0
 

programming_language: Markdown
filename: HttpClientInstrumentationTracerProviderBuilderExtensions.cs
change_type: ModificationType.MODIFY
diff: @@ -59,6 +59,8 @@ public static class HttpClientInstrumentationTracerProviderBuilderExtensions
             {
                 services.Configure(name, configureHttpClientTraceInstrumentationOptions);
             }
+
+            services.RegisterOptionsFactory(configuration => new HttpClientTraceInstrumentationOptions(configuration));
         });
 
 #if NETFRAMEWORK

programming_language: C#
filename: HttpClientTraceInstrumentationOptions.cs
change_type: ModificationType.MODIFY
diff: @@ -3,10 +3,11 @@
 
 using System.Diagnostics;
 using System.Net;
+using System.Runtime.CompilerServices;
 #if NETFRAMEWORK
 using System.Net.Http;
 #endif
-using System.Runtime.CompilerServices;
+using Microsoft.Extensions.Configuration;
 using OpenTelemetry.Instrumentation.Http.Implementation;
 
 namespace OpenTelemetry.Instrumentation.Http;
@@ -16,6 +17,27 @@ namespace OpenTelemetry.Instrumentation.Http;
 /// </summary>
 public class HttpClientTraceInstrumentationOptions
 {
+    /// <summary>
+    /// Initializes a new instance of the <see cref="HttpClientTraceInstrumentationOptions"/> class.
+    /// </summary>
+    public HttpClientTraceInstrumentationOptions()
+        : this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
+    {
+    }
+
+    internal HttpClientTraceInstrumentationOptions(IConfiguration configuration)
+    {
+        Debug.Assert(configuration != null, "configuration was null");
+
+        if (configuration.TryGetBoolValue(
+           HttpInstrumentationEventSource.Log,
+           "OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION",
+           out var disableUrlQueryRedaction))
+        {
+            this.DisableUrlQueryRedaction = disableUrlQueryRedaction;
+        }
+    }
+
     /// <summary>
     /// Gets or sets a filter function that determines whether or not to
     /// collect telemetry on a per request basis.
@@ -125,6 +147,16 @@ public class HttpClientTraceInstrumentationOptions
     /// </remarks>
     public bool RecordException { get; set; }
 
+    /// <summary>
+    /// Gets or sets a value indicating whether the url query value should be redacted or not.
+    /// </summary>
+    /// <remarks>
+    /// The query parameter values are redacted with value set as Redacted.
+    /// e.g. `?key1=value1` is set as `?key1=Redacted`.
+    /// The redaction can be disabled by setting this property to <see langword="true" />.
+    /// </remarks>
+    internal bool DisableUrlQueryRedaction { get; set; }
+
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     internal bool EventFilterHttpRequestMessage(string activityName, object arg1)
     {

programming_language: C#
filename: HttpHandlerDiagnosticListener.cs
change_type: ModificationType.MODIFY
diff: @@ -149,7 +149,7 @@ internal sealed class HttpHandlerDiagnosticListener : ListenerHandler
             activity.SetTag(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
             activity.SetTag(SemanticConventions.AttributeServerPort, request.RequestUri.Port);
 
-            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
+            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri, this.options.DisableUrlQueryRedaction));
 
             try
             {

programming_language: C#
filename: HttpInstrumentationEventSource.cs
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 using System.Diagnostics.Tracing;
+using Microsoft.Extensions.Configuration;
 using OpenTelemetry.Internal;
 
 namespace OpenTelemetry.Instrumentation.Http.Implementation;
@@ -10,7 +11,7 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation;
 /// EventSource events emitted from the project.
 /// </summary>
 [EventSource(Name = "OpenTelemetry-Instrumentation-Http")]
-internal sealed class HttpInstrumentationEventSource : EventSource
+internal sealed class HttpInstrumentationEventSource : EventSource, IConfigurationExtensionsLogger
 {
     public static HttpInstrumentationEventSource Log = new();
 
@@ -100,4 +101,15 @@ internal sealed class HttpInstrumentationEventSource : EventSource
     {
         this.WriteEvent(7, handlerName, eventName, ex);
     }
+
+    [Event(8, Message = "Configuration key '{0}' has an invalid value: '{1}'", Level = EventLevel.Warning)]
+    public void InvalidConfigurationValue(string key, string value)
+    {
+        this.WriteEvent(8, key, value);
+    }
+
+    void IConfigurationExtensionsLogger.LogInvalidConfigurationValue(string key, string value)
+    {
+        this.InvalidConfigurationValue(key, value);
+    }
 }

programming_language: C#
filename: HttpTagHelper.cs
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,8 @@
 // Copyright The OpenTelemetry Authors
 // SPDX-License-Identifier: Apache-2.0
 
+using OpenTelemetry.Internal;
+
 namespace OpenTelemetry.Instrumentation.Http.Implementation;
 
 /// <summary>
@@ -12,15 +14,18 @@ internal static class HttpTagHelper
     /// Gets the OpenTelemetry standard uri tag value for a span based on its request <see cref="Uri"/>.
     /// </summary>
     /// <param name="uri"><see cref="Uri"/>.</param>
+    /// <param name="disableQueryRedaction">Indicates whether query parameter should be redacted or not.</param>
     /// <returns>Span uri value.</returns>
-    public static string GetUriTagValueFromRequestUri(Uri uri)
+    public static string GetUriTagValueFromRequestUri(Uri uri, bool disableQueryRedaction)
     {
-        if (string.IsNullOrEmpty(uri.UserInfo))
+        if (string.IsNullOrEmpty(uri.UserInfo) && disableQueryRedaction)
         {
             return uri.OriginalString;
         }
 
-        return string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment);
+        var query = disableQueryRedaction ? uri.Query : RedactionHelper.GetRedactedQueryString(uri.Query);
+
+        return string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.AbsolutePath, query, uri.Fragment);
     }
 
     public static string GetProtocolVersionString(Version httpVersion) => (httpVersion.Major, httpVersion.Minor) switch

programming_language: C#
filename: HttpWebRequestActivitySource.netfx.cs
change_type: ModificationType.MODIFY
diff: @@ -105,7 +105,7 @@ internal static class HttpWebRequestActivitySource
             activity.SetTag(SemanticConventions.AttributeServerAddress, request.RequestUri.Host);
             activity.SetTag(SemanticConventions.AttributeServerPort, request.RequestUri.Port);
 
-            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
+            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri, tracingOptions.DisableUrlQueryRedaction));
 
             try
             {

programming_language: C#
filename: OpenTelemetry.Instrumentation.Http.csproj
change_type: ModificationType.MODIFY
diff: @@ -12,8 +12,12 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <Compile Include="$(RepoRoot)\src\Shared\Configuration\*.cs" Link="Includes\Configuration\%(Filename).cs" />
+    <Compile Include="$(RepoRoot)\src\Shared\EnvironmentVariables\*.cs" Link="Includes\EnvironmentVariables\%(Filename).cs" />
     <Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
     <Compile Include="$(RepoRoot)\src\Shared\Shims\NullableAttributes.cs" Link="Includes\Shims\NullableAttributes.cs" />
+    <Compile Include="$(RepoRoot)\src\Shared\Options\*.cs" Link="Includes\Options\%(Filename).cs" />
+    <Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
     <Compile Include="$(RepoRoot)\src\Shared\RequestMethodHelper.cs" Link="Includes\RequestMethodHelper.cs" />
   </ItemGroup>
 
@@ -29,6 +33,7 @@
   <ItemGroup>
     <Reference Include="System.Net.Http" Condition="'$(TargetFramework)' == '$(NetFrameworkMinimumSupportedVersion)'" />
     <PackageReference Include="Microsoft.Extensions.Options" />
+    <PackageReference Include="Microsoft.Extensions.Configuration" />
   </ItemGroup>
 
 </Project>

programming_language: PowerShell
filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -68,7 +68,11 @@ for more details about each individual attribute:
 * `network.protocol.version`
 * `server.address`
 * `server.port`
-* `url.full`
+* `url.full` - By default, the values in the query component of the url are
+  replaced with the text `Redacted`. For example, `?key1=value1&key2=value2`
+  becomes `?key1=Redacted&key2=Redacted`. You can disable this redaction by
+  setting the environment variable
+  `OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION` to `true`.
 
 [Enrich Api](#enrich-httpclient-api) can be used if any additional attributes are
 required on activity.

programming_language: Markdown
filename: RedactionHelper.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,59 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#nullable enable
+
+using System.Text;
+
+namespace OpenTelemetry.Internal;
+
+internal class RedactionHelper
+{
+    private static readonly string RedactedText = "Redacted";
+
+    public static string? GetRedactedQueryString(string query)
+    {
+        int length = query.Length;
+        int index = 0;
+
+        // Preallocate some size to avoid re-sizing multiple times.
+        // Since the size will increase, allocating twice as much.
+        // TODO: Check to see if we can borrow from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Text/ValueStringBuilder.cs
+        // to improve perf.
+        StringBuilder queryBuilder = new(2 * length);
+        while (index < query.Length)
+        {
+            // Check if the character is = for redacting value.
+            if (query[index] == '=')
+            {
+                // Append =
+                queryBuilder.Append('=');
+                index++;
+
+                // Append redactedText in place of original value.
+                queryBuilder.Append(RedactedText);
+
+                // Move until end of this key/value pair.
+                while (index < length && query[index] != '&')
+                {
+                    index++;
+                }
+
+                // End of key/value.
+                if (index < length && query[index] == '&')
+                {
+                    queryBuilder.Append(query[index]);
+                }
+            }
+            else
+            {
+                // Keep adding to the result
+                queryBuilder.Append(query[index]);
+            }
+
+            index++;
+        }
+
+        return queryBuilder.ToString();
+    }
+}

programming_language: C#
filename: BasicTests.cs
change_type: ModificationType.MODIFY
diff: @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc.Testing;
 using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
@@ -1052,6 +1053,78 @@ public sealed class BasicTests
     }
 #endif
 
+    [Theory]
+    [InlineData("?a", "?a", false)]
+    [InlineData("?a=bdjdjh", "?a=Redacted", false)]
+    [InlineData("?a=b&", "?a=Redacted&", false)]
+    [InlineData("?c=b&", "?c=Redacted&", false)]
+    [InlineData("?c=a", "?c=Redacted", false)]
+    [InlineData("?a=b&c", "?a=Redacted&c", false)]
+    [InlineData("?a=b&c=1123456&", "?a=Redacted&c=Redacted&", false)]
+    [InlineData("?a=b&c=1&a1", "?a=Redacted&c=Redacted&a1", false)]
+    [InlineData("?a=ghgjgj&c=1deedd&a1=", "?a=Redacted&c=Redacted&a1=Redacted", false)]
+    [InlineData("?a=b&c=11&a1=&", "?a=Redacted&c=Redacted&a1=Redacted&", false)]
+    [InlineData("?c&c&c&", "?c&c&c&", false)]
+    [InlineData("?a&a&a&a", "?a&a&a&a", false)]
+    [InlineData("?&&&&&&&", "?&&&&&&&", false)]
+    [InlineData("?c", "?c", false)]
+    [InlineData("?a", "?a", true)]
+    [InlineData("?a=bdfdfdf", "?a=bdfdfdf", true)]
+    [InlineData("?a=b&", "?a=b&", true)]
+    [InlineData("?c=b&", "?c=b&", true)]
+    [InlineData("?c=a", "?c=a", true)]
+    [InlineData("?a=b&c", "?a=b&c", true)]
+    [InlineData("?a=b&c=111111&", "?a=b&c=111111&", true)]
+    [InlineData("?a=b&c=1&a1", "?a=b&c=1&a1", true)]
+    [InlineData("?a=b&c=1&a1=", "?a=b&c=1&a1=", true)]
+    [InlineData("?a=b123&c=11&a1=&", "?a=b123&c=11&a1=&", true)]
+    [InlineData("?c&c&c&", "?c&c&c&", true)]
+    [InlineData("?a&a&a&a", "?a&a&a&a", true)]
+    [InlineData("?&&&&&&&", "?&&&&&&&", true)]
+    [InlineData("?c", "?c", true)]
+    [InlineData("?c=%26&", "?c=Redacted&", false)]
+    public async Task ValidateUrlQueryRedaction(string urlQuery, string expectedUrlQuery, bool disableQueryRedaction)
+    {
+        var exportedItems = new List<Activity>();
+
+        var configuration = new ConfigurationBuilder()
+            .AddInMemoryCollection(new Dictionary<string, string> { ["OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_DISABLE_URL_QUERY_REDACTION"] = disableQueryRedaction.ToString() })
+            .Build();
+
+        var path = "/api/values" + urlQuery;
+
+        // Arrange
+        using var traceprovider = Sdk.CreateTracerProviderBuilder()
+            .ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
+            .AddAspNetCoreInstrumentation()
+            .AddInMemoryExporter(exportedItems)
+            .Build();
+
+        using (var client = this.factory
+            .WithWebHostBuilder(builder =>
+            {
+                builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
+            })
+            .CreateClient())
+        {
+            try
+            {
+                using var response = await client.GetAsync(path);
+            }
+            catch (Exception)
+            {
+                // ignore errors
+            }
+
+            WaitForActivityExport(exportedItems, 1);
+        }
+
+        Assert.Single(exportedItems);
+        var activity = exportedItems[0];
+
+        Assert.Equal(expectedUrlQuery, activity.GetTagValue(SemanticConventions.AttributeUrlQuery));
+    }
+
     public void Dispose()
     {
         this.tracerProvider?.Dispose();

programming_language: C#
filename: IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs
change_type: ModificationType.MODIFY
diff: @@ -27,7 +27,7 @@ public class IncomingRequestsCollectionsIsAccordingToTheSpecTests
 
     [Theory]
     [InlineData("/api/values", null, "user-agent", 200, null)]
-    [InlineData("/api/values", "?query=1", null, 200, null)]
+    [InlineData("/api/values", null, null, 200, null)]
     [InlineData("/api/exception", null, null, 503, null)]
     [InlineData("/api/exception", null, null, 503, null, true)]
     public async Task SuccessfulTemplateControllerCallGeneratesASpan_New(
@@ -49,7 +49,10 @@ public class IncomingRequestsCollectionsIsAccordingToTheSpecTests
                     services.AddSingleton<CallbackMiddleware.CallbackMiddlewareImpl>(new TestCallbackMiddlewareImpl(statusCode, reasonPhrase));
                     services.AddOpenTelemetry()
                         .WithTracing(builder => builder
-                            .AddAspNetCoreInstrumentation(options => options.RecordException = recordException)
+                            .AddAspNetCoreInstrumentation(options =>
+                            {
+                                options.RecordException = recordException;
+                            })
                             .AddInMemoryExporter(exportedItems));
                 });
                 builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());

programming_language: C#
filename: HttpClientTests.Basic.cs
change_type: ModificationType.MODIFY
diff: @@ -2,8 +2,11 @@
 // SPDX-License-Identifier: Apache-2.0
 
 using System.Diagnostics;
+using Microsoft.Extensions.Configuration;
+
 #if NETFRAMEWORK
 using System.Net.Http;
+
 #endif
 using Microsoft.Extensions.DependencyInjection;
 using OpenTelemetry.Context.Propagation;
@@ -666,6 +669,67 @@ public partial class HttpClientTests : IDisposable
         Assert.Empty(exportedItems[0].Events);
     }
 
+    [Theory]
+    [InlineData("?a", "?a", false)]
+    [InlineData("?a=bdjdjh", "?a=Redacted", false)]
+    [InlineData("?a=b&", "?a=Redacted&", false)]
+    [InlineData("?c=b&", "?c=Redacted&", false)]
+    [InlineData("?c=a", "?c=Redacted", false)]
+    [InlineData("?a=b&c", "?a=Redacted&c", false)]
+    [InlineData("?a=b&c=1123456&", "?a=Redacted&c=Redacted&", false)]
+    [InlineData("?a=b&c=1&a1", "?a=Redacted&c=Redacted&a1", false)]
+    [InlineData("?a=ghgjgj&c=1deedd&a1=", "?a=Redacted&c=Redacted&a1=Redacted", false)]
+    [InlineData("?a=b&c=11&a1=&", "?a=Redacted&c=Redacted&a1=Redacted&", false)]
+    [InlineData("?c&c&c&", "?c&c&c&", false)]
+    [InlineData("?a&a&a&a", "?a&a&a&a", false)]
+    [InlineData("?&&&&&&&", "?&&&&&&&", false)]
+    [InlineData("?c", "?c", false)]
+    [InlineData("?a", "?a", true)]
+    [InlineData("?a=bdfdfdf", "?a=bdfdfdf", true)]
+    [InlineData("?a=b&", "?a=b&", true)]
+    [InlineData("?c=b&", "?c=b&", true)]
+    [InlineData("?c=a", "?c=a", true)]
+    [InlineData("?a=b&c", "?a=b&c", true)]
+    [InlineData("?a=b&c=111111&", "?a=b&c=111111&", true)]
+    [InlineData("?a=b&c=1&a1", "?a=b&c=1&a1", true)]
+    [InlineData("?a=b&c=1&a1=", "?a=b&c=1&a1=", true)]
+    [InlineData("?a=b123&c=11&a1=&", "?a=b123&c=11&a1=&", true)]
+    [InlineData("?c&c&c&", "?c&c&c&", true)]
+    [InlineData("?a&a&a&a", "?a&a&a&a", true)]
+    [InlineData("?&&&&&&&", "?&&&&&&&", true)]
+    [InlineData("?c", "?c", true)]
+    [InlineData("?c=%26&", "?c=Redacted&", false)]
+    public async Task ValidateUrlQueryRedaction(string urlQuery, string expectedUrlQuery, bool disableQueryRedaction)
+    {
+        var exportedItems = new List<Activity>();
+
+        var configuration = new ConfigurationBuilder()
+            .AddInMemoryCollection(new Dictionary<string, string> { ["OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION"] = disableQueryRedaction.ToString() })
+            .Build();
+
+        // Arrange
+        using var traceprovider = Sdk.CreateTracerProviderBuilder()
+            .ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
+            .AddHttpClientInstrumentation()
+            .AddInMemoryExporter(exportedItems)
+            .Build();
+
+        using var c = new HttpClient();
+        try
+        {
+            await c.GetStringAsync($"{this.url}path{urlQuery}");
+        }
+        catch
+        {
+        }
+
+        Assert.Single(exportedItems);
+        var activity = exportedItems[0];
+
+        var expectedUrl = $"{this.url}path{expectedUrlQuery}";
+        Assert.Equal(expectedUrl, activity.GetTagValue(SemanticConventions.AttributeUrlFull));
+    }
+
     [Theory]
     [InlineData(true, true)]
     [InlineData(true, false)]

programming_language: C#
filename: HttpWebRequestActivitySourceTests.netfx.cs
change_type: ModificationType.MODIFY
diff: @@ -35,6 +35,8 @@ public class HttpWebRequestActivitySourceTests : IDisposable
                     ValidateBaggage(httpWebRequest);
                 }
             },
+
+            DisableUrlQueryRedaction = true,
         };
 
         HttpWebRequestActivitySource.TracingOptions = options;

programming_language: C#
filename: RedactionHelperTest.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,32 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+using OpenTelemetry.Internal;
+using Xunit;
+
+namespace OpenTelemetry.Tests.Internal;
+
+public class RedactionHelperTest
+{
+    [Theory]
+    [InlineData("?a", "?a")]
+    [InlineData("?a=b", "?a=Redacted")]
+    [InlineData("?a=b&", "?a=Redacted&")]
+    [InlineData("?c=b&", "?c=Redacted&")]
+    [InlineData("?c=a", "?c=Redacted")]
+    [InlineData("?a=b&c", "?a=Redacted&c")]
+    [InlineData("?a=b&c=1&", "?a=Redacted&c=Redacted&")]
+    [InlineData("?a=b&c=1&a1", "?a=Redacted&c=Redacted&a1")]
+    [InlineData("?a=b&c=1&a1=", "?a=Redacted&c=Redacted&a1=Redacted")]
+    [InlineData("?a=b&c=11&a1=&", "?a=Redacted&c=Redacted&a1=Redacted&")]
+    [InlineData("?c&c&c&", "?c&c&c&")]
+    [InlineData("?a&a&a&a", "?a&a&a&a")]
+    [InlineData("?&&&&&&&", "?&&&&&&&")]
+    [InlineData("?c", "?c")]
+    [InlineData("?=c", "?=Redacted")]
+    [InlineData("?=c&=", "?=Redacted&=Redacted")]
+    public void QueryStringIsRedacted(string input, string expected)
+    {
+        Assert.Equal(expected, RedactionHelper.GetRedactedQueryString(input));
+    }
+}

programming_language: C#
filename: OpenTelemetry.Tests.csproj
change_type: ModificationType.MODIFY
diff: @@ -24,6 +24,7 @@
     <Compile Include="$(RepoRoot)\src\Shared\DiagnosticSourceInstrumentation\PropertyFetcher.cs" Link="Includes\PropertyFetcher.cs" />
     <Compile Include="$(RepoRoot)\src\Shared\Metrics\Base2ExponentialBucketHistogramHelper.cs" Link="Includes\Metrics\Base2ExponentialBucketHistogramHelper.cs" />
     <Compile Include="$(RepoRoot)\src\Shared\PeriodicExportingMetricReaderHelper.cs" Link="Includes\PeriodicExportingMetricReaderHelper.cs" />
+    <Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
     <Compile Include="$(RepoRoot)\src\Shared\PooledList.cs" Link="Includes\PooledList.cs" />
     <Compile Include="$(RepoRoot)\src\Shared\TagTransformerJsonHelper.cs" Link="Includes\TagTransformerJsonHelper.cs" />
   </ItemGroup>

programming_language: PowerShell