Restrict sensitive APIs to reviewed components
Matches application methods that call a sensitive API directly, while not being declared in one of the reviewed components named in the rule source: process execution (Process.Start()), dynamic loading (Assembly.Load(), Activator.CreateInstance(string), Type.GetType(string)), raw SQL (DbCommand.CommandText, EF Core Raw entry points, Dapper) and cryptographic primitives (Aes, RSA, HMAC, RandomNumberGenerator, Rfc2898DeriveBytes...).
This rule expresses an architectural policy rather than a defect: the few places allowed to touch such APIs are known, reviewed and kept small, so that argument escaping, path validation, parameterization and key management are implemented once. Every other call site is a bypass to review. The rule ships inactive: edit the reviewedTypes and reviewedNamespaces lists at the top of the query with the names of your wrappers, trim the API families you don't care about, then activate it. Until the lists are filled, every direct call would be reported.
Flagged Code
This rule expresses an architectural policy rather than a defect: the few places allowed to touch such APIs are known, reviewed and kept small, so that argument escaping, path validation, parameterization and key management are implemented once. Every other call site is a bypass to review. The rule ships inactive: edit the reviewedTypes and reviewedNamespaces lists at the top of the query with the names of your wrappers, trim the API families you don't care about, then activate it. Until the lists are filled, every direct call would be reported.
Flagged Code
public class ReportExporter {
public void OpenInViewer(string reportPath) {
Process.Start("viewer.exe", reportPath); // flagged: direct call outside wrappers
}
}
Fixed Codepublic class ReportExporter {
public void OpenInViewer(string reportPath) {
_processRunner.Run("viewer.exe", reportPath); // reviewed wrapper listed in the rule
}
}
namespace MyCompany.Infrastructure.Security {
public sealed class ProcessRunner { // validates and escapes arguments once
public void Run(string exe, params string[] args) { /* ArgumentList, no shell */ }
}
}How to fix
Route the call through the reviewed wrapper that centralizes the safety checks for that API family. When the calling type is itself a legitimate wrapper, add its full name to the reviewedTypes list (or its namespace to reviewedNamespaces) in the rule source.
When an API family is not a concern for the application, remove it from the sensitiveMembers definition rather than suppressing issues one by one.
When an API family is not a concern for the application, remove it from the sensitiveMembers definition rather than suppressing issues one by one.
CQLinq source
This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.
// <Name>Restrict sensitive APIs to reviewed components</Name>
// <Id>ND3138:RestrictSensitiveApisToReviewedComponents</Id>
warnif count > 0
//------------------------------------------------------------------------------------
// CONFIGURATION: name here the reviewed components (types or namespaces) that are
// allowed to call the sensitive APIs directly. Every other application method calling
// one of these APIs is reported. Activate the rule once this list is filled.
//------------------------------------------------------------------------------------
let reviewedTypes = Application.Types.WithFullNameIn(
"MyCompany.Infrastructure.Security.ProcessRunner",
"MyCompany.Infrastructure.Security.PluginLoader",
"MyCompany.Infrastructure.Security.CryptoService").ToHashSetEx()
let reviewedNamespaces = Application.Namespaces.WithNameIn(
"MyCompany.Infrastructure.Data").ToHashSetEx()
//------------------------------------------------------------------------------------
// SENSITIVE APIS: each family can be trimmed or extended.
// Third-party members are part of the code model only when the application uses them.
//------------------------------------------------------------------------------------
let sensitiveMembers = ThirdParty.Methods.Where(m =>
// Process execution
(m.ParentType.FullName == "System.Diagnostics.Process" && m.SimpleName == "Start") ||
(m.ParentType.FullName == "System.Diagnostics.ProcessStartInfo" && m.IsConstructor) ||
// Dynamic code loading and late-bound instantiation
(m.ParentType.FullName == "System.Reflection.Assembly" && m.SimpleName.StartsWithAny("Load", "ReflectionOnlyLoad", "UnsafeLoadFrom")) ||
(m.ParentType.FullName == "System.Runtime.Loader.AssemblyLoadContext" && m.SimpleName.StartsWith("LoadFrom")) ||
(m.ParentType.FullName == "System.Activator" && m.SimpleName.StartsWith("CreateInstance") && m.Name.Contains("String")) ||
(m.ParentType.FullName == "System.Type" && m.SimpleName == "GetType" && m.Name.Contains("String")) ||
// Raw SQL
(m.ParentType.FullName.EndsWithAny("DbCommand", "SqlCommand", "OracleCommand", "NpgsqlCommand", "MySqlCommand", "SqliteCommand") &&
(m.SimpleName == "set_CommandText" || (m.IsConstructor && m.Name.Contains("String")))) ||
m.SimpleNameEqualsAny("FromSqlRaw", "ExecuteSqlRaw", "ExecuteSqlRawAsync", "SqlQueryRaw", "ExecuteSqlCommand", "ExecuteSqlCommandAsync") ||
(m.ParentType.FullName.StartsWith("Dapper.") && m.SimpleName.StartsWithAny("Query", "Execute")) ||
// Cryptographic primitives
(m.ParentType.FullName.StartsWith("System.Security.Cryptography.") &&
m.ParentType.SimpleNameEqualsAny("Aes", "AesGcm", "AesCcm", "ChaCha20Poly1305", "TripleDES", "DES", "RC2", "Rijndael",
"RSA", "ECDsa", "ECDiffieHellman", "DSA", "HMAC", "HashAlgorithm", "SymmetricAlgorithm",
"AsymmetricAlgorithm", "RandomNumberGenerator", "ProtectedData", "Rfc2898DeriveBytes") &&
(m.IsConstructor || (m.IsStatic && m.SimpleName.StartsWithAny("Create", "Protect", "Unprotect", "Pbkdf2")))))
.ToHashSetEx()
where sensitiveMembers.Any()
from m in Application.Methods.UsingAny(sensitiveMembers)
// The method's type (or the reviewed type it is nested in: lambda closures, nested helpers)
// or its namespace is a reviewed component.
let reviewedTypesAndNested = reviewedTypes.Concat(reviewedTypes.SelectMany(t => t.NestedTypes)).ToHashSetEx()
where !reviewedTypesAndNested.Contains(m.ParentType) &&
!reviewedNamespaces.Contains(m.ParentNamespace)
let sensitiveMembersUsed = m.MethodsCalled.Intersect(sensitiveMembers).ToArray()
select new {
m,
sensitiveMembersUsed,
Debt = (10 + 5 * sensitiveMembersUsed.Length).ToMinutes().ToDebt(),
Severity = Severity.Medium
}
//<Expl>
// The method *{0}* calls the sensitive API *{1}* directly, outside of the
// reviewed components listed in the rule. Route the call through the
// reviewed wrapper, or add the declaring type to the list once it has
// been reviewed.
//</Expl>
// <Desc>
// Matches application methods that call a sensitive API directly, while
// not being declared in one of the reviewed components named in the
// rule source: process execution (*Process.Start()*), dynamic loading
// (*Assembly.Load()*, *Activator.CreateInstance(string)*,
// *Type.GetType(string)*), raw SQL (*DbCommand.CommandText*, EF Core
// *Raw* entry points, Dapper) and cryptographic primitives (*Aes*, *RSA*,
// *HMAC*, *RandomNumberGenerator*, *Rfc2898DeriveBytes*...).
//
// This rule expresses an **architectural policy** rather than a defect:
// the few places allowed to touch such APIs are known, reviewed and kept
// small, so that argument escaping, path validation, parameterization
// and key management are implemented once. Every other call site is a
// bypass to review. The rule ships **inactive**: edit the *reviewedTypes*
// and *reviewedNamespaces* lists at the top of the query with the names
// of your wrappers, trim the API families you don't care about, then
// activate it. Until the lists are filled, every direct call would be
// reported.
//
// **Flagged Code**
// ```csharp
// public class ReportExporter {
// public void OpenInViewer(string reportPath) {
// Process.Start("viewer.exe", reportPath); // flagged: direct call outside wrappers
// }
// }
// ```
//
// **Fixed Code**
// ```csharp
// public class ReportExporter {
// public void OpenInViewer(string reportPath) {
// _processRunner.Run("viewer.exe", reportPath); // reviewed wrapper listed in the rule
// }
// }
// namespace MyCompany.Infrastructure.Security {
// public sealed class ProcessRunner { // validates and escapes arguments once
// public void Run(string exe, params string[] args) { /* ArgumentList, no shell */ }
// }
// }
// ```
// </Desc>
// <HowToFix>
// Route the call through the reviewed wrapper that centralizes the
// safety checks for that API family. When the calling type is itself a
// legitimate wrapper, add its full name to the *reviewedTypes* list (or
// its namespace to *reviewedNamespaces*) in the rule source.
//
// When an API family is not a concern for the application, remove it
// from the *sensitiveMembers* definition rather than suppressing issues
// one by one.
// </HowToFix>
Open in the interactive Rules Explorer · All default rules
Security: all 39 items
- Software Composition Analysis (SCA) ND3120
- Transitive Software Composition Analysis (SCA) ND3121
- Don't hard-code secrets in constants ND3114
- Review credentials built from hard-coded values ND3115
- Don't use insecure deserializers ND3110
- Don't use weak hash algorithms (MD5, SHA1) ND3109
- Don't use DES/3DES/RC2 weak cipher algorithms ND3102
- Don't use weak password-based key derivation ND3123
- Don't use the ECB encryption mode ND3132
- Don't disable certificate validation ND3103
- Don't hard-code TLS/SSL protocol versions ND3122
- Review commands vulnerable to SQL injection ND3107
- Review data adapters vulnerable to SQL injection ND3108
- Review raw SQL built from composed strings ND3128
- Review LDAP filters composed from strings ND3137
- Don't enable external XML entity resolution (XXE) ND3111
- Review processes started with a composed arguments string ND3124
- Review web endpoints building file paths from their parameters ND3129
- Review custom archive extraction (Zip Slip) ND3135
- Review assemblies and types loaded from runtime-provided names ND3125
- Review web endpoints without [Authorize] nor [AllowAnonymous] ND3118
- Review state-changing endpoints opened to anonymous callers ND3130
- Review state-changing MVC actions without anti-forgery validation ND3119
- Review HTML-encoding bypasses ND3133
- Review explicitly disabled request validation ND3134
- Review redirects to a URL that is not validated as local ND3126
- Review request-influenced outbound HTTP calls (SSRF) ND3136
- Review insecure ASP.NET Core CORS policies and unguarded developer exception page ND3116
- Review cookies created without HttpOnly and Secure flags ND3117
- Review regular expressions without a match timeout (ReDoS) ND3112
- Review secrets compared with non-constant-time equality ND3113
- Review secrets passed to logging methods ND3127
- Review security operations that log nothing ND3131
- Don't use System.Random for security purposes ND3101
- Don't use CoSetProxyBlanket and CoInitializeSecurity ND3100
- Review publicly visible event handlers ND3104
- Pointers should not be publicly visible ND3105
- Seal methods that satisfy non-public interfaces ND3106
- Restrict sensitive APIs to reviewed components ND3138