honeyweb-core 2.0.3 → 2.0.5

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 CHANGED
@@ -1,9 +1,7 @@
1
1
  // honeyweb-core/ai/gemini.js
2
- // Google Gemini API client
3
2
 
4
3
  const { GoogleGenerativeAI } = require('@google/generative-ai');
5
4
 
6
- // Retry config
7
5
  const MAX_RETRIES = 3;
8
6
  const INITIAL_BACKOFF_MS = 2000;
9
7
 
@@ -11,7 +9,7 @@ class GeminiClient {
11
9
  constructor(config) {
12
10
  this.enabled = config.enabled && !!config.apiKey;
13
11
  this.timeout = config.timeout || 50000;
14
- this.modelName = config.model || 'gemini-3-flash-preview';
12
+ this.modelName = config.model || 'gemini-2.5-flash';
15
13
 
16
14
  if (this.enabled) {
17
15
  this.genAI = new GoogleGenerativeAI(config.apiKey);
@@ -20,37 +18,20 @@ class GeminiClient {
20
18
  }
21
19
  }
22
20
 
23
- /**
24
- * Sleep helper
25
- */
26
21
  _sleep(ms) {
27
22
  return new Promise(resolve => setTimeout(resolve, ms));
28
23
  }
29
24
 
30
- /**
31
- * Check if an error is retryable
32
- */
33
25
  _isRetryable(err) {
34
26
  const msg = err.message || '';
35
- return msg.includes('503')
36
- || msg.includes('429')
37
- || msg.includes('500')
38
- || msg.includes('Service Unavailable')
39
- || msg.includes('overloaded')
40
- || msg.includes('high demand')
41
- || msg.includes('RESOURCE_EXHAUSTED')
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')
42
30
  || msg.includes('Quota exceeded');
43
31
  }
44
32
 
45
- /**
46
- * Generate AI analysis with retry logic
47
- * @param {string} prompt - Analysis prompt
48
- * @returns {Promise<string|null>} - AI response or null on failure
49
- */
50
33
  async analyze(prompt) {
51
- if (!this.enabled) {
52
- return null;
53
- }
34
+ if (!this.enabled) return null;
54
35
 
55
36
  let lastError = null;
56
37
 
@@ -66,18 +47,16 @@ class GeminiClient {
66
47
  const response = await result.response;
67
48
  const text = response.text();
68
49
 
69
- if (text) {
70
- return text;
71
- }
50
+ if (text) return text;
72
51
 
73
- console.warn('[Gemini] Empty response from AI, retrying...');
52
+ console.warn('[Gemini] Empty response, retrying...');
74
53
  lastError = new Error('Empty AI response');
75
54
  } catch (err) {
76
55
  lastError = err;
77
56
 
78
57
  if (this._isRetryable(err) && attempt < MAX_RETRIES - 1) {
79
58
  const backoff = INITIAL_BACKOFF_MS * Math.pow(2, attempt);
80
- console.warn(`[Gemini] Retryable error (attempt ${attempt + 1}/${MAX_RETRIES}): ${err.message}. Retrying in ${backoff}ms...`);
59
+ console.warn(`[Gemini] Retryable error (${attempt + 1}/${MAX_RETRIES}): ${err.message}. Retry in ${backoff}ms`);
81
60
  await this._sleep(backoff);
82
61
  continue;
83
62
  }
@@ -86,14 +65,10 @@ class GeminiClient {
86
65
  }
87
66
  }
88
67
 
89
- console.error(`[Gemini] AI analysis failed after ${MAX_RETRIES} attempts:`, lastError?.message);
68
+ console.error(`[Gemini] Failed after ${MAX_RETRIES} attempts:`, lastError?.message);
90
69
  return null;
91
70
  }
92
71
 
93
- /**
94
- * Check if AI is enabled
95
- * @returns {boolean}
96
- */
97
72
  isEnabled() {
98
73
  return this.enabled;
99
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
- return null;
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
- return null;
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
  }
@@ -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-3-flash-preview',
5
+ model: 'gemini-2.5-flash',
10
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, // Phase 2 feature - ENABLED
11
+ enabled: true,
22
12
  suspicionThreshold: 50,
23
13
  trackTiming: true,
24
14
  trackNavigation: true
25
15
  },
26
16
  whitelist: {
27
- enabled: true, // Phase 2 feature - ENABLED
28
- verifyDNS: true, // DNS verification ENABLED
29
- cacheTTL: 86400000, // 24 hours
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, // 10 seconds
25
+ window: 10000,
38
26
  maxRequests: 50,
39
- cleanupInterval: 60000 // Auto-cleanup every 60s
27
+ cleanupInterval: 60000
40
28
  },
41
-
42
- // Storage Configuration
43
29
  storage: {
44
- type: 'json', // 'json' or 'memory'
30
+ type: 'json',
45
31
  path: './blocked-ips.json',
46
32
  async: true,
47
33
  cache: true,
48
- banDuration: 300000, // 24 hours
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', // 'debug', 'info', 'warn', 'error'
77
- format: 'pretty' // 'pretty' or 'json'
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
- if (!(key in target)) {
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
- // 2. Override with environment variables
58
- if (process.env.HONEYWEB_API_KEY) {
59
- config.ai.apiKey = process.env.HONEYWEB_API_KEY;
60
- }
61
- if (process.env.HONEYWEB_AI_MODEL) {
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 };
@@ -1,186 +1,94 @@
1
- // honeyweb-core/detection/behavioral.js
2
- // Behavioral analysis to detect bot-like patterns
3
-
4
1
  class BehavioralAnalyzer {
5
2
  constructor(config) {
6
3
  this.enabled = config.enabled !== false;
7
4
  this.suspicionThreshold = config.suspicionThreshold || 50;
8
5
  this.trackTiming = config.trackTiming !== false;
9
6
  this.trackNavigation = config.trackNavigation !== false;
10
-
11
- // Track sessions per IP
12
- this.sessions = new Map(); // ip -> { requests: [], pages: [], firstSeen: timestamp }
13
-
14
- // Auto-cleanup old sessions every 5 minutes
15
- this.cleanupInterval = setInterval(() => {
16
- this._cleanupOldSessions();
17
- }, 300000);
7
+ this.sessions = new Map();
8
+ this.cleanupInterval = setInterval(() => this._cleanupOldSessions(), 300000);
18
9
  }
19
10
 
20
- /**
21
- * Analyze request for bot-like behavior
22
- * @param {Object} req - Express request object
23
- * @param {string} ip - Client IP address
24
- * @returns {Object} - { suspicious: boolean, suspicionScore: number, reasons: string[] }
25
- */
26
11
  analyze(req, ip) {
27
- if (!this.enabled) {
28
- return { suspicious: false, suspicionScore: 0, reasons: [] };
29
- }
12
+ if (!this.enabled) return { suspicious: false, suspicionScore: 0, reasons: [] };
30
13
 
31
14
  const now = Date.now();
32
15
  const reasons = [];
33
16
  let suspicionScore = 0;
34
17
 
35
- // Get or create session
36
18
  let session = this.sessions.get(ip);
37
19
  if (!session) {
38
- session = {
39
- requests: [],
40
- pages: [],
41
- firstSeen: now
42
- };
20
+ session = { requests: [], pages: [], firstSeen: now };
43
21
  this.sessions.set(ip, session);
44
22
  }
45
23
 
46
- // 1. TIMING ANALYSIS
24
+ // Timing analysis
47
25
  if (this.trackTiming && session.requests.length > 0) {
48
- const lastRequest = session.requests[session.requests.length - 1];
49
- const timeSinceLastRequest = now - lastRequest;
26
+ const timeSinceLast = now - session.requests[session.requests.length - 1];
50
27
 
51
- // Too fast (< 100ms between requests)
52
- if (timeSinceLastRequest < 100) {
28
+ if (timeSinceLast < 100) {
53
29
  reasons.push('Requests too fast (< 100ms interval)');
54
30
  suspicionScore += 30;
55
31
  }
56
32
 
57
- // Check for consistent timing (bot pattern)
58
33
  if (session.requests.length >= 5) {
59
34
  const intervals = [];
60
35
  for (let i = 1; i < session.requests.length; i++) {
61
36
  intervals.push(session.requests[i] - session.requests[i - 1]);
62
37
  }
38
+ const avg = intervals.reduce((a, b) => a + b, 0) / intervals.length;
39
+ const variance = intervals.reduce((s, v) => s + Math.pow(v - avg, 2), 0) / intervals.length;
63
40
 
64
- // Calculate variance
65
- const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
66
- const variance = intervals.reduce((sum, interval) => {
67
- return sum + Math.pow(interval - avgInterval, 2);
68
- }, 0) / intervals.length;
69
-
70
- // Low variance = consistent timing = bot
71
- if (variance < 1000 && avgInterval < 2000) {
41
+ if (variance < 1000 && avg < 2000) {
72
42
  reasons.push('Consistent request timing (bot pattern)');
73
43
  suspicionScore += 25;
74
44
  }
75
45
  }
76
46
  }
77
47
 
78
- // 2. NAVIGATION ANALYSIS
48
+ // Navigation analysis
79
49
  if (this.trackNavigation) {
80
- const path = req.path;
81
-
82
- // Detect direct deep link access (skipping homepage)
83
- if (session.pages.length === 0 && path !== '/' && !path.startsWith('/public')) {
50
+ if (session.pages.length === 0 && req.path !== '/' && !req.path.startsWith('/public')) {
84
51
  reasons.push('Direct deep link access (skipped homepage)');
85
52
  suspicionScore += 15;
86
53
  }
87
54
 
88
- // Detect breadth-first crawling (accessing many different paths quickly)
89
- const uniquePaths = new Set(session.pages);
90
- if (uniquePaths.size > 10 && (now - session.firstSeen) < 10000) {
55
+ if (new Set(session.pages).size > 10 && (now - session.firstSeen) < 10000) {
91
56
  reasons.push('Breadth-first crawling detected');
92
57
  suspicionScore += 20;
93
58
  }
94
59
 
95
- session.pages.push(path);
60
+ session.pages.push(req.path);
96
61
  }
97
62
 
98
- // 3. SESSION ANALYSIS
99
- const sessionDuration = now - session.firstSeen;
100
- const requestCount = session.requests.length;
101
-
102
- // Short session with many requests (scraping)
103
- if (sessionDuration < 5000 && requestCount > 10) {
63
+ // Session analysis
64
+ const duration = now - session.firstSeen;
65
+ if (duration < 5000 && session.requests.length > 10) {
104
66
  reasons.push('High request rate in short session');
105
67
  suspicionScore += 20;
106
68
  }
107
-
108
- // Very long session with consistent activity (persistent bot)
109
- if (sessionDuration > 300000 && requestCount > 100) {
69
+ if (duration > 300000 && session.requests.length > 100) {
110
70
  reasons.push('Persistent automated activity');
111
71
  suspicionScore += 15;
112
72
  }
113
73
 
114
- // Record this request
115
74
  session.requests.push(now);
75
+ if (session.requests.length > 20) session.requests = session.requests.slice(-20);
76
+ if (session.pages.length > 50) session.pages = session.pages.slice(-50);
116
77
 
117
- // Keep only last 20 requests to prevent memory bloat
118
- if (session.requests.length > 20) {
119
- session.requests = session.requests.slice(-20);
120
- }
121
- if (session.pages.length > 50) {
122
- session.pages = session.pages.slice(-50);
123
- }
124
-
125
- // Cap score at 100
126
- suspicionScore = Math.min(100, suspicionScore);
127
-
128
- return {
129
- suspicious: suspicionScore >= this.suspicionThreshold,
130
- suspicionScore,
131
- reasons
132
- };
78
+ return { suspicious: Math.min(100, suspicionScore) >= this.suspicionThreshold, suspicionScore: Math.min(100, suspicionScore), reasons };
133
79
  }
134
80
 
135
- /**
136
- * Clean up old sessions (> 1 hour)
137
- * @private
138
- */
139
81
  _cleanupOldSessions() {
140
82
  const now = Date.now();
141
- const maxAge = 3600000; // 1 hour
142
-
143
83
  for (const [ip, session] of this.sessions.entries()) {
144
- if (now - session.firstSeen > maxAge) {
145
- this.sessions.delete(ip);
146
- }
84
+ if (now - session.firstSeen > 3600000) this.sessions.delete(ip);
147
85
  }
148
86
  }
149
87
 
150
- /**
151
- * Get statistics
152
- * @returns {Object}
153
- */
154
- getStats() {
155
- return {
156
- activeSessions: this.sessions.size,
157
- enabled: this.enabled
158
- };
159
- }
160
-
161
- /**
162
- * Reset session for an IP
163
- * @param {string} ip
164
- */
165
- resetSession(ip) {
166
- this.sessions.delete(ip);
167
- }
168
-
169
- /**
170
- * Clear all sessions
171
- */
172
- clear() {
173
- this.sessions.clear();
174
- }
175
-
176
- /**
177
- * Cleanup and stop timers
178
- */
179
- destroy() {
180
- if (this.cleanupInterval) {
181
- clearInterval(this.cleanupInterval);
182
- }
183
- }
88
+ getStats() { return { activeSessions: this.sessions.size, enabled: this.enabled }; }
89
+ resetSession(ip) { this.sessions.delete(ip); }
90
+ clear() { this.sessions.clear(); }
91
+ destroy() { if (this.cleanupInterval) clearInterval(this.cleanupInterval); }
184
92
  }
185
93
 
186
94
  module.exports = BehavioralAnalyzer;