-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Redact constants that get inlined from variables into query in log #35724
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Overview
This PR introduces a new mechanism to mark constant SQL expressions that were originally parameters so that, when inlined into a query, they are handled like parameters with respect to sensitive data logging. Key changes include:
- Adding an extra flag (“originallyParameter”) to the SqlConstantExpression and propagating it through various factory methods and visitors.
- Updating multiple components (SQL generators, expression visitors, and diagnostic loggers) to use the new flag and to pass along a new “logCommandText” parameter for redacted logging.
- Revising XML documentation and method signatures across the EF Core Relational components for consistency.
Reviewed Changes
File | Description |
---|---|
src/EFCore.Relational/Query/SqlExpressions/SqlConstantExpression.cs | Adds the “originallyParameter” flag to the constructors and updates ApplyTypeMapping/Quote. |
src/EFCore.Relational/Query/QuerySqlGenerator.cs | Updates SQL generation logic to pass the flag when composing the command text. |
src/EFCore.Relational/Query/ISqlExpressionFactory.cs | Modifies Constant method overloads to include the new parameter with a default value. |
src/EFCore.Relational/Diagnostics/*.cs | Introduces a new “logCommandText” parameter and revises log formatting in diagnostic events. |
src/EFCore.Relational/Query/SqlNullabilityProcessor.cs | Revises calls to Constant to include the originallyParameter flag (often set to true). |
(Other files across the query translation and diagnostics areas) | Propagates the new flag through SQL expression visitors and translators. |
Copilot reviewed 57 out of 57 changed files in this pull request and generated no comments.
Comments suppressed due to low confidence (2)
src/EFCore.Relational/Query/SqlNullabilityProcessor.cs:131
- [nitpick] Consider adding unit tests to verify that inline constant expressions flagged with 'originallyParameter: true' are correctly redacted when sensitive data logging is disabled.
processedValues.Add(_sqlExpressionFactory.Constant(value, value?.GetType() ?? typeof(object), originallyParameter: true, typeMapping: typeMapping));
src/EFCore.Relational/Diagnostics/Internal/RelationalCommandDiagnosticsLogger.cs:320
- [nitpick] Consider adding tests to verify that the new 'logCommandText' parameter is used appropriately in scenarios where sensitive data logging is disabled, ensuring the redacted command text is correctly applied.
command.CommandText.TrimEnd());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, looks pretty good - see comments below.
Also, don't we need test coverage for this? Or did I miss it?
/// <param name="startTime">The start time of this event.</param> | ||
/// <param name="commandSource">Source of the command.</param> | ||
public CommandEventData( | ||
EventDefinitionBase eventDefinition, | ||
Func<EventDefinitionBase, EventData, string> messageGenerator, | ||
DbConnection connection, | ||
DbCommand command, | ||
string? logCommandText, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit nitty, but another option instead of passing in a nullable text, is to simply always pass in a redactedCommandText parameter - if there are no redacted parameters, it's the same as DbCommand.CommandText. This would both makes the meaning of this parameter clear, and also remove the need for the assumption that if logSensitiveValues is true, this is non-null (i.e. no more bang (!) when accessing it)
@@ -219,6 +219,7 @@ private CommandEndEventData BroadcastCommandCreated( | |||
CommandCreated, | |||
connection, | |||
command, | |||
command.CommandText, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we keep this parameter as nullable, shouldn't it be null here (because below we pass false)? Though as above, I do believe we should just always pass it in and make the parameter non-nullable.
@@ -431,11 +438,13 @@ public virtual InterceptionResult<object> CommandScalarExecuting( | |||
|
|||
definition.Log( | |||
this, | |||
command.Parameters.FormatParameters(ShouldLogParameterValues(command)), | |||
command.Parameters.FormatParameters(ShouldLogSensitiveData()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW I'm not sure a rename of ShouldLogParameterValues to ShouldLogSensitiveData is actually necessary - this still controls whether parameter data is logged or not (it's just that now it also covers inlined parameters). But no strong feelings if we want to make this rename.
/// <param name="typeMapping">The <see cref="RelationalTypeMapping" /> associated with the expression.</param> | ||
/// <returns>An expression representing a constant in a SQL tree.</returns> | ||
SqlExpression Constant(object value, RelationalTypeMapping? typeMapping = null); | ||
SqlExpression Constant(object value, bool originallyParameter = false, RelationalTypeMapping? typeMapping = null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest renaming to isSensitive to convey that the constant contains sensitive information (to be redacted) rather than where it came from (this also aligns with the naming of LogSensitiveValues)... In theory we may have other things that become sensitive at some point without having been a parameter originally?
SqlExpression Constant(object value, bool originallyParameter = false, RelationalTypeMapping? typeMapping = null); | |
SqlExpression Constant(object value, bool isSensitive = false, RelationalTypeMapping? typeMapping = null); |
/// <param name="typeMapping">The <see cref="RelationalTypeMapping" /> associated with the expression.</param> | ||
/// <returns>An expression representing a constant in a SQL tree.</returns> | ||
SqlExpression Constant(object value, RelationalTypeMapping? typeMapping = null); | ||
SqlExpression Constant(object value, bool originallyParameter = false, RelationalTypeMapping? typeMapping = null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, split to a different method, i.e. keep the current overload and add another one that accepts the new required flag. Adding the parameter to the existing signature breaks existing code, and also forces everyone to explicitly specify typeMapping: when they don't want to pass in originallyParameter - which is the 99% case. Once you do that, you can revert all the additions of typeMapping: in this PR.
/// <summary> | ||
/// Whether the expression was originally a parameter. | ||
/// </summary> | ||
public virtual bool OriginallyParameter { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsSensitive?
@@ -765,7 +765,7 @@ InExpression ProcessInExpressionValues( | |||
continue; | |||
} | |||
|
|||
processedValues.Add(_sqlExpressionFactory.Constant(value, value?.GetType() ?? typeof(object), typeMapping)); | |||
processedValues.Add(_sqlExpressionFactory.Constant(value, value?.GetType() ?? typeof(object), originallyParameter: true, typeMapping: typeMapping)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW I'm still slightly undecided about what to do when the user requests inlining via EF.Constant() (in VisitSqlParameter in this visitor)... I think we talked about this briefly, not sure what we said. Part of me does feel like a parameter is a parameter, and the fact that the user requests inlining it doesn't make it less sensitive (and should therefore be redacted) - but I'm really unsure.
Maybe bring to a quick design discussion?
/// <summary> | ||
/// Gets the command text to be logged. | ||
/// </summary> | ||
string? LogCommandText { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RedactedCommandText (also elsewhere)?
|
||
if (!Dependencies.LoggingOptions.IsSensitiveDataLoggingEnabled) | ||
{ | ||
_logCommandTextBuilder = new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means that we always generate the two strings once IsSensitiveDataLoggingEnabled is enabled, even when there are no sensitive values. Can we instead lazily initialize this the first time that Append is called with an actual value that needs to be redacted?
@@ -66,17 +74,19 @@ public virtual IRelationalCommandBuilder RemoveParameterAt(int index) | |||
} | |||
|
|||
/// <inheritdoc /> | |||
public virtual IRelationalCommandBuilder Append(string value) | |||
public virtual IRelationalCommandBuilder Append(string value, string? logValue = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just pass in a boolean isSensitive, or redact? This would centralize the redacted representation (question mark) here, rather than force all callers to duplicate it - unless you can see a case where we'd need to pass something other than a question mark?
This adds
OriginallyParameter
intoSqlConstantExpression
to detect a case when something that was originally a parameter in query was turned into inline value. This value should be then handled the same way as we do for parameters (regardingEnableSensitiveDataLogging
).The SQL generator is then generating two versions of query. The first is the standard one that will be sent to database and other is with inlined values redacted. Later, if sensitive logging is off, diagnostic logger will use it instead of regular
DbCommand.CommandText
.