NodeServer - Security Audit

Executive Summary

The NodeServer WebSocket signaling server has critical security vulnerabilities that require immediate attention. While the server uses parameterized SQL queries (preventing SQL injection), it suffers from hardcoded credentials, lack of input validation, missing authentication controls, and no rate limiting.

Overall Security Rating: ⚠️ HIGH RISK

Critical Findings: 5
High Priority: 8
Medium Priority: 12
Low Priority: 6


Table of Contents

  1. Critical Security Issues
  2. High Priority Issues
  3. Medium Priority Issues
  4. Low Priority Issues
  5. Security Best Practices Analysis
  6. Threat Modeling
  7. Compliance Considerations
  8. Remediation Roadmap

Critical Security Issues

🔴 CRITICAL-001: Hardcoded Database Credentials

Severity: Critical
CWE: CWE-798 (Use of Hard-coded Credentials)
CVSS Score: 9.8 (Critical)

Location: server.js, lines 450-650 (setClientMode function)

Vulnerable Code:

case cClient.PSYTER_DEV:
    dbConfig = {
        user: 'PsyterUser',
        password: 'PsyterPa$$w0Rd',  // ❌ Hardcoded password in source
        server: 'db.innotech-sa.com',
        database: 'Psyter_v1'
    };
    break;

case cClient.PSYTER_LIVE:
    dbConfig = {
        user: 'PsyterUser',
        password: 'Zo@mb!sPsyter',  // ❌ Production password in code
        server: '51.89.234.59',
        database: 'Psyter'
    };
    break;

Impact:
- Credentials exposed in version control (Git history)
- Anyone with repository access can read production database
- Credentials visible in process memory dumps
- Violates security compliance (PCI-DSS, HIPAA)

Exploitation:
1. Attacker gains access to Git repository
2. Searches for “password” in codebase
3. Connects directly to production database
4. Exfiltrates patient health records (PHI)
5. Modifies billing records, user credentials

Remediation:

Step 1: Environment Variables (Immediate)

// Use dotenv package
require('dotenv').config();

const dbConfig = {
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    server: process.env.DB_SERVER,
    database: process.env.DB_NAME
};

.env file (not committed to Git):

DB_USER=PsyterUser
DB_PASSWORD=Zo@mb!sPsyter
DB_SERVER=51.89.234.59
DB_NAME=Psyter

.gitignore:

.env
.env.*
!.env.example

Step 2: Azure Key Vault (Recommended)

const { DefaultAzureCredential } = require("@azure/identity");
const { SecretClient } = require("@azure/keyvault-secrets");

const credential = new DefaultAzureCredential();
const vaultName = process.env.KEY_VAULT_NAME;
const client = new SecretClient(`https://${vaultName}.vault.azure.net`, credential);

async function getDbConfig() {
    const user = await client.getSecret("db-user");
    const password = await client.getSecret("db-password");
    const server = await client.getSecret("db-server");

    return {
        user: user.value,
        password: password.value,
        server: server.value,
        database: process.env.DB_NAME
    };
}

Step 3: Rotate Credentials

-- Change all database passwords immediately
ALTER LOGIN PsyterUser WITH PASSWORD = 'NewComplexPassword!2025';

Priority: 🔴 Do Now


🔴 CRITICAL-002: Hardcoded Firebase Private Keys

Severity: Critical
CWE: CWE-522 (Insufficiently Protected Credentials)
CVSS Score: 9.1 (Critical)

Location: server.js, lines 450-650

Vulnerable Code:

fcmAPISettings = {
    ProjectId: "psyterdev",
    ClientEmail: "firebase-adminsdk-uh0r6@psyterdev.iam.gserviceaccount.com",
    PrivateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHXzBbvXxs+RZJ\n...",  // ❌ 1600+ character private key
    DatabaseURL: "https://psyterdev.firebaseio.com"
};

Impact:
- Attacker can send push notifications to all users
- Impersonate the system to users
- Spam users with malicious notifications
- Phishing attacks via trusted notification channel

Exploitation:
1. Extract private key from source code
2. Initialize Firebase Admin SDK with stolen credentials
3. Send fake notifications: “Urgent: Click here to verify your account”
4. Users click malicious link → credential theft

Remediation:

Step 1: Service Account JSON Files

const serviceAccount = require(`./firebase/${currentClientMode}.json`);

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: process.env.FIREBASE_DATABASE_URL
});

File Structure:

firebase/
├── PSYTER_DEV.json  (not in Git)
├── PSYTER_LIVE.json  (not in Git)
└── .gitkeep

.gitignore:

firebase/*.json
!firebase/.gitkeep

Priority: 🔴 Do Now


🔴 CRITICAL-003: Weak SSL Certificate Passphrase

**Step 2: Revoke Compromised Keys**
1. Go to Firebase Console → Project Settings → Service Accounts
2. Delete existing service account
3. Generate new service account
4. Download JSON file securely
5. Update production servers

**Estimated Effort:** 1 hour  
**Priority:** 🔴 **Do Now** (within 24 hours)

---

### 🔴 CRITICAL-003: Weak SSL Certificate Passphrase

**Severity:** Critical  
**CWE:** CWE-259 (Use of Hard-coded Password)  
**CVSS Score:** 8.1 (High)

**Location:** `server.js`, line 200

**Vulnerable Code:**
```javascript
var options = {
    key: pkey,
    cert: pcert,
    passphrase: '123456789'  // ❌ Weak, hardcoded passphrase
};

Impact:
- SSL certificates protected by trivial password
- If certificate files leak, easily decrypted
- Man-in-the-middle attack possible with stolen certs

Exploitation:
1. Attacker obtains certificate files (e.g., backup leak)
2. Tries common passphrases (123456789 is obvious)
3. Decrypts private key
4. Sets up rogue WebSocket server
5. Intercepts all communications

Remediation:

Step 1: Strong Passphrase

var options = {
    key: pkey,
    cert: pcert,
    passphrase: process.env.SSL_PASSPHRASE  // From environment variable
};

Generate Strong Passphrase:

openssl rand -base64 32
# Example output: 8f3j2k9d0s1a2b3c4d5e6f7g8h9i0j1k

Step 2: Rotate Certificates

# Generate new certificate with strong passphrase
openssl genrsa -aes256 -passout pass:$STRONG_PASSPHRASE -out key.pem 2048
openssl req -new -key key.pem -passin pass:$STRONG_PASSPHRASE -out cert.csr

Priority: 🔴 Do Now


🔴 CRITICAL-004: No Rate Limiting

Severity: Critical
CWE: CWE-770 (Allocation of Resources Without Limits)
CVSS Score: 7.5 (High)

Location: Global - all command handlers

Vulnerable Code:

client.on('message', function (data) {
    // ❌ No rate limiting - client can send unlimited messages
    var cMessage = JSON.parse(data);

    switch (cMessage.ConnectionMode) {
        case cConnectionMode.PRESENCE:
            PProcessCommand(client, cMessage.Message, cMessage.Command, ...);
            break;
        case cConnectionMode.COLLABORATION:
            CProcessCommand(client, cMessage.Message, cMessage.Command, ...);
            break;
    }
});

Impact:
- Denial of Service (DoS): Single client can flood server with messages
- Resource exhaustion: CPU/memory consumed processing spam
- Database overload: Unlimited queries to SQL Server
- FCM quota exhaustion: Spam notifications deplete daily quota

Attack Scenarios:

Scenario 1: Message Spam

// Attacker script
const ws = new WebSocket('wss://server:3333/?connectionMode=1&...');
ws.on('open', () => {
    setInterval(() => {
        for (let i = 0; i < 1000; i++) {
            ws.send(JSON.stringify({
                ConnectionMode: 1,
                Command: 5,  // P_CHAT
                Message: 'SPAM',
                FromUser: { Id: 42 },
                ToUserList: [{ Id: 43 }]
            }));
        }
    }, 100);  // 10,000 messages/second
});

Scenario 2: Database DoS

// Spam P_GET_ALL_USER to overwhelm database
setInterval(() => {
    ws.send(JSON.stringify({
        ConnectionMode: 1,
        Command: 42,  // P_GET_ALL_USER
        FromUser: { Id: 42 }
    }));
}, 10);  // 100 queries/second

Remediation:

Step 1: Implement Rate Limiting

const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');

const redis = new Redis();

// Rate limit storage
const rateLimitStore = {};

function checkRateLimit(userId, command, maxRequests, windowMs) {
    const key = `${userId}:${command}`;
    const now = Date.now();

    if (!rateLimitStore[key]) {
        rateLimitStore[key] = { count: 0, resetTime: now + windowMs };
    }

    const limit = rateLimitStore[key];

    if (now > limit.resetTime) {
        limit.count = 0;
        limit.resetTime = now + windowMs;
    }

    if (limit.count >= maxRequests) {
        return false;  // Rate limit exceeded
    }

    limit.count++;
    return true;
}

client.on('message', function (data) {
    const cMessage = JSON.parse(data);
    const userId = getUserId(cMessage.FromUser);

    // Rate limit: 100 messages per minute
    if (!checkRateLimit(userId, 'global', 100, 60000)) {
        logInfo(`Rate limit exceeded for user ${userId}`, logType.ERROR);
        client.send(JSON.stringify({
            Command: cCommand.P_CLOSE,
            Reason: cReason.ERROR,
            Message: 'Rate limit exceeded'
        }));
        client.close();
        return;
    }

    // Command-specific limits
    if (cMessage.Command === cCommand.P_GET_ALL_USER) {
        if (!checkRateLimit(userId, 'P_GET_ALL_USER', 10, 60000)) {
            logInfo(`Rate limit exceeded for P_GET_ALL_USER`, logType.ERROR);
            return;
        }
    }

    // Process message
    // ...
});

Step 2: Connection Limits

const MAX_CONNECTIONS_PER_USER = 5;
const MAX_CONNECTIONS_PER_IP = 10;

const connectionCounts = {
    byUser: {},
    byIP: {}
};

wss.on('connection', function (client, request) {
    const clientIP = request.socket.remoteAddress;

    // IP-based limit
    connectionCounts.byIP[clientIP] = (connectionCounts.byIP[clientIP] || 0) + 1;

    if (connectionCounts.byIP[clientIP] > MAX_CONNECTIONS_PER_IP) {
        logInfo(`Too many connections from IP ${clientIP}`, logType.ERROR);
        client.close();
        return;
    }

    client.on('close', () => {
        connectionCounts.byIP[clientIP]--;
    });

    // User-based limit (after authentication)
    // ...
});

Priority: 🔴 Do Now


🔴 CRITICAL-005: No Input Validation

Severity: Critical
CWE: CWE-20 (Improper Input Validation)
CVSS Score: 7.3 (High)

Location: All command handlers

Vulnerable Code:

function PSendMessage(client, command, message, fromUser, toUserList, clientInitiationTime) {
    try {
        var requestData = JSON.parse(message);  // ❌ No validation

        request
            .input('SenderId', sql.BigInt, fromUser.Id)
            .input('RecieverId', sql.BigInt, toUser.Id)
            .input('Text', sql.NVarChar, requestData.Text)  // ❌ No length/content check
            .input('FilePath', sql.NVarChar, requestData.FilePath)  // ❌ No path validation
            .execute('Message_InsertMessage', ...);
    }
}

Attack Vectors:

1. Extremely Large Messages

// Send 10MB message to crash server
ws.send(JSON.stringify({
    Command: 55,
    Message: JSON.stringify({
        Text: 'A'.repeat(10 * 1024 * 1024)  // 10MB string
    })
}));

2. Malicious File Paths

// Path traversal attempt
{
    FilePath: "../../../etc/passwd",
    CatFileTypeId: 2
}

3. Invalid User IDs

// Cause database errors
{
    FromUser: { Id: "'; DROP TABLE Users; --" },
    ToUserList: [{ Id: -1 }]
}

Remediation:

const Joi = require('joi');

// Define schemas for each command
const sendMessageSchema = Joi.object({
    Text: Joi.string().max(5000).required(),
    FilePath: Joi.string().uri().max(500).optional(),
    CatFileTypeId: Joi.number().integer().min(1).max(10).required()
});

function PSendMessage(client, command, message, fromUser, toUserList, clientInitiationTime) {
    try {
        var requestData = JSON.parse(message);

        // Validate input
        const { error, value } = sendMessageSchema.validate(requestData);
        if (error) {
            logInfo(`Invalid message format: ${error.message}`, logType.ERROR);
            PSendInvalidRequestCommand(client, '', command, cReason.INVALID_REQUEST);
            return;
        }

        // Validate user IDs
        if (!Number.isInteger(fromUser.Id) || fromUser.Id <= 0) {
            logInfo(`Invalid fromUser.Id: ${fromUser.Id}`, logType.ERROR);
            PSendInvalidRequestCommand(client, '', command, cReason.INVALID_USER);
            return;
        }

        // Sanitize file path
        if (value.FilePath) {
            const allowedDomains = ['media.psyter.com', 'cdn.psyter.com'];
            const url = new URL(value.FilePath);
            if (!allowedDomains.includes(url.hostname)) {
                logInfo(`Invalid file path domain: ${url.hostname}`, logType.ERROR);
                PSendInvalidRequestCommand(client, '', command, cReason.INVALID_REQUEST);
                return;
            }
        }

        // Process validated message
        request
            .input('SenderId', sql.BigInt, fromUser.Id)
            .input('RecieverId', sql.BigInt, toUser.Id)
            .input('Text', sql.NVarChar, value.Text)
            .input('FilePath', sql.NVarChar, value.FilePath || null)
            .input('CatFileTypeId', sql.TinyInt, value.CatFileTypeId)
            .execute('Message_InsertMessage', ...);
    }
}

Priority: 🔴 Do Now


High Priority Issues

🟠 HIGH-001: Unencrypted Database Connections

Severity: High
CWE: CWE-319 (Cleartext Transmission of Sensitive Information)

Current State:

dbConfig = {
    user: 'PsyterUser',
    password: 'Zo@mb!sPsyter',
    server: '51.89.234.59',
    database: 'Psyter'
    // ❌ No SSL/TLS encryption
};

Impact:
- Database credentials transmitted in plaintext on network
- Patient health records (PHI) transmitted unencrypted
- Vulnerable to network sniffing

Remediation:

dbConfig = {
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    server: process.env.DB_SERVER,
    database: process.env.DB_NAME,
    options: {
        encrypt: true,  // Enable TLS
        trustServerCertificate: false,  // Validate cert
        enableArithAbort: true
    }
};

Priority: Do Now


🟠 HIGH-002: No Session Token Expiration

Severity: High
CWE: CWE-613 (Insufficient Session Expiration)

Current State:
- Once authenticated, session never expires
- User remains logged in indefinitely
- No forced re-authentication

Impact:
- Stolen device = perpetual access
- No way to invalidate compromised sessions
- Compliance violation (HIPAA requires session timeout)

Remediation:

const SESSION_TIMEOUT = 24 * 60 * 60 * 1000;  // Configurable timeout

client.on('message', function (data) {
    const now = Date.now();

    if (!client.lastActivity) {
        client.lastActivity = now;
    }

    // Check session timeout
    if (now - client.lastActivity > SESSION_TIMEOUT) {
        logInfo(`Session expired for user ${client.UserId}`, logType.INFO);
        PClose(client, 'Session expired', cReason.RECONNECT);
        return;
    }

    client.lastActivity = now;

    // Process message
    // ...
});

Priority: Do Now


🟠 HIGH-003: Missing CORS Validation

Severity: High
CWE: CWE-346 (Origin Validation Error)

Current State:
- No origin validation on WebSocket connections
- Any website can connect to WebSocket server

Impact:
- Malicious website can connect to user’s session
- Cross-Site WebSocket Hijacking (CSWSH)
- Unauthorized access to user data

Remediation:

wss.on('connection', function (client, request) {
    const origin = request.headers.origin;
    const allowedOrigins = [
        'https://www.psyter.com',
        'https://portal.psyter.com',
        'https://app.psyter.com'
    ];

    if (origin && !allowedOrigins.includes(origin)) {
        logInfo(`Rejected connection from invalid origin: ${origin}`, logType.ERROR);
        client.close();
        return;
    }

    // Continue connection
    // ...
});

Priority: Do Next


🟠 HIGH-004: Insufficient Logging of Security Events

Severity: High
CWE: CWE-778 (Insufficient Logging)

Current State:
- No logging of failed authentication attempts
- No logging of rate limit violations
- No logging of suspicious activity

Impact:
- Cannot detect brute force attacks
- Cannot investigate security incidents
- No audit trail for compliance

Remediation:

function logSecurityEvent(event, userId, clientIP, details) {
    const securityLog = {
        timestamp: new Date().toISOString(),
        event: event,
        userId: userId,
        clientIP: clientIP,
        details: details,
        severity: 'SECURITY'
    };

    logger.warn(JSON.stringify(securityLog));

    // Also send to SIEM system
    if (process.env.SIEM_ENABLED) {
        sendToSIEM(securityLog);
    }
}

// Usage
function PAuthenticate(command, client, data) {
    // ...
    if (objUser.length === 0) {
        logSecurityEvent('AUTH_FAILED', data.communicationKey, client.remoteAddress, {
            reason: 'Invalid credentials',
            communicationKey: data.communicationKey
        });
        PClose(client, '', cReason.INVALID_USER);
        return;
    }
}

Priority: Do Next


🟠 HIGH-005: No IP Whitelisting for Admin Commands

Severity: High
CWE: CWE-284 (Improper Access Control)

Current State:

case cCommand.P_GET_ALL_USER:
    // ❌ No IP restriction - anyone can call this
    PGetAllUser(command, fromUser.Id);
    break;

Impact:
- Sensitive admin commands accessible from anywhere
- User data exposure risk

Remediation:

const ADMIN_IP_WHITELIST = [
    '203.0.113.10',  // Office IP
    '203.0.113.20',  // VPN gateway
    '::ffff:127.0.0.1'  // Localhost
];

function isAdminCommand(command) {
    const adminCommands = [
        cCommand.P_GET_ALL_USER,
        cCommand.P_REGISTER_USER,
        cCommand.P_MANAGE_USER_FRIEND
    ];
    return adminCommands.includes(command);
}

function PProcessCommand(client, data, command, fromUser, ...) {
    if (isAdminCommand(command)) {
        const clientIP = client.upgradeReq.socket.remoteAddress;

        if (!ADMIN_IP_WHITELIST.includes(clientIP)) {
            logSecurityEvent('UNAUTHORIZED_ADMIN_ACCESS', fromUser.Id, clientIP, {
                command: getKeyByValue(cCommand, command)
            });
            PClose(client, 'Unauthorized', cReason.NO_PERMISSION);
            return;
        }
    }

    // Process command
    // ...
}

Priority: Do Next


🟠 HIGH-006: Vulnerable to Replay Attacks

Severity: High
CWE: CWE-294 (Authentication Bypass by Capture-replay)

Current State:
- No nonce or timestamp validation
- Same authentication token can be reused
- No challenge-response mechanism

Impact:
- Attacker intercepts authentication request
- Replays it later to gain access

Remediation:

function PAuthenticate(command, client, data) {
    const requestTimestamp = new Date(data.timestamp);
    const now = new Date();
    const maxAge = 5 * 60 * 1000;  // 5 minutes

    // Check timestamp
    if (Math.abs(now - requestTimestamp) > maxAge) {
        logSecurityEvent('AUTH_REPLAY_DETECTED', data.communicationKey, client.remoteAddress, {
            timestamp: data.timestamp,
            age: Math.abs(now - requestTimestamp)
        });
        PClose(client, 'Request expired', cReason.INVALID_REQUEST);
        return;
    }

    // Check nonce (one-time use token)
    const nonce = data.nonce;
    const nonceKey = `nonce:${nonce}`;

    redis.get(nonceKey, (err, value) => {
        if (value) {
            logSecurityEvent('AUTH_NONCE_REUSE', data.communicationKey, client.remoteAddress, {
                nonce: nonce
            });
            PClose(client, 'Replay detected', cReason.INVALID_REQUEST);
            return;
        }

        // Store nonce with expiration
        redis.setex(nonceKey, maxAge / 1000, '1');

        // Continue authentication
        // ...
    });
}

Priority: Do Next


🟠 HIGH-007: Cleartext Logging of Sensitive Data

Severity: High
CWE: CWE-532 (Insertion of Sensitive Information into Log File)

Current State:

function logInfo(message, cLogType = logType.INFO) {
    logger.info(message);
    // Logs may contain passwords, tokens, etc.
}

// Example
logInfo("CLIENT  -->  cCommand.P_AUTHENTICATE  -->  communicationKey = ABC123");
// ❌ Verification code logged in plaintext

Impact:
- Passwords, tokens visible in logs
- Log files become treasure trove for attackers
- Compliance violation (PCI-DSS prohibits logging PANs)

Remediation:

function sanitizeLogMessage(message) {
    // Redact common sensitive patterns
    message = message.replace(/password[=:]\s*\S+/gi, 'password=***REDACTED***');
    message = message.replace(/token[=:]\s*\S+/gi, 'token=***REDACTED***');
    message = message.replace(/communicationKey[=:]\s*\S+/gi, 'communicationKey=***REDACTED***');
    message = message.replace(/privateKey[=:]\s*[^,}]+/gi, 'privateKey=***REDACTED***');
    return message;
}

function logInfo(message, cLogType = logType.INFO) {
    if (enableLog) {
        const sanitized = sanitizeLogMessage(message);
        logger.info(sanitized);
    }
}

Priority: Do Next


🟠 HIGH-008: No Protection Against CSRF

Severity: High
CWE: CWE-352 (Cross-Site Request Forgery)

Current State:
- WebSocket connections don’t use CSRF tokens
- Vulnerable if used in web context

Remediation:

// Client-side (Web)
const csrfToken = getCsrfTokenFromCookie();
const ws = new WebSocket(
    `wss://server:3333/?connectionMode=1&csrfToken=${csrfToken}&...`
);

// Server-side
wss.on('connection', function (client, request) {
    const query = client.upgradeReq.url.replace('/', '');
    const csrfToken = getQueryVariable(query, 'csrfToken');

    if (client.ClientPlatform === cClientPlatform.WEB) {
        if (!validateCsrfToken(csrfToken, request.headers.cookie)) {
            logSecurityEvent('CSRF_DETECTED', null, client.remoteAddress, {
                csrfToken: csrfToken
            });
            client.close();
            return;
        }
    }
});

Priority: Plan


Medium Priority Issues

🟡 MEDIUM-001: Outdated Dependencies with Known Vulnerabilities

Severity: Medium
CWE: CWE-1104 (Use of Unmaintained Third Party Components)

Current State:

{
  "firebase-admin": "8.9.1",  // Current: 12.x (3+ years old)
  "mssql": "6.0.1",           // Current: 10.x (security patches missed)
  "ws": "7.2.1"               // Current: 8.x
}

Known Vulnerabilities:
- firebase-admin@8.9.1: CVE-2021-xxxxx (hypothetical)
- Older versions may have unpatched security bugs

Remediation:

npm audit
npm audit fix
npm update firebase-admin
npm update mssql
npm update ws
npm install

Priority: Do Next


🟡 MEDIUM-002: Insufficient Password Complexity Requirements

Severity: Medium
CWE: CWE-521 (Weak Password Requirements)

Current State:

function PUpdatePassword(command, password, userId) {
    // ❌ No password strength validation
    request
        .input('Password', sql.NVarChar, password)
        .execute('COB_Update_Password', ...);
}

Remediation:

const passwordValidator = require('password-validator');

const passwordSchema = new passwordValidator();
passwordSchema
    .is().min(12)
    .is().max(100)
    .has().uppercase()
    .has().lowercase()
    .has().digits()
    .has().symbols()
    .has().not().spaces();

function PUpdatePassword(command, password, userId) {
    if (!passwordSchema.validate(password)) {
        logInfo(`Weak password rejected for user ${userId}`, logType.ERROR);
        PSendCommand(clientList, '', command, cReason.INVALID_REQUEST);
        return;
    }

    // Continue
    // ...
}

Priority: Do Next


🟡 MEDIUM-003: No Brute Force Protection

Severity: Medium
CWE: CWE-307 (Improper Restriction of Excessive Authentication Attempts)

Current State:
- Unlimited authentication attempts allowed
- No account lockout mechanism

Remediation:

const loginAttempts = {};

function PAuthenticate(command, client, data) {
    const key = `${data.communicationKey}:${client.remoteAddress}`;

    if (!loginAttempts[key]) {
        loginAttempts[key] = { count: 0, lockUntil: null };
    }

    const attempt = loginAttempts[key];

    // Check if locked
    if (attempt.lockUntil && Date.now() < attempt.lockUntil) {
        const remainingTime = Math.ceil((attempt.lockUntil - Date.now()) / 1000);
        logSecurityEvent('BRUTE_FORCE_BLOCKED', data.communicationKey, client.remoteAddress, {
            remainingLockTime: remainingTime
        });
        PClose(client, `Locked for ${remainingTime}s`, cReason.ERROR);
        return;
    }

    // Try authentication
    request.execute('COB_Authenticate_Get_User_List', (err, result) => {
        if (err || result.recordsets[0].length === 0) {
            attempt.count++;

            if (attempt.count >= 5) {
                attempt.lockUntil = Date.now() + (15 * 60 * 1000);  // 15 min lockout
                logSecurityEvent('ACCOUNT_LOCKED', data.communicationKey, client.remoteAddress, {
                    attempts: attempt.count
                });
            }

            PClose(client, '', cReason.INVALID_USER);
            return;
        }

        // Success - reset attempts
        delete loginAttempts[key];
        PAuthenticateSuccess(command, client, data, ...);
    });
}

Priority: Do Next


🟡 MEDIUM-004: Insecure WebSocket Compression

Severity: Medium
CWE: CWE-327 (Use of a Broken or Risky Cryptographic Algorithm)

Current State:
- No compression configured (good)
- If added later without care, vulnerable to CRIME/BREACH attacks

Recommendation:

// If compression needed, disable for sensitive data
const wss = new ws.Server({ 
    server: sslSrv,
    perMessageDeflate: false  // Disable to prevent compression attacks
});

Priority: Monitor


🟡 MEDIUM-005: No Content Security Policy (CSP)

Severity: Medium
CWE: CWE-1021 (Improper Restriction of Rendered UI Layers)

Current State:
- WebSocket server doesn’t set CSP headers
- If web interface added, vulnerable to XSS

Remediation:

// If adding HTTP endpoints
app.use((req, res, next) => {
    res.setHeader(
        'Content-Security-Policy',
        "default-src 'self'; script-src 'self'; connect-src 'self' wss://nodeserver.psyter.com"
    );
    next();
});

Priority: Plan


🟡 MEDIUM-006 to MEDIUM-012: Additional Issues

MEDIUM-006: No SQL injection protection in legacy code
MEDIUM-007: Missing security headers
MEDIUM-008: Insecure random number generation (UniqueToken)
MEDIUM-009: No protection against timing attacks
MEDIUM-010: Missing certificate pinning guidance
MEDIUM-011: No intrusion detection system (IDS) integration
MEDIUM-012: Vulnerable to slowloris attacks

(Details omitted for brevity - see full security documentation)


Low Priority Issues

🟢 LOW-001: Process Running as Root

Severity: Low
CWE: CWE-250 (Execution with Unnecessary Privileges)

Remediation:

# Create dedicated user
sudo useradd -r -s /bin/false nodeserver

# Run as non-root
sudo -u nodeserver node server.js --clientmode=4

Priority: Plan


🟢 LOW-002 to LOW-006: Additional Low-Priority Items

(Details omitted for brevity)


Security Best Practices Analysis

✅ What’s Done Well

  1. Parameterized SQL Queries - All database calls use request.input(), preventing SQL injection
  2. HTTPS/WSS - SSL/TLS encryption for all connections
  3. Error Handling - Try-catch blocks prevent information disclosure
  4. Logging - Comprehensive logging of operations

❌ What Needs Improvement

  1. Credential Management - All credentials hardcoded
  2. Input Validation - Minimal validation of user input
  3. Rate Limiting - No protection against abuse
  4. Session Management - No expiration or invalidation
  5. Audit Logging - No security event logging

Threat Modeling

STRIDE Analysis

Spoofing:
- ❌ Weak authentication (communication keys can be guessed)
- ❌ No mutual TLS for server-to-server

Tampering:
- ✅ SSL/TLS prevents network tampering
- ❌ No message signing (can’t verify integrity)

Repudiation:
- ❌ No non-repudiation (users can deny actions)
- ❌ Insufficient audit logging

Information Disclosure:
- ❌ Credentials in source code
- ❌ Unencrypted database connections
- ❌ Sensitive data in logs

Denial of Service:
- ❌ No rate limiting
- ❌ No connection limits
- ❌ Vulnerable to resource exhaustion

Elevation of Privilege:
- ❌ No role-based access control
- ❌ Admin commands not restricted by IP


Compliance Considerations

HIPAA (Health Insurance Portability and Accountability Act)

Required Security Controls:
- ✅ Access controls (authentication required)
- ❌ FAIL: Audit controls (insufficient logging)
- ❌ FAIL: Integrity controls (no message signing)
- ✅ Transmission security (SSL/TLS)
- ❌ FAIL: Encryption at rest (database not encrypted)

Required for Compliance:
1. Implement comprehensive audit logging
2. Encrypt database connections
3. Add session timeout (30-minute idle)
4. Implement role-based access control
5. Add breach notification mechanism

PCI-DSS (Payment Card Industry Data Security Standard)

If handling payment data:
- ❌ FAIL: Never log card numbers (not applicable, but ensure)
- ❌ FAIL: Strong cryptography required
- ❌ FAIL: Regular security testing needed

GDPR (General Data Protection Regulation)

Required:
- ✅ Secure transmission (SSL/TLS)
- ❌ FAIL: Right to erasure (no data deletion API)
- ❌ FAIL: Data minimization (logs may contain excessive PII)
- ❌ FAIL: Breach notification (no detection mechanism)


Remediation Roadmap

Phase 1: Critical Fixes

Issue Task Owner
CRITICAL-001 Externalize database credentials DevOps
CRITICAL-002 Move Firebase keys to JSON files DevOps
CRITICAL-003 Rotate SSL passphrases DevOps
CRITICAL-004 Implement rate limiting Backend Dev
CRITICAL-005 Add input validation Backend Dev

Phase 2: High-Priority Fixes

Issue Task Owner
HIGH-001 Enable database encryption DevOps
HIGH-002 Add session timeout Backend Dev
HIGH-003 Implement CORS validation Backend Dev
HIGH-004 Add security event logging Backend Dev
HIGH-005 IP whitelist admin commands Backend Dev
HIGH-006 Add replay attack protection Backend Dev
HIGH-007 Sanitize log messages Backend Dev
HIGH-008 Add CSRF protection Backend Dev

Phase 3: Medium-Priority Fixes

Issue Task Owner
MEDIUM-001 Update dependencies DevOps
MEDIUM-002 Password complexity rules Backend Dev
MEDIUM-003 Brute force protection Backend Dev
MEDIUM-004-012 Various improvements Backend Dev

Phase 4: Low-Priority & Hardening

  • Run as non-root user
  • Add security headers
  • Implement certificate pinning
  • IDS integration
  • Penetration testing

Security Testing Recommendations

1. Penetration Testing

Scope:
- WebSocket authentication bypass
- Rate limiting effectiveness
- Input validation boundaries
- SQL injection attempts
- DoS resilience

Tools:
- Burp Suite (WebSocket support)
- OWASP ZAP
- SQLMap (database testing)
- Custom WebSocket fuzzer

2. Static Code Analysis

npm install -g eslint eslint-plugin-security
eslint --plugin security server.js

3. Dependency Scanning

npm audit
npm install -g snyk
snyk test

4. Dynamic Analysis

# Load testing
npm install -g artillery
artillery quick --count 100 --num 1000 wss://server:3333

Summary

Critical Findings Requiring Immediate Action:
1. Hardcoded credentials (database, Firebase, SSL)
2. No rate limiting (DoS vulnerability)
3. Insufficient input validation
4. No session expiration
5. Missing security logging

Risk Assessment:
- Current State: HIGH RISK (not production-ready)
- After Critical Fixes: MEDIUM RISK
- After All Fixes: LOW RISK (acceptable for production)

Recommended Next Steps:
1. Form security task force (2-3 developers)
2. Complete Phase 1 (critical fixes)
3. Deploy to staging for security testing
4. Complete Phase 2 (high-priority fixes)
5. Conduct penetration testing
6. Deploy to production with monitoring

This security audit provides a comprehensive roadmap for hardening the NodeServer against current and emerging threats.