data-security
Installation
SKILL.md
SQL / data-layer security
The database is the crown jewels and the last line of defense - by the time a request reaches it, every app-layer control has either held or failed. This is the persistence-layer map: how injection, over-privilege, tenant leakage, and secret handling show up at the SQL boundary and what to do about each. It pairs with dotnet-security (the app-layer EF and access-control surface; the ORM mechanics behind it are dotnet-data-access), dotnet-cryptography (the primitives - KDF, AES-GCM, constant-time compare), and dotnet-migrate (the reversible, data-loss-safe migration workflow). The rule under all of it: the database enforces its own security, because an app bug should not become a full-table breach.
Injection - close every sink
- Parameterize, always. Never build SQL by string concatenation or interpolation. In EF Core,
FromSqlInterpolated/FromSqlparameterize the interpolated values; rawFromSqlRaw/ExecuteSqlRawwith a concatenated string does not - aFromSqlRaw($"... {userInput}")is injection. ADO.NET usesSqlParameter/NpgsqlParameter, never a formatted command text.
var bad = db.Users.FromSqlRaw("select * from users where name = '" + name + "'"); // injection - the string is built before the API sees it
var safe = db.Users.FromSql($"select * from users where name = {name}"); // safe - each interpolated value becomes a DbParameter
- Dynamic SQL in a stored procedure is still injectable: build it with
sp_executesql(SQL Server) orEXECUTE ... USING(Postgres) passing parameters, neverEXEC(@sql)on a concatenated string. A proc is not a safe boundary by virtue of being a proc. - Identifiers can't be parameterized - a table or column name chosen from user input must be validated against an allowlist, never interpolated.
- ORDER BY / dynamic filters from the client map to a fixed allowlist of columns and directions, never passed through as text.