Changes: - Add FAL_KEY and GEMINI_API_KEY to .env.example - Update picture-it to use ~/.config/opencode/.env (unified creds) - Remove shodh-memory skill (no longer used) - Remove alphaear-* skills (deprecated) - Remove thai-frontend-dev skill (replaced by website-creator) - Remove theme-factory skill - Add mql-developer skill (MQL5 trading) - Add ecommerce-astro skill (Astro e-commerce) - Add website-creator skill (Next.js + Payload CMS) - Update install script for new skills
260 lines
7.0 KiB
Markdown
260 lines
7.0 KiB
Markdown
# Security & Licensing
|
|
|
|
## Table of Contents
|
|
|
|
- [Account-Based Licensing](#account-based-licensing)
|
|
- [Server-Side License Validation](#server-side-license-validation)
|
|
- [Expiration Date Checks](#expiration-date-checks)
|
|
- [Anti-Decompilation Techniques](#anti-decompilation-techniques)
|
|
- [Distribution Best Practices](#distribution-best-practices)
|
|
- [Node.js License Server Example](#nodejs-license-server-example)
|
|
- [MQL4 Specific Considerations](#mql4-specific-considerations)
|
|
- [Complete License System Architecture](#complete-license-system-architecture)
|
|
|
|
---
|
|
|
|
## Account-Based Licensing
|
|
|
|
### Simple Multi-Account Check
|
|
|
|
```mql5
|
|
bool CheckAccountLicense() {
|
|
long currentAccount = AccountInfoInteger(ACCOUNT_LOGIN);
|
|
long authorizedAccounts[] = {12345678, 87654321, 11223344};
|
|
for(int i = 0; i < ArraySize(authorizedAccounts); i++) {
|
|
if(currentAccount == authorizedAccounts[i]) return true;
|
|
}
|
|
Alert("Account ", currentAccount, " is not authorized.");
|
|
return false;
|
|
}
|
|
```
|
|
|
|
### Account + Broker Hash Check (More Secure)
|
|
|
|
Don't store plain account numbers in code. Hash account + broker + salt:
|
|
|
|
```mql5
|
|
ulong HashString(string s) {
|
|
ulong hash = 5381;
|
|
for(int i = 0; i < StringLen(s); i++)
|
|
hash = ((hash << 5) + hash) + StringGetCharacter(s, i);
|
|
return hash;
|
|
}
|
|
|
|
bool ValidateLicense() {
|
|
long account = AccountInfoInteger(ACCOUNT_LOGIN);
|
|
string server = AccountInfoString(ACCOUNT_SERVER);
|
|
string raw = IntegerToString(account) + "|" + server + "|SECRET_SALT";
|
|
ulong hash = HashString(raw);
|
|
ulong validHashes[] = {0xA1B2C3D4E5F6, 0x1234567890AB};
|
|
for(int i = 0; i < ArraySize(validHashes); i++)
|
|
if(hash == validHashes[i]) return true;
|
|
return false;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Server-Side License Validation
|
|
|
|
### Implementation Pattern
|
|
|
|
```mql5
|
|
input string InpLicenseKey = "";
|
|
|
|
bool ValidateLicenseOnServer() {
|
|
long account = AccountInfoInteger(ACCOUNT_LOGIN);
|
|
string broker = AccountInfoString(ACCOUNT_SERVER);
|
|
string eaName = MQLInfoString(MQL_PROGRAM_NAME);
|
|
|
|
string json = "{";
|
|
json += "\"license_key\":\"" + InpLicenseKey + "\",";
|
|
json += "\"account\":" + IntegerToString(account) + ",";
|
|
json += "\"broker\":\"" + broker + "\",";
|
|
json += "\"ea\":\"" + eaName + "\"";
|
|
json += "}";
|
|
|
|
// Send to server, check response
|
|
// Handle network failures (fail-open vs fail-closed decision)
|
|
}
|
|
```
|
|
|
|
### Fail-Open vs Fail-Closed
|
|
|
|
- **Fail-open:** Allow trading if server unreachable (better UX, weaker security)
|
|
- **Fail-closed:** Block trading if server unreachable (stronger security, risk of false blocks)
|
|
- **Recommended:** Fail-open with cached expiry and offline grace period
|
|
|
|
### Caching License Locally
|
|
|
|
Use `GlobalVariableSet` to cache server expiry:
|
|
|
|
```mql5
|
|
GlobalVariableSet("EA_LICENSE_EXPIRY", (double)StringToTime(expiry));
|
|
```
|
|
|
|
On next check, verify cached expiry first, re-validate with server periodically.
|
|
|
|
---
|
|
|
|
## Expiration Date Checks
|
|
|
|
### Hardcoded Expiration
|
|
|
|
```mql5
|
|
datetime expiryDate = D'2025.12.31 23:59:59';
|
|
if(TimeCurrent() > expiryDate) {
|
|
Alert("EA has expired. Please renew.");
|
|
return INIT_FAILED;
|
|
}
|
|
```
|
|
|
|
### Server-Cached Expiration
|
|
|
|
Check `GlobalVariable` cache first, re-validate on timer.
|
|
|
|
### Demo Mode with Limited Features
|
|
|
|
After trial period, restrict to demo accounts or limited lot sizes.
|
|
|
|
### Integration in OnInit
|
|
|
|
```mql5
|
|
int OnInit() {
|
|
if(!CheckAccountLicense()) return INIT_FAILED;
|
|
if(!CheckExpiration()) return INIT_FAILED;
|
|
EventSetTimer(3600); // Re-check every hour
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
void OnTimer() {
|
|
if(!CheckExpiration()) ExpertRemove();
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Anti-Decompilation Techniques
|
|
|
|
### Protection Layers Table
|
|
|
|
| Layer | Technique | Protection Level | Notes |
|
|
|-------|-----------|-----------------|-------|
|
|
| 1 | Compile to .ex4/.ex5 | Basic | Strips variable/function names |
|
|
| 2 | Code obfuscation | Low | Complex expressions, dummy code |
|
|
| 3 | String encryption | Medium | Hide URLs, keys, constants |
|
|
| 4 | MQL5 Cloud Protector | High | Asymmetric encryption, native code |
|
|
| 5 | DLL offloading | High | Core logic in C++ DLL |
|
|
| 6 | Server-side logic | Highest | Core signals never leave server |
|
|
|
|
### String Obfuscation Example
|
|
|
|
```mql5
|
|
string DecryptString(int key) {
|
|
uchar encrypted[] = {104,116,116,112,115,58,47,47};
|
|
string result = "";
|
|
for(int i = 0; i < ArraySize(encrypted); i++)
|
|
result += CharToString((uchar)(encrypted[i] ^ (key % 256)));
|
|
return result;
|
|
}
|
|
```
|
|
|
|
### MQL5 Cloud Protector
|
|
|
|
- Available in MetaEditor: Tools > MQL5 Cloud Protector
|
|
- Sends compiled .ex5 to MetaQuotes cloud
|
|
- Applies asymmetric encryption + unique key signing
|
|
- Source code never leaves your machine
|
|
- Same protection as MQL5 Market store
|
|
- Files NOT bound to specific computer (unlike Market)
|
|
- Free to use
|
|
|
|
---
|
|
|
|
## Distribution Best Practices
|
|
|
|
### Do's
|
|
|
|
- Distribute only .ex4/.ex5 compiled files
|
|
- Use MQL5 Market for built-in DRM (hardware + account binding)
|
|
- Combine account binding + server validation + Cloud Protector
|
|
- Include version checking in licensing server to force updates
|
|
- Use unique magic numbers or comments to identify your EA
|
|
|
|
### Don'ts
|
|
|
|
- Never distribute .mq4/.mq5 source files
|
|
- Never hardcode API keys or server passwords in source
|
|
- Don't rely on a single protection layer
|
|
- Don't use simple string comparisons for license keys
|
|
|
|
---
|
|
|
|
## Node.js License Server Example
|
|
|
|
### Endpoint: POST /api/validate
|
|
|
|
```javascript
|
|
app.post('/api/validate', (req, res) => {
|
|
const { license_key, account, broker, ea, version } = req.body;
|
|
|
|
// Look up license in database
|
|
const license = db.findLicense(license_key);
|
|
|
|
if(!license) return res.json({ valid: false, message: "Invalid key" });
|
|
if(license.expired) return res.json({ valid: false, message: "License expired" });
|
|
if(license.maxAccounts && license.accounts.length >= license.maxAccounts) {
|
|
if(!license.accounts.includes(account))
|
|
return res.json({ valid: false, message: "Max accounts reached" });
|
|
}
|
|
|
|
// Register account if new
|
|
if(!license.accounts.includes(account)) {
|
|
license.accounts.push(account);
|
|
db.updateLicense(license);
|
|
}
|
|
|
|
res.json({
|
|
valid: true,
|
|
expiry: license.expiryDate,
|
|
features: license.features
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## MQL4 Specific Considerations
|
|
|
|
### Account Number Check (MQL4)
|
|
|
|
```mql4
|
|
bool CheckLicense() {
|
|
int account = AccountNumber(); // MQL4 function
|
|
// ... same logic but with int instead of long
|
|
}
|
|
```
|
|
|
|
### Hardware ID (MQL4 via DLL)
|
|
|
|
Can use Windows API via DLL to get hardware identifiers for machine-specific licensing.
|
|
|
|
---
|
|
|
|
## Complete License System Architecture
|
|
|
|
```
|
|
EA (MQL5) License Server (Node.js)
|
|
| |
|
|
|-- POST /validate ----------->|
|
|
| {key, account, broker} |-- Check DB
|
|
| |-- Validate
|
|
|<-- {valid, expiry, features}-|
|
|
| |
|
|
|-- Cache expiry locally |
|
|
|-- Re-check every hour |
|
|
| |
|
|
|-- POST /heartbeat ---------->| (optional)
|
|
| {account, balance, status} |-- Track usage
|
|
```
|