This document provides security guidance for deploying and operating IntelliTrader in production environments. All recommendations are based on analysis of the actual codebase.
- Threat Model
- Authentication & Authorization
- Secret Handling
- Input Validation
- Dependency Security
- Security Checklist for Production
| Asset | Location | Sensitivity |
|---|---|---|
| Exchange API Keys | keys.bin (encrypted) |
Critical - Grants trading access |
| Trading Funds | Binance exchange account | Critical - Financial loss risk |
| Dashboard Password | config/core.json (hashed) |
High - Controls application access |
| Configuration Files | config/*.json |
Medium - Contains trading parameters |
| Trade History | log/*-trades.txt |
Medium - Business intelligence |
| Session Cookies | Browser | Medium - Authenticated access |
| Actor | Motivation | Capability |
|---|---|---|
| External Attacker | Financial gain, data theft | Network access, web exploits |
| Malicious Configuration | Sabotage, fund extraction | Physical access to config files |
| Compromised Dependency | Supply chain attack | Code execution |
graph TB
subgraph External
A[Internet] --> B[Web Dashboard :7000]
A --> C[Binance API]
end
subgraph Web Dashboard
B --> D[Cookie Auth]
D --> E[MVC Controllers]
D --> F[Minimal API]
D --> G[SignalR Hub]
end
subgraph Protected Resources
E --> H[Trading Service]
F --> H
G --> H
H --> I[Exchange Service]
I --> C
end
subgraph Storage
J[config/*.json]
K[keys.bin]
L[log/*-trades.txt]
end
style K fill:#ff6b6b
style J fill:#ffd93d
style L fill:#6bcb77
The web dashboard uses ASP.NET Core Cookie Authentication with BCrypt password hashing.
Location: IntelliTrader.Web/Services/PasswordService.cs
public class PasswordService : IPasswordService
{
private const int DefaultWorkFactor = 12; // ~250ms on modern hardware
public string HashPassword(string password)
{
return BCrypt.Net.BCrypt.HashPassword(password, workFactor: DefaultWorkFactor);
}
public bool VerifyPassword(string password, string storedHash)
{
// Supports both BCrypt ($2a$/$2b$/$2y$) and legacy MD5 (32 hex chars)
if (IsBCryptHash(storedHash))
{
return BCrypt.Net.BCrypt.Verify(password, storedHash);
}
if (IsLegacyHash(storedHash))
{
return ComputeMD5Hash(password).Equals(storedHash, StringComparison.OrdinalIgnoreCase);
}
return false;
}
}Security Characteristics:
- BCrypt work factor 12 provides approximately 250ms verification time
- Backward compatibility with legacy MD5 hashes (migration recommended)
- Null/empty password rejection at service level
sequenceDiagram
participant User
participant Browser
participant HomeController
participant PasswordService
participant CoreConfig
User->>Browser: Navigate to /Login
Browser->>HomeController: GET /Login
alt Password Protection Disabled
HomeController->>HomeController: PerformLogin(persistent=true)
HomeController->>Browser: Set Cookie, Redirect to /Index
else Password Protection Enabled
HomeController->>Browser: Return Login View
User->>Browser: Enter Password
Browser->>HomeController: POST /Login {password, rememberMe}
HomeController->>CoreConfig: Get stored password hash
HomeController->>PasswordService: VerifyPassword(input, hash)
alt Password Valid
PasswordService->>HomeController: true
alt Legacy MD5 Hash Detected
HomeController->>HomeController: Log security warning
end
HomeController->>HomeController: PerformLogin(rememberMe)
HomeController->>Browser: Set Cookie, Redirect
else Password Invalid
PasswordService->>HomeController: false
HomeController->>Browser: Return View with error
end
end
Configuration: IntelliTrader.Web/Startup.cs
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
options.LoginPath = "/Login";
options.Cookie.Name = $"IntelliTrader_{coreService.Config.InstanceName}";
});Session Properties:
- Cookie name includes instance name for multi-instance deployments
- Persistent option controlled by "Remember Me" checkbox
- No explicit session timeout configured (uses ASP.NET Core defaults)
All protected endpoints require authentication:
| Component | Protection | Location |
|---|---|---|
| HomeController | [Authorize] class attribute |
Controllers/HomeController.cs:19 |
| TradingHub | [Authorize] class attribute |
Hubs/TradingHub.cs:18 |
| Minimal API | .RequireAuthorization() group |
MinimalApiEndpoints.cs:22 |
State-changing MVC actions use [ValidateAntiForgeryToken]:
| Action | Line | Purpose |
|---|---|---|
| GeneratePasswordHash | 143 | Password hash generation |
| Settings (POST) | 468 | Toggle trading settings |
| SaveConfig | 488 | Save configuration changes |
| Sell | 507 | Manual sell order |
| Buy | 524 | Manual buy order |
| BuyDefault | 542 | Manual buy with default amount |
| Swap | 564 | Manual swap operation |
Note: Minimal API endpoints (lines 21-199) do not have explicit CSRF protection but require authentication and use POST for state-changing operations.
API keys for Binance are stored encrypted using Windows DPAPI (Data Protection API) via ExchangeSharp's CryptoUtility.
Location: IntelliTrader/Program.cs:68-77
private static void EncryptKeys(Dictionary<string, string> args)
{
var path = args["path"];
var publicKey = args["publickey"];
var privateKey = args["privatekey"];
CryptoUtility.SaveUnprotectedStringsToFile(path, new string[] { publicKey, privateKey });
}Usage:
dotnet IntelliTrader.dll --encrypt --path=keys.bin --publickey=YOUR_API_KEY --privatekey=YOUR_API_SECRETSecurity Properties:
- Encrypted file bound to current Windows user and machine
- Cannot be decrypted on different machine or by different user
- File path configurable via
config/exchange.json:
{
"Exchange": {
"KeysPath": "keys.bin"
}
}Configuration files in config/ directory are stored as plaintext JSON:
| File | Contains Sensitive Data | Notes |
|---|---|---|
core.json |
Password hash (BCrypt) | Hash is safe to store |
exchange.json |
Path to keys.bin | Keys themselves encrypted |
trading.json |
Trading parameters | No secrets |
signals.json |
Signal definitions | No secrets |
rules.json |
Trading rules | No secrets |
web.json |
Port number only | No secrets |
notification.json |
Telegram tokens | Contains secrets |
Warning: notification.json may contain Telegram bot tokens which should be treated as secrets.
The codebase does not currently use environment variables for secret management. All configuration is file-based.
Configuration files are validated on load using FluentValidation.
Service: IntelliTrader.Core/Validation/ConfigValidationService.cs
public class ConfigValidationService : IConfigValidationService
{
public ConfigValidationService()
{
RegisterValidator<ITradingConfig>(new TradingConfigValidator());
RegisterValidator<ICoreConfig>(new CoreConfigValidator());
RegisterValidator<ISignalsConfig>(new SignalsConfigValidator());
RegisterValidator<IRulesConfig>(new RulesConfigValidator());
}
public void ValidateAndThrow<T>(T config, string sectionName) where T : class
{
var result = Validate(config);
if (!result.IsValid)
{
throw new ConfigValidationException(sectionName, result.Errors);
}
}
}Location: IntelliTrader.Core/Validation/CoreConfigValidator.cs
| Property | Rule | Purpose |
|---|---|---|
| InstanceName | Not empty, max 50 chars, alphanumeric | Prevent injection |
| TimezoneOffset | Between -12 and +14 | Valid UTC offsets |
| Password | Min 8 chars (when not hashed) | Password strength |
| HealthCheckInterval | 10-3600 seconds | Prevent abuse |
Location: IntelliTrader.Core/Validation/TradingConfigValidator.cs
| Property | Rule | Purpose |
|---|---|---|
| Market | Allowlist: BTC, ETH, USDT, BUSD, BNB, EUR, USD, GBP | Prevent invalid markets |
| Exchange | Allowlist: Binance | Supported exchanges only |
| MaxPairs | 1-100 | Resource limits |
| ExcludedPairs | Regex: uppercase alphanumeric 5-15 chars | Format validation |
| BuyMaxCost | > 0 | Positive values only |
| SellStopLossMargin | < 0 (when enabled) | Must be negative |
Web input uses Data Annotations for validation.
Location: IntelliTrader.Web/Models/TradingInputModels.cs
All trading pair inputs use consistent regex validation:
[Required(ErrorMessage = "Trading pair is required")]
[RegularExpression(@"^[A-Z0-9]{2,20}$",
ErrorMessage = "Invalid trading pair format. Must be 2-20 uppercase alphanumeric characters")]
public string Pair { get; set; }[Required(ErrorMessage = "Amount is required")]
[Range(0.00000001, 1000000000, ErrorMessage = "Amount must be between 0.00000001 and 1,000,000,000")]
public decimal Amount { get; set; }public class ConfigUpdateModel
{
[Required]
[RegularExpression(@"^(core|trading|signals|rules|web|notification)$")]
public string Name { get; set; }
[Required]
public string Definition { get; set; }
public bool IsValidJson()
{
try { JsonDocument.Parse(Definition); return true; }
catch (JsonException) { return false; }
}
}Controller validates JSON before saving:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult SaveConfig([FromForm] ConfigUpdateModel model)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
if (!model.IsValidJson()) return BadRequest("Invalid JSON format");
_configProvider.SetSectionJson(model.Name, model.Definition);
return Ok();
}Location: IntelliTrader.Web/Hubs/TradingHub.cs
public async Task SubscribeToPair(string pair)
{
if (string.IsNullOrWhiteSpace(pair))
{
_logger.LogWarning("Client {ConnectionId} attempted to subscribe with null/empty pair",
Context.ConnectionId);
return;
}
var normalizedPair = pair.ToUpperInvariant();
// ... subscription logic
}The solution uses standard NuGet package references without lockfiles.
Lockfile Status: No packages.lock.json files detected in the repository.
Recommendation: Enable NuGet package locking for reproducible builds:
<!-- In Directory.Build.props -->
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>| Package | Purpose | Security Consideration |
|---|---|---|
| BCrypt.Net-Next | Password hashing | Well-maintained, industry standard |
| ExchangeSharp | Exchange API, key encryption | Uses Windows DPAPI |
| FluentValidation | Input validation | No known vulnerabilities |
| Polly | Resilience policies | Rate limiting, circuit breaker |
| Microsoft.AspNetCore.* | Web framework | Security patches via .NET updates |
The repository contains node_modules/ (likely for tooling). This is separate from the .NET application.
- Generate BCrypt password hash - Use
/GeneratePasswordHashendpoint or updatecore.jsondirectly - Verify no legacy MD5 hash - Check
/PasswordStatusendpoint returns"HashType": "BCrypt (secure)" - Encrypt API keys on target machine - Run
--encryptcommand on production server - Review
notification.json- Ensure Telegram tokens are not committed to version control - Set
DebugMode: false- In bothcore.jsonandweb.json - Configure HTTPS - Use reverse proxy (nginx, IIS) with TLS termination
- Restrict network access - Web dashboard should not be exposed to public internet
- Validate all config files - Application validates on startup; review any validation errors
- Set appropriate
MaxPairs- Limit concurrent trading pairs - Configure
BuyMinBalance- Prevent over-trading - Enable
HealthCheckEnabled- Monitor exchange connectivity - Review
ExcludedPairs- Block unwanted trading pairs
- Monitor trade logs - Review
log/*-trades.txtfor anomalies - Check health endpoint - Monitor
/api/statusfor suspended trading - Review SignalR connections - Monitor for unusual subscription patterns
- Enable application logging - Configure appropriate log levels in
logging.json
- Use IP allowlist on Binance - Restrict API key to production server IP
- Enable withdrawal address whitelist - Prevent fund extraction if keys compromised
- Disable withdrawal permission - If not needed, remove from API key
- Regular key rotation - Periodically generate new API keys
- Backup
keys.bin- Encrypted file must be backed up (same machine/user only) - Backup configuration - Store
config/directory in version control (excluding secrets) - Document recovery procedure - Key re-encryption required on new machine
If using a legacy MD5 hash, migrate to BCrypt:
- Login with current password
- Navigate to Settings or use API endpoint
- POST to
/GeneratePasswordHashwith new password (form data:password=YourNewPassword) - Copy the returned BCrypt hash to
config/core.json:
{
"Core": {
"PasswordProtected": true,
"Password": "$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4/jJrZk.oB8C6.Hy"
}
}- Restart the application
The application logs a security warning on each login when using legacy MD5 hashes to encourage migration.