Don't hard-code secrets in constants
Matches const string fields, and static readonly string fields whose static constructor only manipulates strings, named like a password, API key, private key, connection string, token, client secret or signing key. A literal secret ships in every build, leaks through decompilation, source control history and crash dumps, and cannot be rotated without a redeploy.
Names that merely designate a setting (PasswordFieldName, ApiKeyHeader, PasswordMinLength, PasswordHash...) or a user-facing text (PasswordTooltip, InvalidSecretMessage...) are excluded. The rule cannot see the literal value: a placeholder like "CHANGE_ME" or an empty string is reported too, suppress such issues.
This is CWE-798 Use of Hard-coded Credentials.
Flagged Code
Names that merely designate a setting (PasswordFieldName, ApiKeyHeader, PasswordMinLength, PasswordHash...) or a user-facing text (PasswordTooltip, InvalidSecretMessage...) are excluded. The rule cannot see the literal value: a placeholder like "CHANGE_ME" or an empty string is reported too, suppress such issues.
This is CWE-798 Use of Hard-coded Credentials.
Flagged Code
static class DbSettings {
const string Password = "P@ssw0rd!"; // flagged: const secret
static readonly string ApiKey = "sk-live-" + "4f9c2e1a"; // flagged: only string ops
}
Fixed Codeclass DbSettings {
readonly string _password;
readonly string _apiKey;
DbSettings(IConfiguration config) {
_password = config["Db:Password"]; // user-secrets, environment or key vault
_apiKey = Environment.GetEnvironmentVariable("API_KEY");
}
}How to fix
Read the secret at runtime from a configuration source that is not committed: environment variables, dotnet user-secrets in development, a secret store (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault) or a protected configuration provider in production. Rotate the secret that was committed. Once the field is gone, scan the source history to purge the value.
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>Don't hard-code secrets in constants</Name>
// <Id>ND3114:DontHardCodeSecretsInConstants</Id>
warnif count > 0
from f in Application.Fields
where f.FieldType != null &&
f.FieldType.FullName == "System.String" &&
!f.IsGeneratedByCompiler &&
// Names that designate a secret value...
f.NameLike(@"(password|passwd|pwd|secret|apikey|api_key|privatekey|private_key|connectionstring|connection_string|accesstoken|access_token|authtoken|auth_token|bearertoken|bearer_token|refreshtoken|refresh_token|clientsecret|client_secret|encryptionkey|encryption_key|signingkey|signing_key|securitykey|security_key|hmackey|hmac_key|masterkey|master_key|credential)\i") &&
// ...but not a user-facing text mentioning it...
!f.NameLike(@"(tooltip|message|msg|failure|error|label|title|hint|help|desc|caption|prompt|question|warning|notice|info|text)\i") &&
// ...nor the name of a setting, header, claim, validation rule or hashed form of it.
// (key$ matches "ApiKeyKey"-like names that hold the *name* of the setting storing the secret.)
!f.NameLike(@"(name|names|length|len|regex|pattern|format|header|headers|param|parameter|field|property|prop|placeholder|prefix|suffix|url|uri|path|type|kind|id|hash|hashed|salt|policy|option|options|setting|settings|section|claim|claimtype|scheme|scope|template|required|min|max|count|size|resource|expiration|expiry|lifetime|timeout|attempts|provider|source|env|environment|variable|var|key)$\i")
let cctors = f.ParentType.Methods.Where(m => m.IsClassConstructor).ToArray()
// const: the value is a literal for sure.
// static readonly: the value is a literal unless the static constructor calls anything but String members.
where f.IsLiteral ||
(f.IsStatic && f.IsInitOnly && cctors.Length > 0 &&
cctors.All(cctor => !cctor.MethodsCalled.Any(mc => mc.ParentType.FullName != "System.String")))
select new {
f,
IsConst = f.IsLiteral,
Debt = 30.ToMinutes().ToDebt(),
Severity = Severity.High
}
//<Expl>
// The {1?constant:static read-only} field *{0}* is named like a secret and holds a
// literal value baked into the assembly. Anyone with the binary (or the
// source repository) can read it.
//</Expl>
//<Desc>
// Matches *const* string fields, and *static readonly* string fields whose
// static constructor only manipulates strings, named like a password,
// API key, private key, connection string, token, client secret or
// signing key. A literal secret ships in every build, leaks through
// decompilation, source control history and crash dumps, and cannot be
// rotated without a redeploy.
//
// Names that merely designate a setting (*PasswordFieldName*,
// *ApiKeyHeader*, *PasswordMinLength*, *PasswordHash*...) or a user-facing
// text (*PasswordTooltip*, *InvalidSecretMessage*...) are excluded.
// The rule cannot see the literal value: a placeholder like *"CHANGE_ME"*
// or an empty string is reported too, suppress such issues.
//
// This is CWE-798 *Use of Hard-coded Credentials*.
//
// **Flagged Code**
// ```csharp
// static class DbSettings {
// const string Password = "P@ssw0rd!"; // flagged: const secret
// static readonly string ApiKey = "sk-live-" + "4f9c2e1a"; // flagged: only string ops
// }
// ```
//
// **Fixed Code**
// ```csharp
// class DbSettings {
// readonly string _password;
// readonly string _apiKey;
// DbSettings(IConfiguration config) {
// _password = config["Db:Password"]; // user-secrets, environment or key vault
// _apiKey = Environment.GetEnvironmentVariable("API_KEY");
// }
// }
// ```
//</Desc>
//<HowToFix>
// Read the secret at runtime from a configuration source that is not
// committed: environment variables, *dotnet user-secrets* in development,
// a secret store (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault)
// or a protected configuration provider in production. Rotate the secret
// that was committed. Once the field is gone, scan the source history to
// purge the value.
//</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