Best Practices¶
SSRF Prevention¶
-
User-controlled input is never used directly as a request target.
Any URL, host, path, or query value originating from user input is validated, canonicalized, and restricted before being used in HttpClient, HttpRequestMessage, or similar network APIs. -
Server-controlled base host is enforced.
All outbound requests use a server-defined BaseAddress.
User input may only influence validated path segments or query parameters, never the scheme, host, or port. -
Strict allowlist for external hosts.
When business requirements mandate user-provided URLs (e.g., OAuth authority endpoints), the destination host is validated against a hard-coded allowlist.
IP literals, localhost, private/internal network addresses, and unknown domains are rejected. -
Scheme and redirect restrictions.
Only HTTPS is allowed for outbound requests.
Automatic redirects are explicitly disabled to prevent redirect-based SSRF bypasses. -
Safe path and query validation.
Path and query parameters are validated using allowlist-based checks to block: - Path traversal (/ , \ , ..)
- Encoded payloads (%)
- Query or fragment injection (?, #, &, =)
-
Control characters and whitespace
-
Format-specific validation is mandatory.
If an input has a well-defined format (e.g., ASCII-only IDs, version strings, hashes, fixed path segments), it must be validated using a strict allowlist regex that enforces the expected format exactly.
Generic or permissive validation is not allowed for such inputs. -
Server-provided continuation URLs are re-validated.
URLs or query fragments received from server responses (e.g., next, pagination links) are not trusted implicitly.
Their host, scheme, and structure are validated to ensure they still target the expected server-controlled domain before being reused. -
No implicit host override via headers.
User input is never mapped to headers that could influence routing or target selection (e.g., Host, Forwarded, X-Forwarded-*).
Headers are set explicitly and only from trusted sources. -
Controlled User-Agent usage.
User-Agent headers are static or server-defined.
If user-provided values are required, they are sanitized and restricted to a safe character set to prevent request smuggling or downstream abuse. -
Centralized validation helpers are required.
SSRF-related validations (URL authority, path segment, query value) are implemented in shared helper methods to ensure consistency and prevent bypasses across services.
Example: SSRF-Safe URL Construction¶
BuildSecurityHelpers.EnsureGitLabProjectId(repositoryId, true);
BuildSecurityHelpers.EnsureCommitHash(commitHash, true);
// /projects/:id/statuses/:sha
var urlBuilder = new UriBuilder(_httpClient.BaseAddress)
{
Path = _httpClient.BaseAddress.PathAndQuery + $"projects/{repositoryId}/statuses/{commitHash}"
};
// SSRF Protection: Validate URL before making request
var urlToValidate = urlBuilder.Uri.ToString();
if (!_urlValidator.ValidateGitLabCloudUrl(urlToValidate, out var errorMessage))
{
_logger.LogWarning("SSRF attempt blocked in SetCommitStatus: {ErrorMessage}", errorMessage);
throw new SecurityException($"URL validation failed: {errorMessage}");
}
Secret Handling¶
- Secrets (keystore passwords, access tokens, private key passphrases, private keys, etc.) are never passed as command-line arguments to any process.
- Secrets are never written to logs, exception messages, or debug output in plaintext.
- Secrets are obtained only when needed, used immediately, and kept in memory for the shortest time possible (no unnecessary caching).
- Secrets are not stored in long-lived or easily inspectable in-memory structures, such as immutable strings, global/static fields, or application-wide caches, unless strictly necessary.
When a secret must be processed in memory, it should be held in a mutable buffer (e.g.,byte[]) and explicitly cleared/overwritten as soon as it is no longer needed. - Secrets are provided to external tools via safer mechanisms (environment variables, temporary files with restricted permissions, stdin, or a secret manager), never via command strings or process arguments.
- Any command strings or configuration values that may contain sensitive arguments are either not logged at all or are sanitized/masked before logging.
API Validation¶
- Do not apply
[Required]to non-nullable value types (e.g.,bool,int,DateTime).
If the client must provide a value, use a nullable type (bool?,int?, etc.) together with validation, so that “missing” vs “provided but default” can be distinguished. - Prefer FluentValidation (or a similar validation layer) instead of scattering
[Required]and other attributes directly on API models. Keep validation rules in a dedicated validator class at the API boundary.
Debug¶
- Test-only or diagnostic code is wrapped in
#if DEBUG/#endifand excluded from Release builds. - Any code path that should never run in production performs an explicit environment check (e.g.,
ASPNETCORE_ENVIRONMENT != "Production"). - There is no test or debug-only endpoint, flag, or shortcut accessible in Release / Production deployments.
- Debug / test code does not rely on production secrets or data and cannot leak sensitive information if accidentally enabled.
Cryptography & Randomness¶
- All random values used for encryption, hashing, signing, key/IV/nonce/salt generation, tokens, or any other security-sensitive operation must be generated using a cryptographically secure random number generator (CSPRNG) (e.g.,
RandomNumberGeneratorin .NET,SecureRandomin Java, OS-provided CSPRNG). - Non-cryptographic randomness (e.g., picking a random item from a list for UX purposes, shuffling non-sensitive data) may use non-cryptographic RNGs, but such RNGs must never be used for cryptographic or security-sensitive purposes.
TLS & Certificate Validation¶
-
Server TLS certificate validation must never be globally disabled via APIs such as
ServicePointManager.ServerCertificateValidationCallback = _ => trueor equivalent mechanisms. This effectively turns off server identity verification and makes all HTTPS calls vulnerable to man-in-the-middle (MITM) attacks. -
If a custom certificate validation callback is required (e.g., for internal PKI in non-production environments), it must:
- Be scoped to a specific
HttpClient/ handler (e.g.,HttpClientHandler.ServerCertificateCustomValidationCallback), not set globally, and -
Be protected by a strict environment check (e.g., only enabled in
Development) and/or limited to specific expected certificate properties (hostname, thumbprint). -
For all production environments, server certificates must be trusted by the OS trust store and validated by the default TLS implementation. No bypass, blanket
return true, or “ignore SSL errors” patterns are allowed.