Chapter 24 · Security, Raw SQL, Secrets, Authorization Boundaries, and Abuse Resistance

Least-Privilege Database Accounts, Migration Permissions, Row-Level Security, and Tenant Defense in Depth

Combine runtime least privilege, separate migration ownership, ServiceHub tenant constraints, EF Core query filters, application authorization, and optional database row-level security so bypassing one layer does not automatically expose or mutate another tenant’s data.

Advanced180–240 minutesleast-privilege + tenant defense labEF Core 10.0.11 · Microsoft.EntityFrameworkCore.Sqlite 10.0.11 · dotnet-ef 10.0.11 · .NET 10.0.11 · SDK 10.0.400Mandatory free local SQLite path · SQL Server/Azure SQL RLS and managed identity optional/provider-specificSecurity/package/platform status reviewed: August 27, 2026

Learning outcomes

01

Separate runtime DML authority from migration/DDL authority and explain why the application rarely needs schema-owner privileges.

02

Combine application authorization, EF Core tenant filters, tenant-aware constraints, and database least privilege as independent controls.

03

Describe SQL Server/Azure SQL row-level security filter and block predicates without presenting RLS as portable EF behavior.

04

Restrict administrative and raw-SQL paths through explicit authorization and separate operational identities.

05

Test bypass scenarios such as IgnoreQueryFilters, direct SQL, wrong-tenant writes, and migration credential misuse.

06

Build a production permission/runbook model that records grants, migration ownership, tenant context, audit evidence, and rollback safety.

1. The database account defines the blast radius after an application bug

Suppose a ServiceHub endpoint accidentally calls IgnoreQueryFilters() or a raw SQL statement omits the tenant predicate. If the runtime database identity can read every tenant table, alter schema, disable security policies, create users and drop databases, a single application defect inherits that entire blast radius.

Least privilege means granting an identity only the operations and objects needed for its role. Runtime data access usually needs a narrower set of Data Manipulation Language (DML) permissions than schema deployment, which uses Data Definition Language (DDL).

2. Separate runtime and migration identities

Identity Typical authority Should not need
ServiceHub runtime SELECT/INSERT/UPDATE/DELETE on required objects; execute selected routines CREATE/ALTER/DROP schema, create users, disable RLS, broad admin roles
Migration/deployment owner reviewed DDL needed to advance schema/migrations application request traffic
Read-only support/reporting approved SELECT/views writes, schema changes, broad raw access
Break-glass admin time-bound emergency authority normal application traffic

The exact statements/roles vary by SQL Server, PostgreSQL, MySQL/MariaDB, Oracle and SQLite. The principle is portable; the grants are not. For SQLite, operating-system file permissions are a major part of access control because there is no server login/role model.

3. Keep the Chapter 22 tenant controls layered

ServiceHub already has EF Core named tenant/soft-delete/active filters and tenant-aware composite relationship constraints. Each protects a different failure mode:

Layer Stops Does not stop
Application authorization caller choosing an unauthorized tenant/admin operation a database client bypassing the app
EF tenant filter ordinary queries accidentally omitting tenant predicate IgnoreQueryFilters/raw DB access/misconfiguration
Tenant-aware unique/FK constraints cross-tenant relationship mistakes reading another tenant row
Runtime DB permissions application account performing ungranted operations bad reads/writes that are still granted
Database RLS where supported row access from any query using that DB identity/context misconfigured security context/policy or privileged bypass paths

Defense in depth is not duplication: each layer changes the failure mode and provides an independent test/operational control.

4. SQL Server/Azure SQL RLS: filter predicates and block predicates

SQL Server row-level security (RLS) uses a security policy backed by inline table-valued predicate functions. Filter predicates restrict visible rows for reads and applicable update/delete operations. Block predicates can reject writes that would violate the policy. This is database behavior, not an EF feature, and must be tested against SQL Server/Azure SQL rather than SQLite.

T-SQL · conceptual ServiceHub tenant policy
CREATE SCHEMA Security;GOCREATE FUNCTION Security.fn_tenant_access(@tenant_id nvarchar(64))RETURNS TABLEWITH SCHEMABINDINGASRETURN SELECT 1 AS allowedWHERE @tenant_id = CAST(SESSION_CONTEXT(N'TenantId') AS nvarchar(64));GOCREATE SECURITY POLICY Security.WorkOrderTenantPolicyADD FILTER PREDICATE Security.fn_tenant_access(tenant_id)    ON dbo.work_orders,ADD BLOCK PREDICATE Security.fn_tenant_access(tenant_id)    ON dbo.work_orders AFTER INSERT,ADD BLOCK PREDICATE Security.fn_tenant_access(tenant_id)    ON dbo.work_orders AFTER UPDATEWITH (STATE = ON);GO

The application must set and verify the per-connection/session tenant context correctly. Pooled physical connections make session state security-sensitive; clear/reset or deterministically overwrite tenant context whenever a connection is checked out. Never assume EF DbContext pooling and SQL connection pooling reset arbitrary server session context for you.

5. Deliberate failure: use one db_owner-style credential everywhere

WRONG production topology
web requests ─────┐background jobs ───┼── same schema-owner credential ── databasemigration job ─────┤support script ────┘

This collapses all trust boundaries. A SQL injection bug can become DDL; a compromised background worker can alter security policy; an accidental migration call from every replica can race schema changes. Repair it with separate identities, narrowly scoped grants, one coordinated migration owner, and explicit admin pathways.

6. Raw SQL/admin paths need authorization before database execution

An internal endpoint that executes a reviewed maintenance query is still an administrative capability. Protect it with authentication, authorization policy, audited intent, bounded query shapes, and a database identity appropriate to that operation. The example uses a deliberately small ITenantAuditAuthorizer seam so the security decision is explicit and independently testable rather than coupled to a particular web framework. Never let an ordinary user toggle IgnoreQueryFilters() through a generic flag such as ?includeAllTenants=true.

C# · explicit admin service boundary
public interface ITenantAuditAuthorizer{    Task<bool> CanReadDeletedAsync(        ClaimsPrincipal user, string tenantId, CancellationToken ct);}public sealed record AuditRow(int Id, string WorkOrderNumber);public sealed class TenantAuditReader(    IDbContextFactory<ServiceHubContext> contextFactory,    ITenantAuditAuthorizer authorization){    public async Task<IReadOnlyList<AuditRow>> ReadDeletedAsync(        ClaimsPrincipal user,        string tenantId,        CancellationToken ct)    {        if (!await authorization.CanReadDeletedAsync(user, tenantId, ct))            throw new UnauthorizedAccessException();        await using var db = await contextFactory.CreateDbContextAsync(ct);        db.AssignTenant(tenantId);        return await db.WorkOrders            .IgnoreQueryFilters("SoftDeleteFilter")            .Where(w => w.TenantId == tenantId && w.IsDeleted)            .Select(w => new AuditRow(w.Id, w.WorkOrderNumber))            .ToListAsync(ct);    }}

Selective filter disabling is paired with an explicit tenant predicate and authorization check. The database account should still be unable to alter security policy or schema.

7. Verify privileges and RLS independently of EF

Integration tests should connect using the actual runtime test identity, not a schema owner. Attempt an operation the runtime should not have—such as creating a table or selecting a restricted admin object—and assert denial. For RLS, execute the same SQL under two tenant contexts and verify the database changes result visibility even if EF filters are absent.

Security acceptance evidence
runtime identity:  SELECT/INSERT/UPDATE/DELETE approved ServiceHub objects: allowed  CREATE TABLE / ALTER SECURITY POLICY: denied  cross-tenant FK mutation: denied by constraint/policy  IgnoreQueryFilters ordinary endpoint: not reachable by authorization  SQL Server RLS test (optional): tenant-a session cannot read tenant-b rowsmigration identity:  reviewed schema migration: allowed  application request path: not configured with this credential

8. Mandatory lab: prove defense in depth on free SQLite, then add optional RLS

  1. Use Chapter 22’s SQLite tenant schema and composite tenant-aware relationship constraint.
  2. Write an adversarial test that calls an internal query with filters disabled but still requires an explicit authorized tenant.
  3. Attempt a cross-tenant Technician/WorkOrder relationship and verify the database rejects it.
  4. Document the SQLite file/OS permission boundary and why it differs from a server database role model.
  5. For an available SQL Server/Azure SQL lab, create a low-privileged test runtime user and optional RLS policy; verify read filtering and a blocked wrong-tenant write.
  6. Use a separate migration credential to apply schema changes, then confirm the runtime identity cannot execute equivalent DDL.
  7. Clean up test users/policies/database resources.

9. Production judgment and bridge

EF Core is not a database firewall. Secure production access combines caller authorization, safe query construction, tenant-aware modeling, least-privileged runtime credentials, coordinated migration ownership, provider/database controls such as RLS where justified, and adversarial tests using the same identities. Lesson 5 finishes the chapter by protecting the diagnostic and backup artifacts created when these systems fail.

Check your understanding

  1. Why should runtime and migration credentials differ?
  2. What is the difference between an EF query filter and SQL Server RLS?
  3. What does a SQL Server RLS block predicate do?
  4. Why is connection pooling relevant to RLS session context?
  5. Should an ordinary request be able to request IgnoreQueryFilters?
  6. How do you prove least privilege?
Review the answers

1. Runtime usually needs narrow DML while migrations need DDL; separation limits the blast radius of application bugs/compromise and prevents every app instance from becoming a schema owner.

2. An EF filter is application-generated query policy and can be bypassed/misconfigured; RLS is enforced by the SQL Server database policy for queries using that database context/identity.

3. It rejects writes that violate the security predicate for configured insert/update/delete operations.

4. Physical sessions can be reused, so tenant session state must be set/reset deterministically to prevent cross-request leakage.

5. No. Filter bypass is an administrative/data-lifecycle capability and must sit behind explicit authorization and constrained code paths.

6. Connect with the runtime identity and assert required operations succeed while prohibited DDL/admin/cross-tenant operations fail.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.

\n