honeyweb-core 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/gemini.js +54 -29
- package/ai/index.js +4 -27
- package/ai/prompt-templates.js +22 -42
- package/config/defaults.js +14 -37
- package/config/index.js +7 -35
- package/config/schema.js +1 -17
- package/detection/behavioral.js +26 -118
- package/detection/bot-detector.js +11 -62
- package/detection/index.js +19 -35
- package/detection/patterns.js +14 -51
- package/detection/rate-limiter.js +15 -86
- package/detection/traversal.js +42 -0
- package/index.js +1 -13
- package/middleware/blocklist-checker.js +1 -15
- package/middleware/dashboard.js +218 -214
- package/middleware/index.js +17 -39
- package/middleware/request-analyzer.js +29 -70
- package/middleware/trap-injector.js +3 -28
- package/package.json +1 -1
- package/storage/bot-tracker.js +9 -56
- package/storage/index.js +3 -12
- package/storage/json-store.js +15 -89
- package/storage/memory-store.js +9 -61
- package/utils/cache.js +5 -48
- package/utils/dns-verify.js +8 -45
- package/utils/event-emitter.js +7 -39
- package/utils/logger.js +9 -35
package/ai/gemini.js
CHANGED
|
@@ -1,49 +1,74 @@
|
|
|
1
1
|
// honeyweb-core/ai/gemini.js
|
|
2
|
-
// Google Gemini API client (extracted from main index.js)
|
|
3
2
|
|
|
4
3
|
const { GoogleGenerativeAI } = require('@google/generative-ai');
|
|
5
4
|
|
|
5
|
+
const MAX_RETRIES = 3;
|
|
6
|
+
const INITIAL_BACKOFF_MS = 2000;
|
|
7
|
+
|
|
6
8
|
class GeminiClient {
|
|
7
9
|
constructor(config) {
|
|
8
|
-
this.enabled = config.enabled && config.apiKey;
|
|
9
|
-
this.timeout = config.timeout ||
|
|
10
|
+
this.enabled = config.enabled && !!config.apiKey;
|
|
11
|
+
this.timeout = config.timeout || 50000;
|
|
12
|
+
this.modelName = config.model || 'gemini-2.5-flash';
|
|
10
13
|
|
|
11
14
|
if (this.enabled) {
|
|
12
15
|
this.genAI = new GoogleGenerativeAI(config.apiKey);
|
|
13
|
-
this.model = this.genAI.getGenerativeModel({ model:
|
|
16
|
+
this.model = this.genAI.getGenerativeModel({ model: this.modelName });
|
|
17
|
+
console.log(`[Gemini] Initialized with model: ${this.modelName}`);
|
|
14
18
|
}
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
_sleep(ms) {
|
|
22
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_isRetryable(err) {
|
|
26
|
+
const msg = err.message || '';
|
|
27
|
+
return msg.includes('503') || msg.includes('429') || msg.includes('500')
|
|
28
|
+
|| msg.includes('Service Unavailable') || msg.includes('overloaded')
|
|
29
|
+
|| msg.includes('high demand') || msg.includes('RESOURCE_EXHAUSTED')
|
|
30
|
+
|| msg.includes('Quota exceeded');
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
async analyze(prompt) {
|
|
23
|
-
if (!this.enabled)
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
if (!this.enabled) return null;
|
|
35
|
+
|
|
36
|
+
let lastError = null;
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
39
|
+
try {
|
|
40
|
+
const result = await Promise.race([
|
|
41
|
+
this.model.generateContent(prompt),
|
|
42
|
+
new Promise((_, reject) =>
|
|
43
|
+
setTimeout(() => reject(new Error('AI request timeout')), this.timeout)
|
|
44
|
+
)
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
const response = await result.response;
|
|
48
|
+
const text = response.text();
|
|
49
|
+
|
|
50
|
+
if (text) return text;
|
|
51
|
+
|
|
52
|
+
console.warn('[Gemini] Empty response, retrying...');
|
|
53
|
+
lastError = new Error('Empty AI response');
|
|
54
|
+
} catch (err) {
|
|
55
|
+
lastError = err;
|
|
56
|
+
|
|
57
|
+
if (this._isRetryable(err) && attempt < MAX_RETRIES - 1) {
|
|
58
|
+
const backoff = INITIAL_BACKOFF_MS * Math.pow(2, attempt);
|
|
59
|
+
console.warn(`[Gemini] Retryable error (${attempt + 1}/${MAX_RETRIES}): ${err.message}. Retry in ${backoff}ms`);
|
|
60
|
+
await this._sleep(backoff);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
40
66
|
}
|
|
67
|
+
|
|
68
|
+
console.error(`[Gemini] Failed after ${MAX_RETRIES} attempts:`, lastError?.message);
|
|
69
|
+
return null;
|
|
41
70
|
}
|
|
42
71
|
|
|
43
|
-
/**
|
|
44
|
-
* Check if AI is enabled
|
|
45
|
-
* @returns {boolean}
|
|
46
|
-
*/
|
|
47
72
|
isEnabled() {
|
|
48
73
|
return this.enabled;
|
|
49
74
|
}
|
package/ai/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// honeyweb-core/ai/index.js
|
|
2
|
-
// AI orchestrator
|
|
3
2
|
|
|
4
3
|
const GeminiClient = require('./gemini');
|
|
5
4
|
const { generateThreatAnalysisPrompt, generateTrapAnalysisPrompt } = require('./prompt-templates');
|
|
@@ -9,38 +8,16 @@ class AIAnalyzer {
|
|
|
9
8
|
this.client = new GeminiClient(config);
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
/**
|
|
13
|
-
* Analyze threat with AI
|
|
14
|
-
* @param {Object} data - Threat data
|
|
15
|
-
* @returns {Promise<string|null>} - AI analysis report
|
|
16
|
-
*/
|
|
17
11
|
async analyzeThreat(data) {
|
|
18
|
-
if (!this.client.isEnabled())
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const prompt = generateThreatAnalysisPrompt(data);
|
|
23
|
-
return await this.client.analyze(prompt);
|
|
12
|
+
if (!this.client.isEnabled()) return null;
|
|
13
|
+
return await this.client.analyze(generateThreatAnalysisPrompt(data));
|
|
24
14
|
}
|
|
25
15
|
|
|
26
|
-
/**
|
|
27
|
-
* Analyze trap trigger with AI
|
|
28
|
-
* @param {Object} data - Trap trigger data
|
|
29
|
-
* @returns {Promise<string|null>} - AI analysis report
|
|
30
|
-
*/
|
|
31
16
|
async analyzeTrap(data) {
|
|
32
|
-
if (!this.client.isEnabled())
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const prompt = generateTrapAnalysisPrompt(data);
|
|
37
|
-
return await this.client.analyze(prompt);
|
|
17
|
+
if (!this.client.isEnabled()) return null;
|
|
18
|
+
return await this.client.analyze(generateTrapAnalysisPrompt(data));
|
|
38
19
|
}
|
|
39
20
|
|
|
40
|
-
/**
|
|
41
|
-
* Check if AI is enabled
|
|
42
|
-
* @returns {boolean}
|
|
43
|
-
*/
|
|
44
21
|
isEnabled() {
|
|
45
22
|
return this.client.isEnabled();
|
|
46
23
|
}
|
package/ai/prompt-templates.js
CHANGED
|
@@ -1,61 +1,41 @@
|
|
|
1
1
|
// honeyweb-core/ai/prompt-templates.js
|
|
2
2
|
// AI prompt templates for threat analysis
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Generate threat analysis prompt
|
|
6
|
-
* @param {Object} data - Threat data
|
|
7
|
-
* @returns {string} - Formatted prompt
|
|
8
|
-
*/
|
|
9
4
|
function generateThreatAnalysisPrompt(data) {
|
|
10
|
-
const { ip, path, threats, userAgent,
|
|
5
|
+
const { ip, path, threats, userAgent, method, timestamp } = data;
|
|
11
6
|
|
|
12
|
-
return `
|
|
7
|
+
return `Cybersecurity analyst for HoneyWeb honeypot. Analyze this attack. Plain text only, no markdown or asterisks. Use UPPERCASE headers.
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- User-Agent: ${userAgent || 'Not provided'}
|
|
19
|
-
- Referer: ${referer || 'Not provided'}
|
|
20
|
-
- Timestamp: ${new Date(timestamp).toISOString()}
|
|
9
|
+
REQUEST: ${method} ${path} from ${ip}
|
|
10
|
+
User-Agent: ${userAgent || 'N/A'}
|
|
11
|
+
Time: ${new Date(timestamp).toISOString()}
|
|
12
|
+
Threats: ${threats.join('; ')}
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
Respond with:
|
|
15
|
+
CLASSIFICATION: Attack type and technique.
|
|
16
|
+
RISK: Severity (CRITICAL/HIGH/MEDIUM/LOW), impact if successful.
|
|
17
|
+
PROFILE: Skill level, likely tool, automated or manual.
|
|
18
|
+
ACTIONS: Immediate and long-term defenses.
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
1. What type of attack is this likely to be?
|
|
27
|
-
2. What is the attacker's probable intent?
|
|
28
|
-
3. What vulnerabilities are they trying to exploit?
|
|
29
|
-
4. Recommended defensive actions?
|
|
30
|
-
|
|
31
|
-
Provide a brief, actionable report (3-4 sentences).`;
|
|
20
|
+
Keep to 4-6 sentences total.`;
|
|
32
21
|
}
|
|
33
22
|
|
|
34
|
-
/**
|
|
35
|
-
* Generate trap trigger analysis prompt
|
|
36
|
-
* @param {Object} data - Trap trigger data
|
|
37
|
-
* @returns {string} - Formatted prompt
|
|
38
|
-
*/
|
|
39
23
|
function generateTrapAnalysisPrompt(data) {
|
|
40
24
|
const { ip, path, userAgent, timestamp } = data;
|
|
41
25
|
|
|
42
|
-
return `
|
|
43
|
-
|
|
44
|
-
**Bot Details:**
|
|
45
|
-
- IP Address: ${ip}
|
|
46
|
-
- Trap Path: ${path}
|
|
47
|
-
- User-Agent: ${userAgent || 'Not provided'}
|
|
48
|
-
- Timestamp: ${new Date(timestamp).toISOString()}
|
|
26
|
+
return `Cybersecurity analyst for HoneyWeb honeypot. A hidden trap link (invisible to humans) was accessed. Plain text only, no markdown or asterisks. Use UPPERCASE headers.
|
|
49
27
|
|
|
50
|
-
|
|
51
|
-
|
|
28
|
+
TRAP HIT: ${path} from ${ip}
|
|
29
|
+
User-Agent: ${userAgent || 'N/A'}
|
|
30
|
+
Time: ${new Date(timestamp).toISOString()}
|
|
52
31
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
32
|
+
Respond with:
|
|
33
|
+
BOT TYPE: What kind of bot and why.
|
|
34
|
+
SOPHISTICATION: BASIC/INTERMEDIATE/ADVANCED with reason.
|
|
35
|
+
THREAT: Severity (CRITICAL/HIGH/MEDIUM/LOW), malicious or benign.
|
|
36
|
+
ACTIONS: Recommended next steps.
|
|
57
37
|
|
|
58
|
-
|
|
38
|
+
Keep to 3-5 sentences total.`;
|
|
59
39
|
}
|
|
60
40
|
|
|
61
41
|
module.exports = {
|
package/config/defaults.js
CHANGED
|
@@ -1,55 +1,39 @@
|
|
|
1
|
-
// honeyweb-core/config/defaults.js
|
|
2
|
-
// Default configuration values extracted from hardcoded constants
|
|
3
|
-
|
|
4
1
|
module.exports = {
|
|
5
|
-
// AI Configuration
|
|
6
2
|
ai: {
|
|
7
3
|
enabled: true,
|
|
8
4
|
apiKey: process.env.HONEYWEB_API_KEY || '',
|
|
9
|
-
model: 'gemini-
|
|
10
|
-
timeout:
|
|
5
|
+
model: 'gemini-2.5-flash',
|
|
6
|
+
timeout: 30000
|
|
11
7
|
},
|
|
12
|
-
|
|
13
|
-
// Detection Configuration
|
|
14
8
|
detection: {
|
|
15
|
-
patterns: {
|
|
16
|
-
enabled: true,
|
|
17
|
-
sqli: true,
|
|
18
|
-
xss: true
|
|
19
|
-
},
|
|
9
|
+
patterns: { enabled: true, sqli: true, xss: true },
|
|
20
10
|
behavioral: {
|
|
21
|
-
enabled: true,
|
|
11
|
+
enabled: true,
|
|
22
12
|
suspicionThreshold: 50,
|
|
23
13
|
trackTiming: true,
|
|
24
14
|
trackNavigation: true
|
|
25
15
|
},
|
|
26
16
|
whitelist: {
|
|
27
|
-
enabled: true,
|
|
28
|
-
verifyDNS: true,
|
|
29
|
-
cacheTTL: 86400000,
|
|
17
|
+
enabled: true,
|
|
18
|
+
verifyDNS: true,
|
|
19
|
+
cacheTTL: 86400000,
|
|
30
20
|
bots: ['Googlebot', 'Bingbot', 'Slackbot', 'facebookexternalhit']
|
|
31
21
|
}
|
|
32
22
|
},
|
|
33
|
-
|
|
34
|
-
// Rate Limiting
|
|
35
23
|
rateLimit: {
|
|
36
24
|
enabled: true,
|
|
37
|
-
window: 10000,
|
|
25
|
+
window: 10000,
|
|
38
26
|
maxRequests: 50,
|
|
39
|
-
cleanupInterval: 60000
|
|
27
|
+
cleanupInterval: 60000
|
|
40
28
|
},
|
|
41
|
-
|
|
42
|
-
// Storage Configuration
|
|
43
29
|
storage: {
|
|
44
|
-
type: 'json',
|
|
30
|
+
type: 'json',
|
|
45
31
|
path: './blocked-ips.json',
|
|
46
32
|
async: true,
|
|
47
33
|
cache: true,
|
|
48
|
-
banDuration:
|
|
34
|
+
banDuration: 50000,
|
|
49
35
|
autoCleanup: true
|
|
50
36
|
},
|
|
51
|
-
|
|
52
|
-
// Trap Configuration
|
|
53
37
|
traps: {
|
|
54
38
|
paths: [
|
|
55
39
|
'/admin-backup-v2',
|
|
@@ -58,22 +42,15 @@ module.exports = {
|
|
|
58
42
|
'/auth/root-access',
|
|
59
43
|
'/sys/config-safe'
|
|
60
44
|
],
|
|
61
|
-
injection: {
|
|
62
|
-
enabled: true,
|
|
63
|
-
invisibleLinks: true
|
|
64
|
-
}
|
|
45
|
+
injection: { enabled: true, invisibleLinks: true }
|
|
65
46
|
},
|
|
66
|
-
|
|
67
|
-
// Dashboard Configuration
|
|
68
47
|
dashboard: {
|
|
69
48
|
enabled: true,
|
|
70
49
|
path: '/honeyweb-status',
|
|
71
50
|
secret: process.env.HONEYWEB_DASHBOARD_SECRET || 'admin123'
|
|
72
51
|
},
|
|
73
|
-
|
|
74
|
-
// Logging Configuration
|
|
75
52
|
logging: {
|
|
76
|
-
level: 'info',
|
|
77
|
-
format: 'pretty'
|
|
53
|
+
level: 'info',
|
|
54
|
+
format: 'pretty'
|
|
78
55
|
}
|
|
79
56
|
};
|
package/config/index.js
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
// honeyweb-core/config/index.js
|
|
2
|
-
// Configuration loader with validation
|
|
3
2
|
|
|
4
3
|
const defaults = require('./defaults');
|
|
5
4
|
const { validateConfig } = require('./schema');
|
|
6
5
|
const path = require('path');
|
|
7
6
|
const fs = require('fs');
|
|
8
7
|
|
|
9
|
-
/**
|
|
10
|
-
* Deep merge two objects
|
|
11
|
-
*/
|
|
12
8
|
function deepMerge(target, source) {
|
|
13
9
|
const output = { ...target };
|
|
14
10
|
|
|
15
11
|
if (isObject(target) && isObject(source)) {
|
|
16
12
|
Object.keys(source).forEach(key => {
|
|
17
13
|
if (isObject(source[key])) {
|
|
18
|
-
|
|
19
|
-
output[key] = source[key];
|
|
20
|
-
} else {
|
|
21
|
-
output[key] = deepMerge(target[key], source[key]);
|
|
22
|
-
}
|
|
14
|
+
output[key] = key in target ? deepMerge(target[key], source[key]) : source[key];
|
|
23
15
|
} else {
|
|
24
16
|
output[key] = source[key];
|
|
25
17
|
}
|
|
@@ -33,17 +25,10 @@ function isObject(item) {
|
|
|
33
25
|
return item && typeof item === 'object' && !Array.isArray(item);
|
|
34
26
|
}
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
* Load configuration from multiple sources
|
|
38
|
-
* Priority: userConfig > configFile > envVars > defaults
|
|
39
|
-
*
|
|
40
|
-
* @param {Object} userConfig - Configuration passed programmatically
|
|
41
|
-
* @returns {Object} - Merged and validated configuration
|
|
42
|
-
*/
|
|
28
|
+
// Priority: userConfig > configFile > envVars > defaults
|
|
43
29
|
function loadConfig(userConfig = {}) {
|
|
44
30
|
let config = { ...defaults };
|
|
45
31
|
|
|
46
|
-
// 1. Try to load from config file (honeyweb.config.js)
|
|
47
32
|
const configPath = path.join(process.cwd(), 'honeyweb.config.js');
|
|
48
33
|
if (fs.existsSync(configPath)) {
|
|
49
34
|
try {
|
|
@@ -54,27 +39,14 @@ function loadConfig(userConfig = {}) {
|
|
|
54
39
|
}
|
|
55
40
|
}
|
|
56
41
|
|
|
57
|
-
|
|
58
|
-
if (process.env.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (process.env.
|
|
62
|
-
config.ai.model = process.env.HONEYWEB_AI_MODEL;
|
|
63
|
-
}
|
|
64
|
-
if (process.env.HONEYWEB_DASHBOARD_SECRET) {
|
|
65
|
-
config.dashboard.secret = process.env.HONEYWEB_DASHBOARD_SECRET;
|
|
66
|
-
}
|
|
67
|
-
if (process.env.HONEYWEB_LOG_LEVEL) {
|
|
68
|
-
config.logging.level = process.env.HONEYWEB_LOG_LEVEL;
|
|
69
|
-
}
|
|
70
|
-
if (process.env.HONEYWEB_STORAGE_PATH) {
|
|
71
|
-
config.storage.path = process.env.HONEYWEB_STORAGE_PATH;
|
|
72
|
-
}
|
|
42
|
+
if (process.env.HONEYWEB_API_KEY) config.ai.apiKey = process.env.HONEYWEB_API_KEY;
|
|
43
|
+
if (process.env.HONEYWEB_AI_MODEL) config.ai.model = process.env.HONEYWEB_AI_MODEL;
|
|
44
|
+
if (process.env.HONEYWEB_DASHBOARD_SECRET) config.dashboard.secret = process.env.HONEYWEB_DASHBOARD_SECRET;
|
|
45
|
+
if (process.env.HONEYWEB_LOG_LEVEL) config.logging.level = process.env.HONEYWEB_LOG_LEVEL;
|
|
46
|
+
if (process.env.HONEYWEB_STORAGE_PATH) config.storage.path = process.env.HONEYWEB_STORAGE_PATH;
|
|
73
47
|
|
|
74
|
-
// 3. Override with user-provided config (highest priority)
|
|
75
48
|
config = deepMerge(config, userConfig);
|
|
76
49
|
|
|
77
|
-
// 4. Validate configuration
|
|
78
50
|
const validation = validateConfig(config);
|
|
79
51
|
if (!validation.valid) {
|
|
80
52
|
throw new Error(`[HoneyWeb] Invalid configuration:\n${validation.errors.join('\n')}`);
|
package/config/schema.js
CHANGED
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
// honeyweb-core/config/schema.js
|
|
2
|
-
// Configuration validation schema
|
|
3
2
|
|
|
4
|
-
/**
|
|
5
|
-
* Validates configuration object
|
|
6
|
-
* @param {Object} config - Configuration to validate
|
|
7
|
-
* @returns {Object} - { valid: boolean, errors: string[] }
|
|
8
|
-
*/
|
|
9
3
|
function validateConfig(config) {
|
|
10
4
|
const errors = [];
|
|
11
5
|
|
|
12
|
-
// Validate AI config
|
|
13
6
|
if (config.ai) {
|
|
14
|
-
// Note: API key is optional - AI will be disabled at runtime if missing
|
|
15
7
|
if (config.ai.timeout && (typeof config.ai.timeout !== 'number' || config.ai.timeout < 0)) {
|
|
16
8
|
errors.push('AI timeout must be a positive number');
|
|
17
9
|
}
|
|
18
10
|
}
|
|
19
11
|
|
|
20
|
-
// Validate rate limit config
|
|
21
12
|
if (config.rateLimit) {
|
|
22
13
|
if (config.rateLimit.window && (typeof config.rateLimit.window !== 'number' || config.rateLimit.window <= 0)) {
|
|
23
14
|
errors.push('Rate limit window must be a positive number');
|
|
@@ -27,7 +18,6 @@ function validateConfig(config) {
|
|
|
27
18
|
}
|
|
28
19
|
}
|
|
29
20
|
|
|
30
|
-
// Validate storage config
|
|
31
21
|
if (config.storage) {
|
|
32
22
|
if (config.storage.type && !['json', 'memory'].includes(config.storage.type)) {
|
|
33
23
|
errors.push('Storage type must be "json" or "memory"');
|
|
@@ -37,7 +27,6 @@ function validateConfig(config) {
|
|
|
37
27
|
}
|
|
38
28
|
}
|
|
39
29
|
|
|
40
|
-
// Validate traps config
|
|
41
30
|
if (config.traps && config.traps.paths) {
|
|
42
31
|
if (!Array.isArray(config.traps.paths)) {
|
|
43
32
|
errors.push('Trap paths must be an array');
|
|
@@ -46,14 +35,12 @@ function validateConfig(config) {
|
|
|
46
35
|
}
|
|
47
36
|
}
|
|
48
37
|
|
|
49
|
-
// Validate dashboard config
|
|
50
38
|
if (config.dashboard) {
|
|
51
39
|
if (config.dashboard.enabled && !config.dashboard.path) {
|
|
52
40
|
errors.push('Dashboard path is required when dashboard is enabled');
|
|
53
41
|
}
|
|
54
42
|
}
|
|
55
43
|
|
|
56
|
-
// Validate logging config
|
|
57
44
|
if (config.logging) {
|
|
58
45
|
if (config.logging.level && !['debug', 'info', 'warn', 'error'].includes(config.logging.level)) {
|
|
59
46
|
errors.push('Logging level must be one of: debug, info, warn, error');
|
|
@@ -63,10 +50,7 @@ function validateConfig(config) {
|
|
|
63
50
|
}
|
|
64
51
|
}
|
|
65
52
|
|
|
66
|
-
return {
|
|
67
|
-
valid: errors.length === 0,
|
|
68
|
-
errors
|
|
69
|
-
};
|
|
53
|
+
return { valid: errors.length === 0, errors };
|
|
70
54
|
}
|
|
71
55
|
|
|
72
56
|
module.exports = { validateConfig };
|