From 856952f3182942b1c069b968abeb185d0000320c Mon Sep 17 00:00:00 2001 From: Kiro Agent <244629292+kiro-agent@users.noreply.github.com> Date: Tue, 23 Jun 2026 11:45:29 +0000 Subject: [PATCH] feat: add EF Core interpolated SQL methods as SQL injection sanitizers Entity Framework Core's FromSqlInterpolated, ExecuteSqlInterpolated, and ExecuteSqlInterpolatedAsync methods properly parameterize interpolated string values. Arguments to these methods should not be flagged as SQL injection sinks since EF Core handles parameterization automatically. --- .../security/dataflow/SqlInjectionQuery.qll | 21 ++++++++ .../CWE-089/SqlInjectionEfCoreInterpolated.cs | 50 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 csharp/ql/test/query-tests/Security Features/CWE-089/SqlInjectionEfCoreInterpolated.cs diff --git a/csharp/ql/lib/semmle/code/csharp/security/dataflow/SqlInjectionQuery.qll b/csharp/ql/lib/semmle/code/csharp/security/dataflow/SqlInjectionQuery.qll index addc19321776..3f036881646f 100644 --- a/csharp/ql/lib/semmle/code/csharp/security/dataflow/SqlInjectionQuery.qll +++ b/csharp/ql/lib/semmle/code/csharp/security/dataflow/SqlInjectionQuery.qll @@ -87,3 +87,24 @@ private class ExternalSqlInjectionSanitizer extends Sanitizer { private class SimpleTypeSanitizer extends Sanitizer, SimpleTypeSanitizedExpr { } private class GuidSanitizer extends Sanitizer, GuidSanitizedExpr { } + +/** + * A sanitizer for Entity Framework Core's interpolated SQL methods. + * Methods like `FromSqlInterpolated` and `ExecuteSqlInterpolated` accept + * `FormattableString` parameters and properly parameterize interpolated values, + * making them safe from SQL injection. + */ +private class EfCoreInterpolatedSanitizer extends Sanitizer { + EfCoreInterpolatedSanitizer() { + exists(MethodCall mc | + mc.getTarget().getName() in [ + "FromSqlInterpolated", "ExecuteSqlInterpolated", "ExecuteSqlInterpolatedAsync" + ] and + mc.getTarget() + .getDeclaringType() + .hasQualifiedName("Microsoft.EntityFrameworkCore", + ["RelationalDatabaseFacadeExtensions", "RelationalQueryableExtensions"]) and + this.getExpr() = mc.getAnArgument() + ) + } +} diff --git a/csharp/ql/test/query-tests/Security Features/CWE-089/SqlInjectionEfCoreInterpolated.cs b/csharp/ql/test/query-tests/Security Features/CWE-089/SqlInjectionEfCoreInterpolated.cs new file mode 100644 index 000000000000..0dbb8f5830c9 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-089/SqlInjectionEfCoreInterpolated.cs @@ -0,0 +1,50 @@ +namespace Test +{ + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Infrastructure; + + public class EfCoreInterpolatedTest : Controller + { + private DbContext _context; + + // GOOD: ExecuteSqlInterpolated properly parameterizes interpolated strings + public void SafeExecuteInterpolated(string userInput) + { + _context.Database.ExecuteSqlInterpolated($"DELETE FROM Users WHERE Name = {userInput}"); + } + + // BAD: ExecuteSqlRaw with string concatenation is vulnerable + public void UnsafeExecuteRaw(string userInput) + { + _context.Database.ExecuteSqlRaw("DELETE FROM Users WHERE Name = '" + userInput + "'"); + } + } +} + +namespace Microsoft.EntityFrameworkCore +{ + public class DbContext + { + public DatabaseFacade Database { get; } + } + + public class DbSet { } +} + +namespace Microsoft.EntityFrameworkCore.Infrastructure +{ + public class DatabaseFacade { } + + public static class RelationalDatabaseFacadeExtensions + { + public static int ExecuteSqlInterpolated(this DatabaseFacade database, System.FormattableString sql) => 0; + public static int ExecuteSqlRaw(this DatabaseFacade database, string sql, params object[] parameters) => 0; + } + + public static class RelationalQueryableExtensions + { + public static System.Linq.IQueryable FromSqlInterpolated(this DbSet source, System.FormattableString sql) where TEntity : class => null; + } +}