Skip to content

[EFCore] Fix MySQL/MariaDB literal sanitization - #4986

Open
martincostello wants to merge 8 commits into
open-telemetry:mainfrom
martincostello:sanitise-mysql-literals
Open

[EFCore] Fix MySQL/MariaDB literal sanitization#4986
martincostello wants to merge 8 commits into
open-telemetry:mainfrom
martincostello:sanitise-mysql-literals

Conversation

@martincostello

@martincostello martincostello commented Aug 17, 2026

Copy link
Copy Markdown
Member

Changes

Sanitize SQL for MySQL and MariaDB when dollar-quoted literals, double-quoted literals and backslash-escaped single quotes are present.

Merge requirement checklist

  • CONTRIBUTING guidelines followed (license requirements, nullable enabled, static analysis, etc.)
  • Unit tests added/updated
  • Appropriate CHANGELOG.md files updated for non-trivial changes
  • Changes in public API reviewed (if applicable)

Sanitize SQL for MySQL and MariaDB when dollar-quoted literals and backslash-escaped single quotes are present.
@github-actions github-actions Bot added the comp:instrumentation.entityframeworkcore Things related to OpenTelemetry.Instrumentation.EntityFrameworkCore label Aug 17, 2026
@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 78.11%. Comparing base (cd0365a) to head (8b0ded7).
⚠️ Report is 6 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/Shared/SqlProcessor.cs 98.57% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4986      +/-   ##
==========================================
+ Coverage   77.97%   78.11%   +0.13%     
==========================================
  Files         477      484       +7     
  Lines       20395    20601     +206     
==========================================
+ Hits        15904    16092     +188     
- Misses       4491     4509      +18     
Flag Coverage Δ
unittests-Contrib.Shared.Tests 89.00% <98.57%> (+0.51%) ⬆️
unittests-DynamicControl 99.14% <ø> (-0.86%) ⬇️
unittests-Exporter.Geneva 56.40% <ø> (-0.38%) ⬇️
unittests-Exporter.InfluxDB 97.96% <ø> (ø)
unittests-Exporter.OneCollector 95.60% <ø> (ø)
unittests-Extensions 95.77% <ø> (ø)
unittests-Extensions.Enrichment 100.00% <ø> (ø)
unittests-Extensions.Enrichment.AspNetCore 86.27% <ø> (ø)
unittests-Extensions.Enrichment.Http 94.33% <ø> (ø)
unittests-Instrumentation.AWS 85.35% <ø> (ø)
unittests-Instrumentation.AspNet 79.15% <ø> (ø)
unittests-Instrumentation.AspNetCore 87.20% <ø> (ø)
unittests-Instrumentation.Cassandra 92.30% <ø> (ø)
unittests-Instrumentation.ConfluentKafka 83.42% <ø> (ø)
unittests-Instrumentation.ElasticsearchClient 80.36% <ø> (ø)
unittests-Instrumentation.EntityFrameworkCore 81.32% <100.00%> (+0.29%) ⬆️
unittests-Instrumentation.EventCounters 76.31% <ø> (ø)
unittests-Instrumentation.GrpcCore 91.12% <ø> (ø)
unittests-Instrumentation.GrpcNetClient 78.30% <ø> (ø)
unittests-Instrumentation.Hangfire 88.88% <ø> (ø)
unittests-Instrumentation.Http 76.00% <ø> (ø)
unittests-Instrumentation.Kusto 92.44% <ø> (ø)
unittests-Instrumentation.Owin 89.15% <ø> (ø)
unittests-Instrumentation.Process 100.00% <ø> (ø)
unittests-Instrumentation.Quartz 77.77% <ø> (ø)
unittests-Instrumentation.Remoting 65.59% <ø> (ø)
unittests-Instrumentation.Runtime 100.00% <ø> (ø)
unittests-Instrumentation.ServiceFabricRemoting 39.91% <ø> (ø)
unittests-Instrumentation.SqlClient 84.88% <ø> (ø)
unittests-Instrumentation.StackExchangeRedis 95.30% <ø> (+0.31%) ⬆️
unittests-Instrumentation.Wcf 82.64% <ø> (ø)
unittests-OpAmp.Client 86.78% <ø> (+0.17%) ⬆️
unittests-PersistentStorage 69.11% <ø> (-0.10%) ⬇️
unittests-Resources.AWS 72.78% <ø> (ø)
unittests-Resources.Azure 84.18% <ø> (ø)
unittests-Resources.Container 80.76% <ø> (ø)
unittests-Resources.Gcp 80.70% <ø> (ø)
unittests-Resources.Host 72.34% <ø> (ø)
unittests-Resources.OperatingSystem 77.16% <ø> (ø)
unittests-Resources.Process 90.90% <ø> (ø)
unittests-Resources.ProcessRuntime 80.76% <ø> (ø)
unittests-Sampler.AWS 96.05% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...mplementation/EntityFrameworkDiagnosticListener.cs 86.06% <100.00%> (+0.28%) ⬆️
src/Shared/DatabaseSemanticConventionHelper.cs 83.33% <ø> (ø)
src/Shared/SqlProcessor.cs 98.80% <98.57%> (+0.09%) ⬆️

... and 17 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread src/Shared/SqlProcessor.cs Outdated
Fix typo in comment.
@martincostello
martincostello marked this pull request as ready for review August 17, 2026 12:03
@martincostello
martincostello requested a review from a team as a code owner August 17, 2026 12:04
Copilot AI lite review requested due to automatic review settings August 17, 2026 12:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SQL sanitization for EFCore instrumentation (and shared SQL processing) to prevent literal leakage for MySQL/MariaDB backslash-escaped quotes and PostgreSQL dollar-quoted string literals, while keeping the existing query-summary behavior and adding tests to lock in the new parsing behavior.

Changes:

  • Added dialect-aware sanitization to handle MySQL/MariaDB backslash-escaped single quotes.
  • Added sanitization support for PostgreSQL dollar-quoted string literals ($tag$...$tag$ / $$...$$).
  • Updated EFCore instrumentation to select the backslash-escape dialect for MySQL providers and added/expanded unit tests + changelog entry.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/EntityFrameworkDiagnosticListenerTests.cs Adds coverage for detecting providers requiring backslash-escape parsing.
test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorTests.cs Adds regression tests for backslash-escaped quotes and dollar-quoted string handling.
src/Shared/SqlProcessor.cs Implements dialect-aware sanitization, including backslash-escaped quote handling and dollar-quoted literals.
src/Shared/DatabaseSemanticConventionHelper.cs Plumbs dialect flag into sanitization call for query text attributes.
src/OpenTelemetry.Instrumentation.EntityFrameworkCore/Implementation/EntityFrameworkDiagnosticListener.cs Detects MySQL providers and enables backslash-escape sanitization in EFCore query text tagging.
src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md Documents the sanitization fixes in the component changelog.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Shared/SqlProcessor.cs
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Aug 17, 2026

Copy link
Copy Markdown

Pull request dashboard status

Waiting on reviewers · refreshed 2026-08-25 20:52 UTC

Review the latest changes.

Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Anything look wrong? Report it with what you expected; it helps us improve the dashboard.

Avoid false positive for dollar-quoted values.
* Fixed query sanitization so that backslash-escaped quotes (`'a\'b'`) in
MySQL/MariaDB string literals and PostgreSQL dollar-quoted strings
(`$$...$$`) are correctly redacted.
([#4985](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4985))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
([#4985](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4985))
([#4986](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4986))

Comment thread src/Shared/SqlProcessor.cs Outdated
var closeOffset = sql.Slice(bodyStart).IndexOf(delimiter);
if (closeOffset < 0)
{
return false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning false here leaves state.ParsePosition unchanged, so the $ character falls through to ParseNextToken and is emitted verbatim. The body of the unterminated literal then flows through subsequent iterations as plain identifier/keyword tokens and is not redacted.

SanitizeStringLiteral handles its equivalent unterminated case by advancing state.ParsePosition to sql.Length and emitting ?, which keeps the security guarantee consistent. Worth doing the same here I think.

public static SqlStatementInfo GetSanitizedSql(string? sql, bool useBackslashEscapes = false) =>
sql != null
? useBackslashEscapes
? GetSanitizedSql(sql, BackslashEscapeCache, ref approxBackslashEscapeCacheCount, useBackslashEscapes: true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an application uses more tha one DB technology, one using backslash escapes and one not, I think we could end up with two caches up to 1000 entries each. Arguably, that may be okay, but we might need to document that, or maintain a shared count across both caches?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that's needed - if you have a multi-engine application and do enough SQL with it for there to be an internal cache with 2000 entries, that's probably neither here nor there in the grander scheme?

Comment thread src/Shared/SqlProcessor.cs Outdated
/// <returns>The sanitized SQL and query summary.</returns>
public static SqlStatementInfo GetSanitizedSql(string? sql, bool useBackslashEscapes = false) =>
sql != null
? useBackslashEscapes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is a little hard to read now with nested ternaries. Would an if/else be clearer? This may depend on the decision on cache count sharing.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ended up like this because the code I started with had "use expression bodied member", then once that was applied it had "simplify if statement", then that had "use ternary". I just kept accepting until the IDE stopped suggesting refactorings. I figured that was easier than having to suppress it to keep the simple if.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public static SqlStatementInfo GetSanitizedSql(string? sql, bool useBackslashEscapes = false)
{
    if (sql == null)
    {
        return default;
    }

    return useBackslashEscapes
        ? GetSanitizedSql(sql, BackslashEscapeCache, ref approxBackslashEscapeCacheCount, useBackslashEscapes: true)
        : GetSanitizedSql(sql, Cache, ref approxCacheCount, useBackslashEscapes: false);
}
image

then:

public static SqlStatementInfo GetSanitizedSql(string? sql, bool useBackslashEscapes = false)
{
    return sql == null
        ? default
        : useBackslashEscapes
        ? GetSanitizedSql(sql, BackslashEscapeCache, ref approxBackslashEscapeCacheCount, useBackslashEscapes: true)
        : GetSanitizedSql(sql, Cache, ref approxCacheCount, useBackslashEscapes: false);
}
image

// string-literal escape character unless the NO_BACKSLASH_ESCAPES SQL mode
// is enabled. The other supported engines follow the SQL standard where
// only a doubled quote ('') escapes a quote.
(_, var dbSystemName) = GetDbSystemNames(providerOrCommandName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anything actually detect if NO_BACKSLASH_ESCAPES is enabled or not and adjust the logic accordingly?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No.

- Fix CHANGELOG PR number.
- Sanitize unterminated literals.
- Make ternary slightly more readable.
- Add comment about `NO_BACKSLASH_ESCAPES`.
@martincostello martincostello added the keep-open Prevents issues and pull requests being closed as stale label Aug 22, 2026
Fix entry location.
Fix MySQL/MariaDB double-quoted literal (valid syntax when `ANSI_QUOTES` is disabled) not being sanitized.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:instrumentation.entityframeworkcore Things related to OpenTelemetry.Instrumentation.EntityFrameworkCore keep-open Prevents issues and pull requests being closed as stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants