Security fixes: Remove PAT, add idempotency, fix admin auth
- Remove exposed GitHub PAT from git remote URL - Remove admin password plaintext fallback (bcrypt only) - Add webhook idempotency protection to prevent duplicate payments - Fix webhook error handling to return 500 on errors (enables retry) - Upgrade archiver to v7 to fix npm vulnerabilities - Add production environment validation for critical secrets - Add comprehensive security review documentation
This commit is contained in:
@@ -15,7 +15,7 @@
|
|||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"archiver": "^6.0.1",
|
"archiver": "^7.0.1",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"pdfkit": "^0.17.2",
|
"pdfkit": "^0.17.2",
|
||||||
|
|||||||
122
chat/server.js
122
chat/server.js
@@ -185,6 +185,8 @@ const FEATURE_REQUESTS_FILE = path.join(STATE_DIR, 'feature-requests.json');
|
|||||||
const CONTACT_MESSAGES_FILE = path.join(STATE_DIR, 'contact-messages.json');
|
const CONTACT_MESSAGES_FILE = path.join(STATE_DIR, 'contact-messages.json');
|
||||||
const INVOICES_FILE = path.join(STATE_DIR, 'invoices.json');
|
const INVOICES_FILE = path.join(STATE_DIR, 'invoices.json');
|
||||||
const INVOICES_DIR = path.join(STATE_DIR, 'invoices');
|
const INVOICES_DIR = path.join(STATE_DIR, 'invoices');
|
||||||
|
const PROCESSED_WEBHOOKS_FILE = path.join(STATE_DIR, 'processed-webhooks.json');
|
||||||
|
const PROCESSED_WEBHOOKS_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||||
// One-off top-up discounts (Business 2.5%, Enterprise 5%; boost add-ons keep higher 10%/25% rates)
|
// One-off top-up discounts (Business 2.5%, Enterprise 5%; boost add-ons keep higher 10%/25% rates)
|
||||||
const BUSINESS_TOPUP_DISCOUNT = 0.025;
|
const BUSINESS_TOPUP_DISCOUNT = 0.025;
|
||||||
const ENTERPRISE_TOPUP_DISCOUNT = 0.05;
|
const ENTERPRISE_TOPUP_DISCOUNT = 0.05;
|
||||||
@@ -693,6 +695,10 @@ let lastMemoryCleanup = 0;
|
|||||||
let memoryCleanupTimer = null;
|
let memoryCleanupTimer = null;
|
||||||
const recentlyCleanedSessions = new Set(); // Track recently cleaned sessions to avoid redundant work
|
const recentlyCleanedSessions = new Set(); // Track recently cleaned sessions to avoid redundant work
|
||||||
|
|
||||||
|
// Processed webhooks for idempotency protection (prevents duplicate payment processing)
|
||||||
|
const processedWebhookEvents = new Map(); // eventId -> { processedAt, type }
|
||||||
|
let processedWebhooksPersistTimer = null;
|
||||||
|
|
||||||
// Track spawned child processes for proper cleanup
|
// Track spawned child processes for proper cleanup
|
||||||
const childProcesses = new Map(); // processId -> { pid, startTime, sessionId, messageId }
|
const childProcesses = new Map(); // processId -> { pid, startTime, sessionId, messageId }
|
||||||
|
|
||||||
@@ -1087,6 +1093,72 @@ function stopMemoryCleanup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Webhook Idempotency Protection
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
async function loadProcessedWebhooks() {
|
||||||
|
try {
|
||||||
|
await ensureStateFile();
|
||||||
|
const raw = await fs.readFile(PROCESSED_WEBHOOKS_FILE, 'utf8').catch(() => null);
|
||||||
|
if (raw) {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
if (parsed && typeof parsed === 'object') {
|
||||||
|
const now = Date.now();
|
||||||
|
// Clean up old entries on load
|
||||||
|
for (const [eventId, data] of Object.entries(parsed)) {
|
||||||
|
if (data && data.processedAt && (now - data.processedAt) < PROCESSED_WEBHOOKS_MAX_AGE_MS) {
|
||||||
|
processedWebhookEvents.set(eventId, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log('Loaded processed webhooks', { count: processedWebhookEvents.size });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log('Failed to load processed webhooks', { error: String(error) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistProcessedWebhooks() {
|
||||||
|
try {
|
||||||
|
await ensureStateFile();
|
||||||
|
const now = Date.now();
|
||||||
|
const obj = {};
|
||||||
|
// Only persist recent entries
|
||||||
|
for (const [eventId, data] of processedWebhookEvents.entries()) {
|
||||||
|
if (data && data.processedAt && (now - data.processedAt) < PROCESSED_WEBHOOKS_MAX_AGE_MS) {
|
||||||
|
obj[eventId] = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await safeWriteFile(PROCESSED_WEBHOOKS_FILE, JSON.stringify(obj, null, 2));
|
||||||
|
} catch (error) {
|
||||||
|
log('Failed to persist processed webhooks', { error: String(error) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWebhookProcessed(eventId) {
|
||||||
|
if (!eventId) return false;
|
||||||
|
return processedWebhookEvents.has(eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function markWebhookProcessed(eventId, eventType) {
|
||||||
|
if (!eventId) return;
|
||||||
|
processedWebhookEvents.set(eventId, {
|
||||||
|
processedAt: Date.now(),
|
||||||
|
type: eventType || 'unknown'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Debounce persistence
|
||||||
|
if (processedWebhooksPersistTimer) {
|
||||||
|
clearTimeout(processedWebhooksPersistTimer);
|
||||||
|
}
|
||||||
|
processedWebhooksPersistTimer = setTimeout(() => {
|
||||||
|
persistProcessedWebhooks().catch(err => {
|
||||||
|
log('Failed to persist processed webhooks (debounced)', { error: String(err) });
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a child process for tracking
|
* Registers a child process for tracking
|
||||||
* @param {string} processId - Unique process identifier
|
* @param {string} processId - Unique process identifier
|
||||||
@@ -14385,6 +14457,13 @@ async function handleDodoWebhook(req, res) {
|
|||||||
const event = JSON.parse(rawBody);
|
const event = JSON.parse(rawBody);
|
||||||
log('Dodo webhook received', { type: event.type, id: event.id });
|
log('Dodo webhook received', { type: event.type, id: event.id });
|
||||||
|
|
||||||
|
// Idempotency check - prevent duplicate processing
|
||||||
|
const eventId = event.id || event.data?.id;
|
||||||
|
if (eventId && isWebhookProcessed(eventId)) {
|
||||||
|
log('Dodo webhook already processed, skipping', { eventId, type: event.type });
|
||||||
|
return sendJson(res, 200, { received: true, duplicate: true });
|
||||||
|
}
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case 'payment_succeeded':
|
case 'payment_succeeded':
|
||||||
case 'subscription_payment_succeeded':
|
case 'subscription_payment_succeeded':
|
||||||
@@ -14454,10 +14533,18 @@ async function handleDodoWebhook(req, res) {
|
|||||||
log('Unhandled Dodo webhook event', { type: event.type });
|
log('Unhandled Dodo webhook event', { type: event.type });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark as processed for idempotency
|
||||||
|
const eventId = event.id || event.data?.id;
|
||||||
|
if (eventId) {
|
||||||
|
await markWebhookProcessed(eventId, event.type);
|
||||||
|
}
|
||||||
|
|
||||||
sendJson(res, 200, { received: true });
|
sendJson(res, 200, { received: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log('Dodo webhook error', { error: String(error), stack: error.stack });
|
log('Dodo webhook error', { error: String(error), stack: error.stack });
|
||||||
sendJson(res, 200, { received: true });
|
// Return 500 to trigger webhook retry from payment provider
|
||||||
|
// This ensures failed payments aren't silently lost
|
||||||
|
sendJson(res, 500, { error: 'Webhook processing failed', received: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15792,15 +15879,13 @@ async function handleAdminLogin(req, res) {
|
|||||||
return sendJson(res, 401, { error: 'Incorrect credentials' });
|
return sendJson(res, 401, { error: 'Incorrect credentials' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate password using bcrypt
|
// Validate password using bcrypt - no plaintext fallback for security
|
||||||
let passwordValid = false;
|
if (!adminPasswordHash) {
|
||||||
if (adminPasswordHash) {
|
log('admin login failed: bcrypt hash not initialized', { ip: clientIp });
|
||||||
passwordValid = await bcrypt.compare(pass, adminPasswordHash);
|
return sendJson(res, 500, { error: 'Authentication system not ready. Please try again.' });
|
||||||
} else {
|
|
||||||
// Fallback to plaintext comparison if hashing failed at startup
|
|
||||||
passwordValid = pass === ADMIN_PASSWORD.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const passwordValid = await bcrypt.compare(pass, adminPasswordHash);
|
||||||
if (!passwordValid) {
|
if (!passwordValid) {
|
||||||
log('failed admin login', { user: userNormalized, ip: clientIp, reason: 'invalid_password' });
|
log('failed admin login', { user: userNormalized, ip: clientIp, reason: 'invalid_password' });
|
||||||
return sendJson(res, 401, { error: 'Incorrect credentials' });
|
return sendJson(res, 401, { error: 'Incorrect credentials' });
|
||||||
@@ -19272,6 +19357,26 @@ async function routeInternal(req, res, url, pathname) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
// Production environment validation
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
const criticalEnvVars = [];
|
||||||
|
|
||||||
|
if (isProduction) {
|
||||||
|
if (!process.env.USER_SESSION_SECRET && !process.env.SESSION_SECRET) {
|
||||||
|
criticalEnvVars.push('USER_SESSION_SECRET or SESSION_SECRET');
|
||||||
|
}
|
||||||
|
if (!process.env.DODO_PAYMENTS_API_KEY && !process.env.DODO_API_KEY) {
|
||||||
|
criticalEnvVars.push('DODO_PAYMENTS_API_KEY');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (criticalEnvVars.length > 0) {
|
||||||
|
console.error('❌ CRITICAL: Missing required environment variables for production:');
|
||||||
|
criticalEnvVars.forEach(v => console.error(` - ${v}`));
|
||||||
|
console.error('Please set these environment variables before running in production.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
process.on('uncaughtException', async (error) => {
|
process.on('uncaughtException', async (error) => {
|
||||||
log('Uncaught Exception, saving state before exit', { error: String(error), stack: error.stack });
|
log('Uncaught Exception, saving state before exit', { error: String(error), stack: error.stack });
|
||||||
try {
|
try {
|
||||||
@@ -19312,6 +19417,7 @@ async function bootstrap() {
|
|||||||
await loadAffiliatesDb();
|
await loadAffiliatesDb();
|
||||||
await loadWithdrawalsDb();
|
await loadWithdrawalsDb();
|
||||||
await loadTrackingData();
|
await loadTrackingData();
|
||||||
|
await loadProcessedWebhooks();
|
||||||
await loadFeatureRequestsDb();
|
await loadFeatureRequestsDb();
|
||||||
contactMessagesDb = await loadContactMessagesDb();
|
contactMessagesDb = await loadContactMessagesDb();
|
||||||
await ensureAssetsDir();
|
await ensureAssetsDir();
|
||||||
|
|||||||
514
security-review/SECURITY_AND_FUNCTIONALITY_REVIEW.md
Normal file
514
security-review/SECURITY_AND_FUNCTIONALITY_REVIEW.md
Normal file
@@ -0,0 +1,514 @@
|
|||||||
|
# Security and Functionality Review
|
||||||
|
## Plugin Compass AI App Builder - Pre-Launch Assessment
|
||||||
|
|
||||||
|
**Review Date:** February 20, 2026
|
||||||
|
**Review Scope:** Main chat application, builder, server, and payment systems
|
||||||
|
**Status:** CRITICAL ISSUES FOUND - DO NOT LAUNCH WITHOUT REMEDIATION
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critical Security Issues (MUST FIX BEFORE LAUNCH)
|
||||||
|
|
||||||
|
### 1. EXPOSED GITHUB PERSONAL ACCESS TOKEN (CRITICAL)
|
||||||
|
|
||||||
|
**Location:** `.git/config` line 7
|
||||||
|
**Severity:** CRITICAL
|
||||||
|
**CVSS:** 9.1 (Critical)
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
```
|
||||||
|
url = https://southseact-3d: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@github.com/...
|
||||||
|
```
|
||||||
|
|
||||||
|
A GitHub Personal Access Token (PAT) is embedded directly in the git remote URL configuration. This token has been committed to the repository and provides full access to the GitHub account.
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- Complete compromise of the GitHub account
|
||||||
|
- Access to all repositories
|
||||||
|
- Potential for supply chain attacks
|
||||||
|
- Token may have already been scraped by automated tools
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. **IMMEDIATELY** revoke the exposed token at https://github.com/settings/tokens
|
||||||
|
2. Remove the token from git config:
|
||||||
|
```bash
|
||||||
|
git remote set-url origin https://github.com/southseact-3d/shopify-ai-backup.git
|
||||||
|
```
|
||||||
|
3. Force push to remove from history if necessary
|
||||||
|
4. Use SSH keys or a credential manager instead
|
||||||
|
5. Never commit credentials to version control
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Admin Password Plaintext Fallback (HIGH)
|
||||||
|
|
||||||
|
**Location:** `chat/server.js` lines 15796-15802
|
||||||
|
**Severity:** HIGH
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
```javascript
|
||||||
|
// Validate password using bcrypt
|
||||||
|
let passwordValid = false;
|
||||||
|
if (adminPasswordHash) {
|
||||||
|
passwordValid = await bcrypt.compare(pass, adminPasswordHash);
|
||||||
|
} else {
|
||||||
|
// Fallback to plaintext comparison if hashing failed at startup
|
||||||
|
passwordValid = pass === ADMIN_PASSWORD.trim();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The admin login has a fallback to plaintext password comparison when bcrypt hashing fails. This creates a timing attack vulnerability and undermines the security model.
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- Timing attacks possible on plaintext comparison
|
||||||
|
- Weaker authentication if bcrypt initialization fails silently
|
||||||
|
- Admin account compromise potential
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. Remove the plaintext fallback entirely
|
||||||
|
2. Fail hard if bcrypt initialization fails
|
||||||
|
3. Log and alert on any password comparison failures
|
||||||
|
4. Add logging for bcrypt initialization status
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Session Secret Auto-Generation Warning (MEDIUM)
|
||||||
|
|
||||||
|
**Location:** `chat/server.js` lines 377-385
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
```javascript
|
||||||
|
const USER_SESSION_SECRET = process.env.USER_SESSION_SECRET || process.env.SESSION_SECRET || (() => {
|
||||||
|
const generatedSecret = randomBytes(32).toString('hex');
|
||||||
|
console.warn('⚠️ WARNING: No USER_SESSION_SECRET or SESSION_SECRET found. Generated a random secret...');
|
||||||
|
return generatedSecret;
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
The session secret is auto-generated if not provided. While there's a warning, if this happens in production:
|
||||||
|
- All user sessions will be invalidated on server restart
|
||||||
|
- An attacker who reads logs could potentially forge session tokens
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. Make `USER_SESSION_SECRET` a required environment variable in production
|
||||||
|
2. Exit the application if not set in production mode
|
||||||
|
3. Never log generated secrets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Webhook Error Handling Returns 200 (MEDIUM)
|
||||||
|
|
||||||
|
**Location:** `chat/server.js` lines 14458-14461
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
```javascript
|
||||||
|
} catch (error) {
|
||||||
|
log('Dodo webhook error', { error: String(error), stack: error.stack });
|
||||||
|
sendJson(res, 200, { received: true });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The webhook handler returns HTTP 200 even on errors, which prevents the payment provider from retrying. This could lead to:
|
||||||
|
- Lost payment confirmations
|
||||||
|
- Inconsistent subscription states
|
||||||
|
- Customers charged but not credited
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. Return appropriate HTTP error codes (400/500) for actual errors
|
||||||
|
2. Only return 200 when processing is successful
|
||||||
|
3. Implement proper retry handling for transient errors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## High Priority Issues
|
||||||
|
|
||||||
|
### 5. No Idempotency Protection for Payments (HIGH)
|
||||||
|
|
||||||
|
**Location:** Payment handling functions throughout `server.js`
|
||||||
|
**Severity:** HIGH
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
Payment processing lacks idempotency keys. If the same webhook is delivered multiple times (which payment providers do by design), the same payment could be processed multiple times.
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- Customers could receive duplicate token credits
|
||||||
|
- Revenue loss from over-crediting
|
||||||
|
- Inconsistent account states
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. Store processed webhook event IDs
|
||||||
|
2. Check for duplicates before processing
|
||||||
|
3. Implement idempotency keys for all payment operations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Payment Method Save Endpoint Logs Without Verification (MEDIUM)
|
||||||
|
|
||||||
|
**Location:** `chat/server.js` lines 14580-14582
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
```javascript
|
||||||
|
if (inferredType === 'payment_method_save') {
|
||||||
|
log('payment_method_save: payment method added via webhook', { userId, eventId: event.id, checkoutId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Payment method save events are logged but not properly processed/verified.
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. Verify the payment method save with the payment provider
|
||||||
|
2. Update user record with payment method details
|
||||||
|
3. Implement proper state tracking
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Email Marketing Webhook Hardcoded (LOW)
|
||||||
|
|
||||||
|
**Location:** `chat/server.js` line 11441
|
||||||
|
**Severity:** LOW
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
```javascript
|
||||||
|
await fetch('https://emailmarketing.modelrailway3d.co.uk/api/webhooks/incoming/wh_0Z49zi_DGj4-lKJMOPO8-g', ...
|
||||||
|
```
|
||||||
|
|
||||||
|
A webhook URL with an apparent API key is hardcoded. This should be configurable.
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. Move to environment variable
|
||||||
|
2. Make configurable per environment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Functionality Issues
|
||||||
|
|
||||||
|
### 8. Builder State Not Properly Cleared on Logout
|
||||||
|
|
||||||
|
**Location:** `chat/public/builder.js` lines 52-66
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
The builder state is persisted to localStorage but the `clearBuilderState` function preserves `pluginPrompt` and `subsequentPrompt`. When a user logs out, sensitive state could persist.
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. Clear all builder state on logout
|
||||||
|
2. Clear state when switching accounts
|
||||||
|
3. Consider encrypting sensitive builder state
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. External Testing MCP Server Path
|
||||||
|
|
||||||
|
**Location:** `chat/server.js` lines 9640-9651
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
```javascript
|
||||||
|
const wpMcpServerPath = path.resolve(__dirname, '../opencode/mcp-servers/wp-cli-testing/index.js');
|
||||||
|
```
|
||||||
|
|
||||||
|
This path is relative to the chat folder and may not exist in all deployments. No validation that the file exists before use.
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. Validate file exists before enabling feature
|
||||||
|
2. Add graceful degradation if file not found
|
||||||
|
3. Log warning when feature is disabled due to missing file
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. Memory Management Could Cause Performance Issues
|
||||||
|
|
||||||
|
**Location:** `chat/server.js` lines 681-999
|
||||||
|
**Severity:** LOW
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
Memory cleanup runs every 5 minutes with debouncing to 60 seconds. For high-traffic deployments, this may not be sufficient.
|
||||||
|
|
||||||
|
**Observations:**
|
||||||
|
- Session max age: 7 days
|
||||||
|
- Message history limit: 50 messages
|
||||||
|
- Stale stream timeout: 10 minutes
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
1. Consider making cleanup intervals configurable
|
||||||
|
2. Add memory pressure-based cleanup triggers
|
||||||
|
3. Implement connection pooling limits
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 11. Token Validation May Reject Valid Usage
|
||||||
|
|
||||||
|
**Location:** `chat/server.js` lines 7459-7499
|
||||||
|
**Severity:** LOW
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
Token validation has strict character-per-token ratio checking that might reject legitimate edge cases (very dense code, minified JS, etc.).
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
1. Widen acceptable character-per-token ratio bounds
|
||||||
|
2. Add context-aware validation for code vs natural language
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Payment System Issues
|
||||||
|
|
||||||
|
### 12. Dodo Product IDs Using Placeholder Values
|
||||||
|
|
||||||
|
**Location:** `chat/server.js` lines 254-276
|
||||||
|
**Severity:** HIGH
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
```javascript
|
||||||
|
starter_monthly_usd: process.env.DODO_STARTER_MONTHLY_USD || 'prod_starter_monthly_usd',
|
||||||
|
```
|
||||||
|
|
||||||
|
Default product IDs are placeholder strings. If environment variables aren't set, payment checkouts will fail silently or create invalid products.
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. Validate all required product IDs on startup
|
||||||
|
2. Exit application if payment product IDs not configured in production
|
||||||
|
3. Add startup health check for payment configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 13. Subscription Renewal Date Calculation
|
||||||
|
|
||||||
|
**Location:** Multiple locations
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
|
||||||
|
The renewal date calculation should be reviewed to ensure proper handling of:
|
||||||
|
- Month-end dates (Feb 28/29)
|
||||||
|
- Timezone consistency
|
||||||
|
- Leap years
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authentication Issues
|
||||||
|
|
||||||
|
### 14. CSRF Token Implementation
|
||||||
|
|
||||||
|
**Location:** `chat/server.js` lines 1574, 1935-1946
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
CSRF tokens are implemented but it's unclear if they're consistently validated on all state-changing requests.
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
1. Audit all POST/PUT/DELETE endpoints for CSRF validation
|
||||||
|
2. Add CSRF token requirement to all forms
|
||||||
|
3. Consider using SameSite=Strict cookies for session cookies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 15. Password Requirements
|
||||||
|
|
||||||
|
**Location:** `chat/server.js` - password validation function
|
||||||
|
**Severity:** LOW
|
||||||
|
|
||||||
|
The password validation appears reasonable but should be reviewed to ensure it meets current security standards:
|
||||||
|
- Minimum 8 characters recommended
|
||||||
|
- No common password blacklist
|
||||||
|
- No breach database check
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
1. Implement password strength meter on frontend
|
||||||
|
2. Add common password blacklist check
|
||||||
|
3. Consider HaveIBeenPwned API integration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Builder Functionality Issues
|
||||||
|
|
||||||
|
### 16. Model Selection Lock for Free Plans
|
||||||
|
|
||||||
|
**Location:** `chat/public/builder.js` lines 1036-1090
|
||||||
|
**Severity:** LOW
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
Free plan users have model selection locked to 'auto'. The logic adds an 'auto' option and prevents selection. This UX could be confusing.
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
1. Show clear messaging about auto model selection
|
||||||
|
2. Consider showing which model was actually used after request
|
||||||
|
3. Add upgrade prompt when user attempts to select specific model
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 17. Message Input Caching
|
||||||
|
|
||||||
|
**Location:** `chat/public/builder.js` lines 447-508
|
||||||
|
**Severity:** LOW
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
Message input is cached in localStorage for 24 hours. If a user types sensitive information and doesn't send, it persists.
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
1. Clear cache on logout
|
||||||
|
2. Add user notification about caching
|
||||||
|
3. Consider shorter cache duration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration Issues
|
||||||
|
|
||||||
|
### 18. Missing Environment Variable Documentation
|
||||||
|
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
Many environment variables are required for proper operation but not documented in a single location.
|
||||||
|
|
||||||
|
**Required Variables:**
|
||||||
|
- `DODO_PAYMENTS_API_KEY` - Payment processing
|
||||||
|
- `DODO_PAYMENTS_WEBHOOK_KEY` - Webhook verification
|
||||||
|
- `DODO_TOPUP_*_USD/GBP/EUR` - Product IDs (12 variables)
|
||||||
|
- `USER_SESSION_SECRET` - Session security
|
||||||
|
- `ADMIN_USER` / `ADMIN_PASSWORD` - Admin access
|
||||||
|
- `OPENROUTER_API_KEY` - AI model access
|
||||||
|
- Various model-specific API keys
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. Create `.env.example` with all required variables
|
||||||
|
2. Add startup validation for required variables
|
||||||
|
3. Document in deployment guide
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 19. Docker Configuration Security
|
||||||
|
|
||||||
|
**Location:** `docker-compose.yml` (root)
|
||||||
|
**Severity:** MEDIUM
|
||||||
|
|
||||||
|
Review Docker configuration for:
|
||||||
|
- Container user (should not run as root)
|
||||||
|
- Resource limits
|
||||||
|
- Secret management
|
||||||
|
- Network isolation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendations Summary
|
||||||
|
|
||||||
|
### Before Launch (MUST DO):
|
||||||
|
1. Revoke exposed GitHub PAT immediately
|
||||||
|
2. Remove plaintext password fallback
|
||||||
|
3. Add idempotency to payment processing
|
||||||
|
4. Validate all payment product IDs on startup
|
||||||
|
5. Fix webhook error response codes
|
||||||
|
6. Make session secret required in production
|
||||||
|
|
||||||
|
### Soon After Launch:
|
||||||
|
1. Audit all CSRF validation
|
||||||
|
2. Add payment method save handling
|
||||||
|
3. External MCP server path validation
|
||||||
|
4. Environment variable documentation
|
||||||
|
|
||||||
|
### Future Improvements:
|
||||||
|
1. Password breach database check
|
||||||
|
2. Memory management optimization
|
||||||
|
3. Token validation edge cases
|
||||||
|
4. Better free plan UX for model selection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Reviewed
|
||||||
|
|
||||||
|
| File | Lines | Focus |
|
||||||
|
|------|-------|-------|
|
||||||
|
| `chat/server.js` | ~19,500 | Main server, auth, payments, API |
|
||||||
|
| `chat/public/builder.js` | ~2,500 | Builder frontend |
|
||||||
|
| `chat/public/app.js` | ~300 | Main app frontend |
|
||||||
|
| `chat/security/index.js` | 20 | Security module |
|
||||||
|
| `chat/security/prompt-sanitizer.js` | 497 | Prompt injection protection |
|
||||||
|
| `chat/package.json` | 27 | Dependencies |
|
||||||
|
| `.git/config` | - | Git configuration |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Recommendations
|
||||||
|
|
||||||
|
Before launch, perform:
|
||||||
|
|
||||||
|
1. **Penetration Testing**
|
||||||
|
- Authentication bypass attempts
|
||||||
|
- Payment manipulation
|
||||||
|
- Session hijacking
|
||||||
|
- CSRF attacks
|
||||||
|
|
||||||
|
2. **Load Testing**
|
||||||
|
- Concurrent user limits
|
||||||
|
- Payment processing under load
|
||||||
|
- Memory leak detection
|
||||||
|
|
||||||
|
3. **Payment Flow Testing**
|
||||||
|
- Complete payment flows in test mode
|
||||||
|
- Webhook delivery failures
|
||||||
|
- Duplicate payment handling
|
||||||
|
- Refund processing
|
||||||
|
|
||||||
|
4. **Security Scanning**
|
||||||
|
- Dependency vulnerability scan (`npm audit`)
|
||||||
|
- Static code analysis
|
||||||
|
- Secret detection in codebase
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependency Vulnerabilities
|
||||||
|
|
||||||
|
### 20. npm Audit Findings (HIGH)
|
||||||
|
|
||||||
|
**Severity:** HIGH
|
||||||
|
**Status:** Multiple high-severity vulnerabilities found
|
||||||
|
|
||||||
|
```
|
||||||
|
archiver - HIGH severity (via archiver-utils, readdir-glob, zip-stream)
|
||||||
|
archiver-utils - HIGH severity (via glob)
|
||||||
|
glob - HIGH severity (via minimatch)
|
||||||
|
minimatch - HIGH severity (ReDoS vulnerability GHSA-3ppc-4f35-3m26)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
```bash
|
||||||
|
npm update archiver
|
||||||
|
# Or pin to secure version:
|
||||||
|
npm install archiver@4.0.2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 21. Test Framework Dependency (LOW)
|
||||||
|
|
||||||
|
**Severity:** LOW
|
||||||
|
**Location:** `chat/package.json` line 7
|
||||||
|
|
||||||
|
**Issue:**
|
||||||
|
```json
|
||||||
|
"test": "bun test"
|
||||||
|
```
|
||||||
|
|
||||||
|
Tests are written for Bun test framework but Bun is not installed in the environment.
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- Cannot run tests in production environment
|
||||||
|
- CI/CD may fail
|
||||||
|
|
||||||
|
**Remediation:**
|
||||||
|
1. Either install Bun in deployment environment
|
||||||
|
2. Or migrate tests to use Node.js built-in test runner
|
||||||
|
3. Add test runner to documentation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
**DO NOT LAUNCH** until critical issues #1-4 are resolved. The exposed GitHub token (#1) is a severe security breach that could lead to complete account compromise. The payment-related issues (#4, #5) could result in revenue loss or customer disputes.
|
||||||
|
|
||||||
|
After remediation of critical and high-priority issues, the application should be suitable for launch with the noted improvements implemented over time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This review was generated by automated analysis. Manual verification of all findings is recommended.*
|
||||||
Reference in New Issue
Block a user