We take security seriously and provide security updates for the following versions of Esox.SharpAndRusty:
| Version | Supported | Status |
|---|---|---|
| 1.2.x | ✅ | Current stable release |
| 1.1.x | ✅ | Maintenance support |
| 1.0.x | Critical fixes only | |
| < 1.0 | ❌ | No longer supported |
- Current Release (1.2.x): Full security support with immediate patches
- Latest: 1.2.2 (includes ?? experimental Mutex and RwLock features)
- Core Result/Error functionality: Production-ready (9.5/10)
- ?? Experimental: Mutex and RwLock (use with caution in production)
- Previous Release (1.1.x): Security updates for critical vulnerabilities (6 months after 1.2.0 release)
- Older Releases (1.0.x): Critical security fixes only (3 months after 1.2.0 release)
- Legacy Versions (< 1.0): Not supported - please upgrade
?? IMPORTANT: The
Mutex<T>andRwLock<T>types are experimental features. While they are thoroughly tested and follow security best practices, their APIs may change in future versions.
Security Considerations for Experimental Features:
- API Stability: Experimental features may have breaking changes in minor versions
- Production Use: Recommended for non-critical paths only until API stabilizes
- Thorough Testing: Test extensively in your specific scenarios before deployment
- Monitoring: Implement comprehensive monitoring for deadlocks and race conditions
- Rollback Plans: Have contingency plans if issues arise
- Community Feedback: Report any security concerns immediately
What "Experimental" Means for Security:
- Thoroughly tested (36+ tests for Mutex)
- Follows security best practices
- Built on well-tested .NET primitives (SemaphoreSlim, ReaderWriterLockSlim)
- Result-based API for explicit error handling
- API may change based on feedback
- Use caution in production-critical systems
- Extensive real-world testing recommended
We take all security vulnerabilities seriously. If you discover a security issue, please report it responsibly.
Please do NOT report security vulnerabilities through public GitHub issues.
Instead, please report security vulnerabilities by:
- Email (Preferred): Send details to info@codenomad.nl
Please include the following information in your report:
- Description: Clear description of the vulnerability
- Impact: What an attacker could achieve
- Reproduction Steps: Detailed steps to reproduce the issue
- Affected Versions: Which versions are affected
- Proof of Concept: Code or screenshots (if applicable)
- Suggested Fix: Your proposed solution (if you have one)
- Contact Information: How we can reach you for follow-up
Example Template:
## Vulnerability Report
### Summary
Brief description of the vulnerability.
### Affected Component
- File: ErrorExtensions.cs
- Method: TryAsync
- Versions: 1.2.0, 1.1.0
### Vulnerability Type
- [ ] Code Injection
- [ ] Information Disclosure
- [ ] Denial of Service
- [ ] Authentication Bypass
- [ ] Other: _______________
### Impact
What can an attacker do with this vulnerability?
### Reproduction Steps
1. Create a Result<T, Error> with...
2. Call method X with parameters...
3. Observe behavior...
### Proof of Concept
```csharp
// Minimal code to reproduce
var result = Result<int, Error>.Ok(42);
// ...- .NET Version: 10.0
- OS: Windows 11 / macOS / Linux
- Library Version: 1.2.0
Optional: Your proposed solution.
We aim to respond to security reports according to the following timeline:
- Initial Response: Within 48 hours
- Confirmation: Within 5 business days
- Status Updates: Every 7 days until resolution
- Patch Release: Based on severity (see below)
Always use the latest stable version:
# Check for updates
dotnet list package --outdated
# Update to latest version
dotnet add package Esox.SharpAndRusty --version 1.2.0Always validate data before processing, especially in error messages:
// Don't expose sensitive data in error messages
var error = Error.New($"Failed to process credit card: {creditCardNumber}");
// Use sanitized messages
var error = Error.New("Failed to process payment")
.WithMetadata("transactionId", transactionId) // Safe to log
.WithMetadata("timestamp", DateTime.UtcNow);Be cautious with metadata containing sensitive information:
// Don't store sensitive data in metadata
error.WithMetadata("password", userPassword);
error.WithMetadata("apiKey", secretKey);
// Use metadata for debugging, not secrets
error.WithMetadata("userId", userId);
error.WithMetadata("operationType", "login");
error.WithMetadata("attemptCount", 3);Use Try and TryAsync to prevent information leakage:
// Don't expose full exception details to users
try
{
// Operation
}
catch (Exception ex)
{
return Error.FromException(ex); // May contain stack traces
}
// Sanitize error messages for users
var result = ErrorExtensions.Try(() => SensitiveOperation())
.MapError(error => Error.New("Operation failed")
.WithKind(error.Kind)
// Don't include stack trace or detailed error in production
);The library automatically limits error chains to 50 levels, but be mindful:
// The library protects against this automatically
// But avoid creating unnecessarily deep chains
var error = baseError;
for (int i = 0; i < 1000; i++) // Protected by depth limit
{
error = error.WithContext($"Level {i}");
}Always use cancellation tokens to prevent resource exhaustion:
// Use cancellation tokens
var result = await ErrorExtensions.TryAsync(
async () => await LongRunningOperation(),
cancellationToken: cts.Token
);
// Set timeouts for operations
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var result = await ProcessAsync(cts.Token);Issue: Improper use of concurrency primitives can lead to deadlocks, race conditions, or resource leaks.
Mitigation:
- Result-based API - All lock operations return explicit success/failure
- RAII lock management - Automatic lock release via
IDisposable - Well-tested primitives - Built on
SemaphoreSlim(Mutex) andReaderWriterLockSlim(RwLock) - Timeout support - Prevents indefinite waiting
- Cancellation support - Allows graceful shutdown
- No lock recursion - Prevents accidental deadlocks from recursive locking
Mutex Best Practices:
// Always use 'using' for guards
var result = mutex.Lock();
if (result.TryGetValue(out var guard))
{
using (guard) // Lock automatically released
{
guard.Value++;
}
}
// Use timeouts to prevent deadlocks
var result = mutex.TryLockTimeout(TimeSpan.FromSeconds(5));
// ? Use cancellation tokens for async operations
var result = await mutex.LockAsync(cancellationToken);
// Don't forget to dispose guards
var result = mutex.Lock();
if (result.TryGetValue(out var guard))
{
guard.Value++; // Never released - RESOURCE LEAK!
}RwLock Best Practices:
// Multiple readers can access concurrently
var readResult = rwLock.Read();
if (readResult.TryGetValue(out var readGuard))
{
using (readGuard)
{
// Read-only access - safe with multiple readers
var value = readGuard.Value;
}
}
// ? Exclusive writer access
var writeResult = rwLock.Write();
if (writeResult.TryGetValue(out var writeGuard))
{
using (writeGuard)
{
// Exclusive write access
writeGuard.Value = newValue;
}
}
// ? Use try-variants to avoid blocking
var tryResult = rwLock.TryWrite();
if (!tryResult.IsSuccess)
{
// Handle lock unavailable gracefully
}Deadlock Prevention:
// Bad: Can deadlock if threads acquire in different order
mutex1.Lock(); // Thread 1 holds mutex1
mutex2.Lock(); // Thread 2 holds mutex2
// Thread 1 waits for mutex2, Thread 2 waits for mutex1 = DEADLOCK
// Good: Use timeouts
var result1 = mutex1.TryLockTimeout(TimeSpan.FromSeconds(5));
var result2 = mutex2.TryLockTimeout(TimeSpan.FromSeconds(5));
// Better: Always acquire locks in the same order
// All threads must lock mutex1 before mutex2Experimental Status Note:
- Mutex and RwLock are currently experimental
- API may change in future versions
- Thorough testing recommended before production use
- Report any deadlocks, race conditions, or unexpected behavior immediately
- Security patches will be provided promptly for reported issues
All code must pass security review before merging:
- No hardcoded secrets or credentials
- Input validation for all public APIs
- No sensitive data in logs or error messages
- Proper exception handling
- No SQL injection vectors (if database access added)
- No code injection vulnerabilities
- Thread-safe operations
- Proper async/await patterns with cancellation
- No denial of service vulnerabilities
- Use only trusted NuGet packages
- Keep dependencies updated
- Review dependency security advisories
- Minimize dependency count
Argument Validation:
public Error WithMetadata(string key, object value)
{
// Always validate arguments
if (key is null) throw new ArgumentNullException(nameof(key));
if (value is null) throw new ArgumentNullException(nameof(value));
// Validate business rules
if (!IsMetadataTypeValid(value))
{
throw new ArgumentException(
$"Type {value.GetType().Name} is not suitable for metadata.",
nameof(value));
}
// Implementation
}Immutability:
// Use readonly fields
private readonly string _message;
private readonly ImmutableDictionary<string, object>? _metadata;
// Return new instances, don't modify existing
public Error WithContext(string contextMessage)
{
return new Error(contextMessage, _kind, this, null, null);
}Resource Cleanup:
// Proper async disposal
public async Task<Result<T, Error>> ProcessAsync(CancellationToken cancellationToken)
{
await using var resource = await AcquireResourceAsync();
// Use resource
}Issue: Stack traces may contain sensitive file paths or internal implementation details.
Mitigation:
- Use
CaptureStackTrace(includeFileInfo: false)in production - Sanitize error messages before sending to clients
- Use metadata for structured logging instead of stack traces
Example:
#if DEBUG
var error = Error.New("Operation failed")
.CaptureStackTrace(includeFileInfo: true); // Detailed in dev
#else
var error = Error.New("Operation failed")
.CaptureStackTrace(includeFileInfo: false); // Safe in production
#endifIssue: Detailed error messages might reveal internal system structure.
Mitigation:
- Use generic error messages for external users
- Store detailed information in metadata (logged internally)
- Filter error messages based on environment
Example:
public Error CreateUserFacingError(Error internalError)
{
// External: Generic message
var publicError = Error.New("An error occurred")
.WithKind(internalError.Kind);
// Internal: Full context (logged, not sent to client)
Logger.LogError(internalError.GetFullMessage());
return publicError;
}Issue: Metadata is stored as objects and could contain references to sensitive data.
Mitigation:
- Type validation prevents storing complex objects
- Only primitives, strings, and value types allowed
- Immutable collections prevent modification
Protected by Design:
// Type validation prevents this
error.WithMetadata("connection", databaseConnection); // Throws ArgumentException
// ? Only safe types allowed
error.WithMetadata("userId", 123); // OK: int
error.WithMetadata("timestamp", DateTime.UtcNow); // OK: DateTimeIssue: Unhandled exceptions in async operations could leak information.
Mitigation:
- All async methods have proper exception handling
- Cancellation tokens supported throughout
- Proper async/await patterns used
Built-in Protection:
public static async Task<Result<T, Error>> TryAsync<T>(
Func<Task<T>> operation,
CancellationToken cancellationToken = default)
{
try
{
cancellationToken.ThrowIfCancellationRequested();
var value = await operation().ConfigureAwait(false);
return Ok(value);
}
catch (Exception ex) // All exceptions caught
{
return Err(Error.FromException(ex));
}
}Issue: Circular references in error chains could cause stack overflow or infinite loops.
Mitigation:
- HashSet-based cycle detection in
GetFullMessage() - Depth limiting (max 50 levels)
- Graceful degradation with informative messages
Automatic Protection:
// Protected automatically - no user action needed
var message = error.GetFullMessage(); // Safe, even with cyclesIssue: Improper use of concurrency primitives can lead to deadlocks, race conditions, or resource leaks.
Mitigation:
- Result-based API - All lock operations return
Result<Guard, Error>for explicit handling - RAII pattern - Guards automatically release locks on disposal
- Timeout support - All blocking operations have timeout variants
- Cancellation support - Async operations support
CancellationToken - No lock recursion - Configured to prevent recursive locking
- Tested primitives - Built on
SemaphoreSlimandReaderWriterLockSlim
Automatic Protection:
// Protected by RAII - lock automatically released
using var guard = mutex.Lock().Expect("Failed to acquire lock");
guard.Value++;
// Lock released here automatically
// Timeout prevents indefinite waiting
var result = mutex.TryLockTimeout(TimeSpan.FromSeconds(5));
result.Match(
success: guard => { /* process */ },
failure: error => { /* handle timeout */ }
);
// Cancellation allows graceful shutdown
var result = await mutex.LockAsync(cancellationToken);Common Pitfalls to Avoid:
// Forgetting to dispose guard
var result = mutex.Lock();
if (result.TryGetValue(out var guard))
{
guard.Value++;
// Guard not disposed - lock never released!
}
// Inconsistent lock ordering
// Thread 1: lock(A), lock(B)
// Thread 2: lock(B), lock(A)
// Result: Potential deadlock
// Holding lock while doing expensive I/O
using var guard = mutex.Lock().Unwrap();
await ExpensiveNetworkCall(); // Other threads blocked!
guard.Value = result;
// Better: Do work outside the lock
var result = await ExpensiveNetworkCall();
using var guard = mutex.Lock().Unwrap();
guard.Value = result; // Lock held minimallyExperimental Feature Risks:
- API changes in future versions may require code updates
- Edge cases may exist in specific concurrency scenarios
- Stress testing recommended for high-contention use cases
- Community feedback will improve API safety and usability
-
Immutability
- All types are immutable by design
- Prevents accidental or malicious modification
- Thread-safe by default
-
Type Safety
- Strong typing prevents type confusion attacks
- Generic constraints ensure type correctness
- Nullable reference types prevent null reference errors
-
Resource Management
- Proper async/await patterns
- Cancellation token support
- No resource leaks
- RAII pattern for automatic cleanup (Mutex guards)
-
Depth Limiting
- Error chains limited to 50 levels
- Prevents stack overflow attacks
- Graceful degradation
-
Cycle Detection
- HashSet-based circular reference detection
- Prevents infinite loops
- O(1) detection per node
-
Argument Validation
- All public APIs validate arguments
- Clear exception messages
- No undefined behavior
-
Concurrency Safety (Mutex and RwLock - ?? Experimental)
- Explicit lock acquisition via Result types
- Automatic lock release via IDisposable (RAII pattern)
- Timeout support to prevent indefinite waiting
- Cancellation token support for async operations
- No lock recursion to prevent accidental deadlocks
- Built on well-tested .NET concurrency primitives
- Result-based API forces explicit error handling
This library follows these security principles:
- OWASP Secure Coding Practices: Input validation, error handling, data protection
- Principle of Least Privilege: Minimal API surface, restricted access
- Defense in Depth: Multiple layers of protection
- Fail Securely: Secure defaults, graceful degradation
- Secure by Design: Immutability, type safety, validation
- No Telemetry: Library does not collect or send any data
- No External Dependencies: No third-party data transmission
- Local Processing Only: All operations are local
- User Control: You control what data goes into errors/metadata
Stay informed about security updates:
- GitHub Security Advisories: Subscribe to security advisories
- GitHub Releases: Watch releases
- NuGet: Check for updates regularly
When a security update is released:
- Announcement: Published on GitHub and security advisory
- Patch Release: New version released with fix
- CHANGELOG: Updated with security fix details
- Documentation: Updated if necessary
We are committed to:
- Acknowledgment: We will acknowledge receipt of your report
- Communication: We will keep you informed of progress
- Credit: We will credit you in release notes (if desired)
- Responsible Disclosure: We will coordinate disclosure timing with you
For security-related questions:
- Email: security@esoxsolutions.com
- GitHub: Create a private security advisory
For non-security questions:
- GitHub Issues: Create an issue
- GitHub Discussions: Start a discussion
- OWASP Secure Coding Practices
- Microsoft Security Development Lifecycle
- .NET Security Guidelines
- NuGet Package Security Best Practices
- .NET Concurrency Best Practices
- Deadlock Prevention Patterns
Last Updated: 2025
Policy Version: 1.1
Contact: security@esoxsolutions.com
- Review this security policy
- Update to latest stable version (1.2.2)
- Validate no sensitive data in error messages
- Sanitize error messages before sending to clients
- Use
includeFileInfo: falsefor stack traces - Implement proper logging (separate from user-facing errors)
- Use cancellation tokens for async operations
- Set appropriate timeouts
- Test error handling paths
- Review metadata contents
If using Mutex or RwLock (?? Experimental):
- ?? Understand experimental status and potential API changes
- ?? Use in non-critical paths initially
- ? Always use
usingstatements with lock guards - ? Set appropriate timeouts to prevent deadlocks
- ? Use consistent lock ordering if acquiring multiple locks
- ? Minimize time holding locks (no expensive I/O under lock)
- ? Test concurrency scenarios thoroughly (stress tests)
- ? Monitor for deadlocks in production
- ? Have rollback plan if issues arise
- ? Report any issues or concerns immediately
- ?? Be prepared for API changes in future versions
- ?? Subscribe to release notes for breaking changes
- Monitor for security advisories
- Update dependencies regularly
- Review security logs
- Test security scenarios
- Train team on secure usage
- Conduct security reviews
- Keep documentation updated
- Review concurrency patterns (if using experimental features)
- Monitor for deadlocks and race conditions
- Stay informed about experimental feature stabilization
Thank you for helping keep Esox.SharpAndRusty and its users secure! ??