Add application identity to USERAGENT payload (V2) - #4632
Add application identity to USERAGENT payload (V2)#4632cheenamalhotra wants to merge 12 commits into
Conversation
Adds an optional agent identifier to the USERAGENT login feature extension so known middleware (EF Core, SSMS, DacFx, ...) can be told apart from direct SqlClient use. - New public `SqlClientAgent` enum and `SqlConnection.RegisterSqlClientAgent(SqlClientAgent)`. - Registration is process-wide and allowed once, so an application cannot overwrite or spoof an agent set by a library. - Can also be set from App.config via a `SqlClientAgent` section. - Payload format bumped to version 2; the agent id is appended as an optional 8th part only when registered. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbed43f-1014-45d7-a9f6-9f04711c281e
There was a problem hiding this comment.
🟡 Changes recommended
The LOGIN7 length race, enum validation, test isolation, and documentation issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds process-wide middleware identification to the USERAGENT login payload.
Changes:
- Adds
SqlClientAgentregistration and configuration APIs. - Extends USERAGENT v2 with an optional agent ID.
- Adds tests, documentation, and samples.
File summaries
| File | Description |
|---|---|
src/Microsoft.Data.SqlClient/tests/UnitTests/UserAgentTests.cs |
Tests payload versioning and agent encoding. |
src/Microsoft.Data.SqlClient/tests/UnitTests/SqlClientAgentTests.cs |
Tests identifiers and configuration parsing. |
src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs |
Verifies LOGIN7 agent transmission. |
src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientAgentConfigurationTests.cs |
Tests configuration precedence. |
src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config |
Registers the test agent. |
src/Microsoft.Data.SqlClient/src/Resources/Strings.resx |
Adds registration error messages. |
src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs |
Exposes generated resource accessors. |
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs |
Builds and caches agent payloads. |
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs |
Writes agent payloads into LOGIN7. |
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs |
Creates agent-related exceptions. |
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.cs |
Adds the registration API. |
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientAgent.cs |
Defines agents and registration logic. |
src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs |
Updates the public API contract. |
doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml |
Documents registration behavior. |
doc/samples/SqlConnection_RegisterSqlClientAgent.cs |
Demonstrates middleware registration. |
Review details
Files not reviewed (1)
- src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs: Generated file
Suppressed comments (1)
src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientAgentConfigurationTests.cs:20
- The test intent is written as ordinary comments, but test methods require XML
<summary>documentation. Convert this explanation to an XML summary so the new test follows the test documentation contract.
[ConditionalFact(typeof(TestUtility), nameof(TestUtility.IsNetFramework))]
public void AppConfigAgent_PreventsProgrammaticRegistration()
- Files reviewed: 14/15 changed files
- Comments generated: 4
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Capture the USERAGENT payload once in SendPreLoginHandshake and pass it to WriteLoginData, so a concurrent registration cannot make the reserved feature length disagree with the bytes written. - Restrict RegisterSqlClientAgent to declared enum members. Undeclared numeric ids remain valid in config, where forward compatibility matters. - Serialize ConnectionTests via SimulatedServerTestCollection; it now mutates process-wide registration. - Add XML summary to SqlClientAgentConfigurationTests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbed43f-1014-45d7-a9f6-9f04711c281e
There was a problem hiding this comment.
🔵 Needs a closer look
Malformed App.config handling needs isolated regression coverage before approval.
Review details
Files not reviewed (1)
- src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs: Generated file
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientAgent.cs:198
- The new tests validate
Parsedirectly, but none exercises this catch throughLoadFromAppConfig. Consequently, the stated guarantee that an invalid or malformed application configuration cannot turn first use into aTypeInitializationExceptionhas no regression coverage. Add an isolated-process/AppDomain test with a badSqlClientAgentsection that triggers registration loading and verifies the failure is consumed.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs:39
Valueis always built withagentId: null, so it never has the optional eighth part; onlyGetUcs2Bytescan return that transmitted form. Describing theValueproperty itself with the optional format makes its contract inconsistent with the implementation and the seven-part tests. Clarify that this is the base value and that the encoded login payload may append the agent ID.
/// The format is pipe ('|') delimited into 7 parts, plus an optional
/// 8th part:
///
/// <code>2|MS-MDS|{Driver Version}|{Arch}|{OS Type}|{OS Info}|{Runtime Info}[|{Agent Id}]</code>
src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientAgentConfigurationTests.cs:24
- Convert the preceding ordinary comment into an XML
<summary>for this test method. The repository's test documentation rules require behavior-focused XML summaries on every test method.
[ConditionalFact(typeof(TestUtility), nameof(TestUtility.IsNetFramework))]
public void AppConfigAgent_PreventsProgrammaticRegistration()
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Balanced
- Change SqlClientAgent to int-backed so it needs no CLSCompliant attribute, which the notsupported assembly rejects (CS3021). Identifiers are still bounded to a positive 16-bit range. - Extract LoadAgent so the configuration failure paths are testable, and cover malformed config, invalid id, wrong section type, missing section, and a throwing loader. - Clarify that UserAgent.Value never carries the agent id; only the login payload does. - Convert the App.config test comment to an XML summary. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbed43f-1014-45d7-a9f6-9f04711c281e
There was a problem hiding this comment.
🟡 Changes recommended
The public enum’s underlying type and CLS annotations do not match the advertised 16-bit API contract.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Files not reviewed (1)
- src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs: Generated file
- Files reviewed: 14/15 changed files
- Comments generated: 1
- Review effort level: Balanced
- Remove the now-unnecessary CLSCompliant attribute from the implementation method. - Document the 16-bit identifier contract on the enum, since it is no longer implied by the underlying type. - Assert the underlying type is Int32 so the CLS-compliant surface cannot regress. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbed43f-1014-45d7-a9f6-9f04711c281e
There was a problem hiding this comment.
🔵 Needs a closer look
Public API, configuration, and wire-payload changes require final human review, and two documentation nits remain.
Review details
Files not reviewed (1)
- src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs: Generated file
Suppressed comments (2)
src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs:603
- The PR's API example still declares
SqlClientAgent : ushort, but this public surface (andUnderlyingType_IsInt32) intentionally publishes anInt32-backed enum. Please update the PR description to omit: ushortor use: int, so consumers are not given an API signature that differs from the assembly.
public enum SqlClientAgent
src/Microsoft.Data.SqlClient/tests/UnitTests/UserAgentTests.cs:221
- Document the
bytesparameter and return value for this new test helper. The repository's test documentation rules require XML<param>and<returns>elements for helper methods where applicable.
/// <summary>
/// Decode a UCS-2 encoded payload back to its string form.
/// </summary>
private static string Decode(ReadOnlyMemory<byte> bytes) =>
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Balanced
doc/samples builds against the published Microsoft.Data.SqlClient package, so it cannot reference an API that has not shipped yet. Move the example into the XML docs alongside the existing App.config example and drop the compiled sample file. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbed43f-1014-45d7-a9f6-9f04711c281e
There was a problem hiding this comment.
🔵 Needs a closer look
The PR description incorrectly documents SqlClientAgent as having a ushort underlying type.
Review details
Files not reviewed (1)
- src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs: Generated file
Suppressed comments (1)
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientAgent.cs:23
- The PR's API example still advertises
public enum SqlClientAgent : ushort, while this declaration, the reference surface, and the new underlying-type test intentionally publishInt32. Because the enum's underlying type is observable, update the PR description to showpublic enum SqlClientAgent(or: int) and describe 16 bits as the validated identifier range rather than the underlying type.
public enum SqlClientAgent
- Files reviewed: 13/14 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
CI failure analysis — none of the 6 failing legs are caused by this change.
The test runs a 6-way cross join over
Why this change can't be the cause: it only appends an optional 8th part to the LOGIN7 USERAGENT payload. No manual test references Re-running to clear the flakes. |
Replace the process-wide agent registration with a per-connection application identity, matching the updated USERAGENT V2 spec. The payload now carries nine parts. The App Id and the driver-owned Driver Properties parts are always present, each written as four uppercase hexadecimal characters: 2|MS-MDS|6.1.3|X64|Windows|...|.NET 9.0.4|0000|0001 - SqlConnection.RegisterSqlClientAgent is replaced by the SqlConnection.SqlClientAppId property, set before the connection is opened. Values outside the 16-bit range are rejected rather than silently truncated at login. - SqlClientAgent is renamed to SqlClientApp. The enum keeps the default int backing so it stays CLS-compliant, and unregistered identifiers can still be reported by casting. - SqlClientDriverProperties tracks driver-owned feature flags, starting with connection pool V2 enablement. - App.config registration is removed. It only had meaning while registration was process-wide and once-only. Public type documentation lives in doc/snippets, per repo convention. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbed43f-1014-45d7-a9f6-9f04711c281e
There was a problem hiding this comment.
🟡 Changes recommended
Moderate API-contract, pooling, cloning, payload-format, and test-coverage issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Files not reviewed (1)
- src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs: Generated file
Suppressed comments (1)
src/Microsoft.Data.SqlClient/src/Resources/Strings.resx:2204
- This key is absent from every localized
Strings.*.resxfile, while their key sets otherwise match the base file (for example,Strings.de.resx:2199-2204,Strings.fr.resx:2199-2204, andStrings.ja.resx:2199-2204). Add the corresponding satellite entries so localized applications do not fall back to English for this new public exception.
<data name="SQL_InvalidSqlClientAppId" xml:space="preserve">
<value>The SqlClient application identifier '{0}' is outside the supported range of 0 to 65535.</value>
</data>
- Files reviewed: 17/18 changed files
- Comments generated: 6
- Review effort level: Balanced
- SqlConnection(SqlConnection) now copies the application identity, so ICloneable.Clone no longer resets a configured identity to Unknown. - Add SqlClientApp.SqlPackage. sqlpackage builds on the Data-Tier Application Framework but reports its own identifier so command-line use can be told apart from other callers of that framework. - Split the driver properties mapping out of the switch it reads, so the mapping can be tested. The switches are cached for the life of the process, which makes them impractical to vary in a test. - Document how the identity behaves under pooling. It is not part of the pool key, so a pooled connection reports the identity that created the physical connection, and connections opened in the background to satisfy Min Pool Size report Unknown. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbed43f-1014-45d7-a9f6-9f04711c281e
There was a problem hiding this comment.
🟡 Changes recommended
Multiple moderate API, login propagation, documentation, and localization issues remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Files not reviewed (1)
- src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs: Generated file
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs:1362
- The registration API no longer exists, so concurrent registration cannot occur here. Keep the useful consistency rationale without referring to the removed process-wide agent model.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/UserAgent.cs:530
- This comment still describes the removed
Agent Idmodel and calls the field optional, but V2 now always appends bothApp IdandDriver Properties. Update it so the source documentation matches the implemented nine-part payload.
// Version 2 adds the optional Agent Id part.
private const string PayloadVersion = "2";
- Files reviewed: 17/18 changed files
- Comments generated: 4
- Review effort level: Balanced
- SqlClientAppId now rejects being set once the connection is connecting or open, matching AccessToken and the other login-time properties. The identity is only reported during login, so allowing a later change let the getter report a value that was never sent. - Propagate the identity to the preliminary SQL Express connection used to discover the user instance name. That connection performs its own physical login, which was reported as Unknown. - Add SQL_InvalidSqlClientAppId to the localized resource files, which are otherwise key-synchronized with the neutral file. The value is the neutral text pending localization. - Drop two comments describing the removed process-wide agent model. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbed43f-1014-45d7-a9f6-9f04711c281e
There was a problem hiding this comment.
🟡 Changes recommended
The public API documentation and release notes remain incomplete.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Files not reviewed (1)
- src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs: Generated file
Suppressed comments (2)
doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml:2324
- Carry the trust boundary from the PR contract into the public API documentation. This identifier is entirely client-supplied (including arbitrary in-range casts), so server-side consumers must not treat it as an authenticated identity or use it for authorization decisions.
<remarks>
<para>
This API is intended for registered applications that reserve an identifier in
<see cref="T:Microsoft.Data.SqlClient.SqlClientApp" />. An unregistered identifier may be reported by casting
a value to that type, provided it is within the 16-bit range the protocol allows.
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientApp.cs:10
- Add a release-note entry for this user-visible feature. This introduces a new public enum/property and changes the always-sent USERAGENT wire payload to version 2, but no file under
release-notes/describes either change, so package consumers will not discover the new API or protocol behavior from the repository's release documentation.
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SqlClientApp/*' />
public enum SqlClientApp
- Files reviewed: 30/31 changed files
- Comments generated: 1
- Review effort level: Balanced
Record the InvalidOperationException the setter raises once the connection is opening or open, and state that the identity is client-supplied telemetry rather than an authenticated identity, so it must not be used for authorization. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbed43f-1014-45d7-a9f6-9f04711c281e
There was a problem hiding this comment.
🔵 Needs a closer look
The protocol, public API, telemetry, and connection-lifecycle changes require final human validation.
Review details
Files not reviewed (1)
- src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs: Generated file
- Files reviewed: 30/31 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4632 +/- ##
==========================================
- Coverage 65.92% 64.60% -1.32%
==========================================
Files 290 285 -5
Lines 44987 68038 +23051
==========================================
+ Hits 29656 43958 +14302
- Misses 15331 24080 +8749
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
New resource strings are added only to the neutral Strings.resx; the scheduled OneLocBuild run populates the satellite files. Adding them by hand risks conflicting with that pipeline. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: efbed43f-1014-45d7-a9f6-9f04711c281e
There was a problem hiding this comment.
🟡 Changes recommended
The new resource key must be added to all localized satellite resource files.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Files not reviewed (1)
- src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs: Generated file
- Files reviewed: 17/18 changed files
- Comments generated: 1
- Review effort level: Balanced
|
Not sure if the enum names matter but: EntityFramework => EntityFrameworkCore And how about this tool owned by Azure Data: Data API Builder |
paulmedynski
left a comment
There was a problem hiding this comment.
Some of my feedback depends on finalizing the design doc, so you can hold off on those sorts of changes until we have design concensus.
Other feedback can be addressed now.
I will look at tests once the design is finalized and the corresponding updates appear here.
| } | ||
|
|
||
| /// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlClientApp.xml' path='docs/members[@name="SqlClientApp"]/SqlClientApp/*' /> | ||
| public enum SqlClientApp |
There was a problem hiding this comment.
Set the underlying type to ushort.
Applies here and in src/
| </list> | ||
| <para> | ||
| An unregistered identifier may still be reported by casting a value to this type. Identifiers are limited to | ||
| the 16-bit space the protocol allows, so a value outside 0x0000 to 0xFFFF is rejected when it is assigned to |
There was a problem hiding this comment.
We only need the first sentence here. The compiler won't allow values larger than ushort once we set that as the enum's underlying type. It won't be possible to specify larger values.
| </para> | ||
| </remarks> | ||
| </System.ICloneable.Clone> | ||
| <SqlClientAppId> |
There was a problem hiding this comment.
We don't need the Id suffix here. C# property names can be the same as their return value name.
| using Microsoft.Data.SqlClient; | ||
|
|
||
| using var connection = new SqlConnection(connectionString); | ||
| connection.SqlClientAppId = SqlClientApp.EntityFramework; |
There was a problem hiding this comment.
Should our example use modern property initialization syntax?
using SqlConnection connection = new(connectionString)
{
SqlClientAppId = SqlClientApp.EntityFramework;
}
| connection.Open(); | ||
| </code> | ||
| </example> | ||
| <exception cref="T:System.ArgumentOutOfRangeException"> |
There was a problem hiding this comment.
This property won't throw out-of-range.
| (ushort)SqlClientDriverPropertiesResolver.Current); | ||
|
|
||
| /// <summary> | ||
| /// Narrow an application identifier to the 16 bits the payload reports. |
There was a problem hiding this comment.
We won't be narrowing, just casting, in which case do we need this method?
| string osInfo, | ||
| string runtimeInfo) | ||
| string runtimeInfo, | ||
| ushort appId = 0, |
There was a problem hiding this comment.
Driver code never calls Build() without these values, so they shouldn't have defaults.
The tests will need to adjust.
| /// drivers use the same part for their own purposes. | ||
| /// </remarks> | ||
| [Flags] | ||
| internal enum SqlClientDriverProperties : ushort |
There was a problem hiding this comment.
Suggest ulong to give us room for 64 flags.
| // The rows come from constant row sets rather than the system | ||
| // catalog, so the scan cannot contend with concurrent DDL on | ||
| // the shared test database. | ||
| const string rows = "(values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15))"; |
There was a problem hiding this comment.
Ahh, trying to fix our pipeline deadlocks?
Fixes #3201
Implements USERAGENT payload version 2, per the SQL Drivers User Agent V2 spec.
Summary
Version 2 adds two parts to the login USERAGENT feature extension:
The application id comes from an enum rather than a free-form string, so applications cannot inject arbitrary text into the telemetry payload.
API
Reserved ranges:
0x0001-0x7FFF0x8000-0xBFFF0xC000-0xFFFFSet the property before opening the connection. An id that is not in the enum yet can still be reported by casting, so an app assigned an id after a driver release does not have to wait for a new build. Values outside the 16-bit range throw
ArgumentOutOfRangeExceptionrather than being silently truncated at login.Payload
Version part bumped
1->2, and the payload is now 9 parts. Both new parts are always sent, as 4 uppercase hex characters:For example:
With no app id set, part 8 is
0000.Notes
Min Pool SizereportUnknown. Treat this as indicative rather than exact attribution. Partitioning the pool per app id was considered and rejected — it would cost real connections to buy telemetry precision.SqlClientAgenttoSqlClientAppand replaces the staticRegisterSqlClientAgentmethod from the earlier revision of this PR. App.config registration is also dropped — it only had meaning while registration was process-wide and once-only. Neither has shipped.Checklist