Describe the bug
After upgrading from Microsoft.Data.SqlClient v6.x to v7.x, SqlBulkCopy now requires the application SQL login to have metadata visibility sufficient for a query against sys.all_columns.
This appears related to PR #3590, which changed SqlBulkCopy target-column discovery from the previous metadata-discovery path:
SET FMTONLY ON;
SELECT * FROM {table};
SET FMTONLY OFF;
to a query against sys.all_columns in order to support hidden columns, such as those used by temporal tables.
I understand the motivation for the change, and the hidden-column behavior is a real improvement. However, this appears to introduce a runtime breaking change for applications running under least-privilege SQL logins that previously worked with SqlBulkCopy in v6.
In our environment, the application login has permission to insert into the target table, but it does not have the broader metadata visibility needed for the new sys.all_columns query. Our DBA/security team is not willing to grant broader catalog visibility to the application runtime identity.
This is not being reported as a SqlClient security vulnerability. The concern is that v7 changes the permission contract for existing SqlBulkCopy callers and conflicts with least-privilege database policies in hardened environments.
To reproduce
The core repro is:
- Create a SQL login/user with permission to insert into a target table.
- Do not grant that login broader catalog metadata visibility.
- Execute a
SqlBulkCopy.WriteToServer(...) call targeting that table using Microsoft.Data.SqlClient v6.x.
- Observe that the operation succeeds.
- Upgrade to
Microsoft.Data.SqlClient v7.x.
- Execute the same
SqlBulkCopy.WriteToServer(...) call.
- Observe a permission error related to the new
sys.all_columns query.
Example repro:
using System.Data;
using Microsoft.Data.SqlClient;
var connectionString = "Server=localhost;Database=SqlBulkCopyRepro;User Id=bulkcopy_app;Password=your-password;";
var table = new DataTable();
table.Columns.Add("Name", typeof(string));
table.Rows.Add("Example row");
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
using var bulkCopy = new SqlBulkCopy(connection)
{
DestinationTableName = "dbo.BulkCopyTarget"
};
bulkCopy.ColumnMappings.Add("Name", "Name");
await bulkCopy.WriteToServerAsync(table);
Example database setup:
CREATE DATABASE SqlBulkCopyRepro;
GO
USE SqlBulkCopyRepro;
GO
CREATE TABLE dbo.BulkCopyTarget
(
Id int IDENTITY(1,1) NOT NULL CONSTRAINT PK_BulkCopyTarget PRIMARY KEY,
Name nvarchar(100) NOT NULL
);
GO
CREATE LOGIN bulkcopy_app WITH PASSWORD = 'your-password';
GO
CREATE USER bulkcopy_app FOR LOGIN bulkcopy_app;
GO
GRANT INSERT ON dbo.BulkCopyTarget TO bulkcopy_app;
GO
-- Intentionally do not grant broader metadata visibility / catalog permissions.
-- This is the least-privilege configuration that worked with Microsoft.Data.SqlClient v6.x.
Expected behavior
SqlBulkCopy against a normal table that does not require hidden-column handling should not require broader metadata/catalog permissions than were required in v6.
A SqlBulkCopy operation that worked in v6 with a least-privilege application login should not fail after upgrading to v7 solely because the implementation now queries sys.all_columns.
Callers that do not use hidden columns should have a compatibility path that preserves the previous least-privilege behavior.
Further technical details
Microsoft.Data.SqlClient version: v7.x
Previous working Microsoft.Data.SqlClient version: v6.x
.NET target: .NET 10.0
SQL Server version: SQL Server 2017
OS (App): Linux Docker container / Kubernetes
OS (DB): Microsoft SQL Server 2017 (RTM-CU24) (KB5001228) - 14.0.3391.2 (X64) Apr 28 2021 10:32:18 Copyright (C) 2017 Microsoft Corporation Enterprise Edition: Core-based Licensing (64-bit) on Windows Server 2016 Datacenter 10.0 (Build 14393: ) (Hypervisor)
Additional context
PR #3590 changed SqlBulkCopy column discovery to query sys.all_columns to support hidden columns. That is understandable for hidden-column scenarios, but it also changes the permission contract for existing SqlBulkCopy callers.
This blocks upgrading from v6 to v7 in environments where application logins are intentionally restricted and DBAs are not willing to grant broader catalog metadata visibility to runtime application identities.
Would the SqlClient team consider one of the following mitigations?
-
AppContext switch — expose a switch such as Switch.Microsoft.Data.SqlClient.SqlBulkCopy.UseLegacyColumnDiscovery that restores the previous metadata discovery behavior for callers who do not need hidden-column support.
-
Graceful fallback — attempt the sys.all_columns query first, and fall back to the previous metadata discovery path if a permissions error is returned.
-
Conditional/lazy metadata path — avoid querying sys.all_columns unless hidden-column handling is required or explicitly requested.
Option 2 or 3 would be transparent to callers. Option 1 would at least give affected applications an escape hatch without requiring a DBA security policy change.
References:
Describe the bug
After upgrading from
Microsoft.Data.SqlClientv6.x to v7.x,SqlBulkCopynow requires the application SQL login to have metadata visibility sufficient for a query againstsys.all_columns.This appears related to PR #3590, which changed
SqlBulkCopytarget-column discovery from the previous metadata-discovery path:to a query against
sys.all_columnsin order to support hidden columns, such as those used by temporal tables.I understand the motivation for the change, and the hidden-column behavior is a real improvement. However, this appears to introduce a runtime breaking change for applications running under least-privilege SQL logins that previously worked with
SqlBulkCopyin v6.In our environment, the application login has permission to insert into the target table, but it does not have the broader metadata visibility needed for the new
sys.all_columnsquery. Our DBA/security team is not willing to grant broader catalog visibility to the application runtime identity.This is not being reported as a SqlClient security vulnerability. The concern is that v7 changes the permission contract for existing
SqlBulkCopycallers and conflicts with least-privilege database policies in hardened environments.To reproduce
The core repro is:
SqlBulkCopy.WriteToServer(...)call targeting that table usingMicrosoft.Data.SqlClientv6.x.Microsoft.Data.SqlClientv7.x.SqlBulkCopy.WriteToServer(...)call.sys.all_columnsquery.Example repro:
Example database setup:
Expected behavior
SqlBulkCopyagainst a normal table that does not require hidden-column handling should not require broader metadata/catalog permissions than were required in v6.A
SqlBulkCopyoperation that worked in v6 with a least-privilege application login should not fail after upgrading to v7 solely because the implementation now queriessys.all_columns.Callers that do not use hidden columns should have a compatibility path that preserves the previous least-privilege behavior.
Further technical details
Microsoft.Data.SqlClient version: v7.x
Previous working Microsoft.Data.SqlClient version: v6.x
.NET target: .NET 10.0
SQL Server version: SQL Server 2017
OS (App): Linux Docker container / Kubernetes
OS (DB): Microsoft SQL Server 2017 (RTM-CU24) (KB5001228) - 14.0.3391.2 (X64) Apr 28 2021 10:32:18 Copyright (C) 2017 Microsoft Corporation Enterprise Edition: Core-based Licensing (64-bit) on Windows Server 2016 Datacenter 10.0 (Build 14393: ) (Hypervisor)
Additional context
PR #3590 changed
SqlBulkCopycolumn discovery to querysys.all_columnsto support hidden columns. That is understandable for hidden-column scenarios, but it also changes the permission contract for existingSqlBulkCopycallers.This blocks upgrading from v6 to v7 in environments where application logins are intentionally restricted and DBAs are not willing to grant broader catalog metadata visibility to runtime application identities.
Would the SqlClient team consider one of the following mitigations?
AppContext switch — expose a switch such as
Switch.Microsoft.Data.SqlClient.SqlBulkCopy.UseLegacyColumnDiscoverythat restores the previous metadata discovery behavior for callers who do not need hidden-column support.Graceful fallback — attempt the
sys.all_columnsquery first, and fall back to the previous metadata discovery path if a permissions error is returned.Conditional/lazy metadata path — avoid querying
sys.all_columnsunless hidden-column handling is required or explicitly requested.Option 2 or 3 would be transparent to callers. Option 1 would at least give affected applications an escape hatch without requiring a DBA security policy change.
References: