CVE-2024-32028: Insufficient Information (NVD-CWE-noinfo)
value: 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.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-12T23:15Z
Repository: https://github.com/open-telemetry/opentelemetry-dotnet
Language: C#
Files changed: OpenTelemetry.sln, src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreTraceInstrumentationOptions.cs, src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md, src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs, src/OpenTelemetry.Instrumentation.AspNetCore/OpenTelemetry.Instrumentation.AspNetCore.csproj, src/OpenTelemetry.Instrumentation.AspNetCore/README.md, src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md, src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationTracerProviderBuilderExtensions.cs, src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs, src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs, src/OpenTelemetry.Instrumentation.Http/Implementation/HttpInstrumentationEventSource.cs, src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs, src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs, src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj, src/OpenTelemetry.Instrumentation.Http/README.md, src/Shared/RedactionHelper.cs, test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs, test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs, test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs, test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs, test/OpenTelemetry.Tests/Internal/RedactionHelperTest.cs, test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj
Commit date: 2024-04-12 11:47:42 -0700
Commit message: [Instrumentation.Http][Instrumentation.AspNetCore] Fix `url.full` and `url.query` attribute values (#5532)


Diff: diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln
index 7a98293e..e9839f41 100644
--- a/OpenTelemetry.sln
+++ b/OpenTelemetry.sln
@@ -268,10 +268,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A49299
 		src\Shared\Guard.cs = src\Shared\Guard.cs
 		src\Shared\MathHelper.cs = src\Shared\MathHelper.cs
 		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
 		src\Shared\SpanAttributeConstants.cs = src\Shared\SpanAttributeConstants.cs
 		src\Shared\SpanHelper.cs = src\Shared\SpanHelper.cs
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreTraceInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreTraceInstrumentationOptions.cs
index 38791ee2..f5ffb796 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreTraceInstrumentationOptions.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreTraceInstrumentationOptions.cs
@@ -30,10 +30,18 @@ public class AspNetCoreTraceInstrumentationOptions
             "OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_ENABLE_GRPC_INSTRUMENTATION",
             out var enableGrpcInstrumentation))
         {
             this.EnableGrpcAspNetCoreSupport = enableGrpcInstrumentation;
         }
+
+        if (configuration.TryGetBoolValue(
+            AspNetCoreInstrumentationEventSource.Log,
+            "OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_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.
@@ -92,6 +100,16 @@ public class AspNetCoreTraceInstrumentationOptions
     /// </summary>
     /// <remarks>
     /// 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; }
 }
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
index baf8bd6a..d8f74ef6 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
@@ -1,8 +1,18 @@
 # 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
 
 Released 2024-Apr-04
 
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
index 7d865263..7b5942a1 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
@@ -191,12 +191,18 @@ 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);
 
             activity.SetTag(SemanticConventions.AttributeUrlScheme, request.Scheme);
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/OpenTelemetry.Instrumentation.AspNetCore.csproj b/src/OpenTelemetry.Instrumentation.AspNetCore/OpenTelemetry.Instrumentation.AspNetCore.csproj
index a95dd1c1..e60747b9 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/OpenTelemetry.Instrumentation.AspNetCore.csproj
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/OpenTelemetry.Instrumentation.AspNetCore.csproj
@@ -15,10 +15,11 @@
     <Compile Include="$(RepoRoot)\src\Shared\Configuration\*.cs" Link="Includes\Configuration\%(Filename).cs" />
     <Compile Include="$(RepoRoot)\src\Shared\EnvironmentVariables\*.cs" Link="Includes\EnvironmentVariables\%(Filename).cs" Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'" />
     <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" />
   </ItemGroup>
 
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md
index e51a41be..f8ef36ef 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md
@@ -73,11 +73,15 @@ for more details about each individual attribute:
 * `network.protocol.version`
 * `user_agent.original`
 * `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
 required on activity.
 
diff --git a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
index c3f73302..829e5952 100644
--- a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
@@ -1,8 +1,18 @@
 # 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
 
 Released 2024-Apr-04
 
diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationTracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationTracerProviderBuilderExtensions.cs
index 6015c183..5f97d034 100644
--- a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationTracerProviderBuilderExtensions.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationTracerProviderBuilderExtensions.cs
@@ -57,10 +57,12 @@ public static class HttpClientInstrumentationTracerProviderBuilderExtensions
         {
             if (configureHttpClientTraceInstrumentationOptions != null)
             {
                 services.Configure(name, configureHttpClientTraceInstrumentationOptions);
             }
+
+            services.RegisterOptionsFactory(configuration => new HttpClientTraceInstrumentationOptions(configuration));
         });
 
 #if NETFRAMEWORK
         builder.AddSource(HttpWebRequestActivitySource.ActivitySourceName);
 
diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs
index 95bb6dab..a5fbec23 100644
--- a/src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs
@@ -1,23 +1,45 @@
 // Copyright The OpenTelemetry Authors
 // SPDX-License-Identifier: Apache-2.0
 
 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;
 
 /// <summary>
 /// Options for HttpClient instrumentation.
 /// </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.
     /// </summary>
     /// <remarks>
@@ -123,10 +145,20 @@ public class HttpClientTraceInstrumentationOptions
     /// href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/exceptions/exceptions-spans.md"
     /// />.</para>
     /// </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)
     {
         try
         {
diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs
index 6b3bea0d..8dda9051 100644
--- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs
@@ -147,11 +147,11 @@ internal sealed class HttpHandlerDiagnosticListener : ListenerHandler
             RequestMethodHelper.SetHttpMethodTag(activity, request.Method.Method);
 
             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
             {
                 this.options.EnrichWithHttpRequestMessage?.Invoke(activity, request);
             }
diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpInstrumentationEventSource.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpInstrumentationEventSource.cs
index 360fa496..4f04f9b6 100644
--- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpInstrumentationEventSource.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpInstrumentationEventSource.cs
@@ -1,18 +1,19 @@
 // Copyright The OpenTelemetry Authors
 // SPDX-License-Identifier: Apache-2.0
 
 using System.Diagnostics.Tracing;
+using Microsoft.Extensions.Configuration;
 using OpenTelemetry.Internal;
 
 namespace OpenTelemetry.Instrumentation.Http.Implementation;
 
 /// <summary>
 /// 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();
 
     [NonEvent]
     public void FailedProcessResult(Exception ex)
@@ -98,6 +99,17 @@ internal sealed class HttpInstrumentationEventSource : EventSource
     [Event(7, Message = "Unknown error processing event '{1}' from handler '{0}', Exception: {2}", Level = EventLevel.Error)]
     public void UnknownErrorProcessingEvent(string handlerName, string eventName, string ex)
     {
         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);
+    }
 }
diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs
index 82d7fd11..0b5d7184 100644
--- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs
@@ -1,8 +1,10 @@
 // Copyright The OpenTelemetry Authors
 // SPDX-License-Identifier: Apache-2.0
 
+using OpenTelemetry.Internal;
+
 namespace OpenTelemetry.Instrumentation.Http.Implementation;
 
 /// <summary>
 /// A collection of helper methods to be used when building Http activities.
 /// </summary>
@@ -10,19 +12,22 @@ internal static class HttpTagHelper
 {
     /// <summary>
     /// 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
     {
         (1, 0) => "1.0",
diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs
index 1595a0a7..1418bc0f 100644
--- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs
+++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs
@@ -103,11 +103,11 @@ internal static class HttpWebRequestActivitySource
             RequestMethodHelper.SetHttpMethodTag(activity, request.Method);
 
             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
             {
                 TracingOptions.EnrichWithHttpWebRequest?.Invoke(activity, request);
             }
diff --git a/src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj b/src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj
index 55158028..35e43a09 100644
--- a/src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj
+++ b/src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj
@@ -10,12 +10,16 @@
     <!-- this is temporary. will remove in future PR. -->
     <Nullable>disable</Nullable>
   </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>
 
   <ItemGroup Condition="'$(RunningDotNetPack)' != 'true'">
     <ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Api.ProviderBuilderExtensions\OpenTelemetry.Api.ProviderBuilderExtensions.csproj" />
@@ -27,8 +31,9 @@
   </ItemGroup>
 
   <ItemGroup>
     <Reference Include="System.Net.Http" Condition="'$(TargetFramework)' == '$(NetFrameworkMinimumSupportedVersion)'" />
     <PackageReference Include="Microsoft.Extensions.Options" />
+    <PackageReference Include="Microsoft.Extensions.Configuration" />
   </ItemGroup>
 
 </Project>
diff --git a/src/OpenTelemetry.Instrumentation.Http/README.md b/src/OpenTelemetry.Instrumentation.Http/README.md
index e9177766..b3fec4eb 100644
--- a/src/OpenTelemetry.Instrumentation.Http/README.md
+++ b/src/OpenTelemetry.Instrumentation.Http/README.md
@@ -66,11 +66,15 @@ for more details about each individual attribute:
 * `http.request.method_original`
 * `http.response.status_code`
 * `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.
 
 #### Metrics
diff --git a/src/Shared/RedactionHelper.cs b/src/Shared/RedactionHelper.cs
new file mode 100644
index 00000000..05944efa
--- /dev/null
+++ b/src/Shared/RedactionHelper.cs
@@ -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();
+    }
+}
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs
index 360f9b54..03f6f4c1 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs
@@ -6,10 +6,11 @@ using System.Text.Json;
 using Microsoft.AspNetCore.Builder;
 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;
 using OpenTelemetry.Context.Propagation;
 using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
@@ -1050,10 +1051,82 @@ public sealed class BasicTests
         // Confirm that Activity.Current and IHttpActivityFeature activity are same
         Assert.True(result);
     }
 #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();
     }
 
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs
index ee2195c6..d5777dbc 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/IncomingRequestsCollectionsIsAccordingToTheSpecTests.cs
@@ -25,11 +25,11 @@ public class IncomingRequestsCollectionsIsAccordingToTheSpecTests
         this.factory = factory;
     }
 
     [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(
         string urlPath,
         string query,
@@ -47,11 +47,14 @@ public class IncomingRequestsCollectionsIsAccordingToTheSpecTests
                 builder.ConfigureTestServices((IServiceCollection services) =>
                 {
                     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());
             })
             .CreateClient())
diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs
index e6c0191b..d9d58399 100644
--- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs
+++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs
@@ -1,11 +1,14 @@
 // Copyright The OpenTelemetry Authors
 // 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;
 using OpenTelemetry.Instrumentation.Http.Implementation;
 using OpenTelemetry.Metrics;
@@ -664,10 +667,71 @@ public partial class HttpClientTests : IDisposable
         // Exception is thrown and not collected as event
         Assert.True(exceptionThrown);
         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)]
     [InlineData(false, true)]
     [InlineData(false, false)]
diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs
index 33708c34..4235f98e 100644
--- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs
+++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs
@@ -33,10 +33,12 @@ public class HttpWebRequestActivitySourceTests : IDisposable
                 if (validateBaggage)
                 {
                     ValidateBaggage(httpWebRequest);
                 }
             },
+
+            DisableUrlQueryRedaction = true,
         };
 
         HttpWebRequestActivitySource.TracingOptions = options;
 
         _ = Sdk.SuppressInstrumentation;
diff --git a/test/OpenTelemetry.Tests/Internal/RedactionHelperTest.cs b/test/OpenTelemetry.Tests/Internal/RedactionHelperTest.cs
new file mode 100644
index 00000000..dda28455
--- /dev/null
+++ b/test/OpenTelemetry.Tests/Internal/RedactionHelperTest.cs
@@ -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));
+    }
+}
diff --git a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj
index 86fe7e36..6d2b3619 100644
--- a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj
+++ b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj
@@ -22,10 +22,11 @@
     <Compile Include="$(RepoRoot)\src\Shared\ActivityInstrumentationHelper.cs" Link="Includes\ActivityInstrumentationHelper.cs" />
     <Compile Include="$(RepoRoot)\src\Shared\DiagnosticSourceInstrumentation\ListenerHandler.cs" Link="Includes\ListenerHandler.cs" />
     <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>
 
   <ItemGroup>

Vulnerable code: ## Unreleased
                // QueryString should be sanitized. see: https://github.com/open-telemetry/opentelemetry-dotnet/issues/4571
                activity.SetTag(SemanticConventions.AttributeUrlQuery, request.QueryString.Value);
* `url.query`
## Unreleased
using System.Runtime.CompilerServices;
            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
internal sealed class HttpInstrumentationEventSource : EventSource
    public static string GetUriTagValueFromRequestUri(Uri uri)
        if (string.IsNullOrEmpty(uri.UserInfo))
        return string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment);
            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri));
* `url.full`
    [InlineData("/api/values", "?query=1", null, 200, null)]
                            .AddAspNetCoreInstrumentation(options => options.RecordException = recordException)
Fixed code: 		src\Shared\RedactionHelper.cs = src\Shared\RedactionHelper.cs

        if (configuration.TryGetBoolValue(
            AspNetCoreInstrumentationEventSource.Log,
            "OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_DISABLE_URL_QUERY_REDACTION",
            out var disableUrlQueryRedaction))
        {
            this.DisableUrlQueryRedaction = disableUrlQueryRedaction;
        }

    /// <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; }
## 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))
                if (this.options.DisableUrlQueryRedaction)
                {
                    activity.SetTag(SemanticConventions.AttributeUrlQuery, request.QueryString.Value);
                }
                else
                {
                    activity.SetTag(SemanticConventions.AttributeUrlQuery, RedactionHelper.GetRedactedQueryString(request.QueryString.Value));
                }
    <Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
* `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`.
## 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))

            services.RegisterOptionsFactory(configuration => new HttpClientTraceInstrumentationOptions(configuration));
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Configuration;
    /// <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 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; }

            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri, this.options.DisableUrlQueryRedaction));
using Microsoft.Extensions.Configuration;
internal sealed class HttpInstrumentationEventSource : EventSource, IConfigurationExtensionsLogger

    [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);
    }
using OpenTelemetry.Internal;

    /// <param name="disableQueryRedaction">Indicates whether query parameter should be redacted or not.</param>
    public static string GetUriTagValueFromRequestUri(Uri uri, bool disableQueryRedaction)
        if (string.IsNullOrEmpty(uri.UserInfo) && disableQueryRedaction)
        var query = disableQueryRedaction ? uri.Query : RedactionHelper.GetRedactedQueryString(uri.Query);

        return string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.AbsolutePath, query, uri.Fragment);
            activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri, tracingOptions.DisableUrlQueryRedaction));
    <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\Options\*.cs" Link="Includes\Options\%(Filename).cs" />
    <Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
    <PackageReference Include="Microsoft.Extensions.Configuration" />
* `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`.
// 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();
    }
}
using Microsoft.Extensions.Configuration;
    [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));
    }

    [InlineData("/api/values", null, null, 200, null)]
                            .AddAspNetCoreInstrumentation(options =>
                            {
                                options.RecordException = recordException;
                            })
using Microsoft.Extensions.Configuration;


    [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));
    }


            DisableUrlQueryRedaction = true,
// 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));
    }
}
    <Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />