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.
@@ -1,29 +1,12 @@
1
- // honeyweb-core/middleware/request-analyzer.js
2
- // Request analysis middleware
3
-
4
- /**
5
- * Create request analyzer middleware
6
- * @param {Object} config - Configuration
7
- * @param {Object} storage - Storage instance
8
- * @param {Object} botTracker - Bot tracker instance
9
- * @param {Object} detector - Detection engine
10
- * @param {Object} aiAnalyzer - AI analyzer
11
- * @param {Object} logger - Logger instance
12
- * @param {Object} events - Event emitter
13
- * @returns {Function} - Middleware function
14
- */
15
1
  function createRequestAnalyzer(config, storage, botTracker, detector, aiAnalyzer, logger, events) {
16
2
  const trapPaths = config.traps.paths;
17
3
 
18
4
  return async function analyzeRequest(req, res, next) {
19
5
  const clientIP = req.ip || req.connection.remoteAddress;
20
-
21
- // 1. THREAT DETECTION (whitelist check happens FIRST to protect legit bots)
22
6
  const analysis = await detector.analyze(req, clientIP);
23
7
 
24
- // Skip ALL checks if legitimate bot (even trap paths!)
8
+ // Legitimate bots skip all checks
25
9
  if (analysis.legitimateBot) {
26
- // Record legitimate bot visit for dashboard
27
10
  botTracker.recordVisit({
28
11
  botName: analysis.whitelist.botName,
29
12
  ip: clientIP,
@@ -32,22 +15,18 @@ function createRequestAnalyzer(config, storage, botTracker, detector, aiAnalyzer
32
15
  verified: analysis.whitelist.verified,
33
16
  timestamp: Date.now()
34
17
  });
35
-
36
18
  logger.info('Legitimate bot detected', {
37
- ip: clientIP,
38
- botName: analysis.whitelist.botName,
39
- verified: analysis.whitelist.verified,
40
- path: req.path
19
+ ip: clientIP, botName: analysis.whitelist.botName,
20
+ verified: analysis.whitelist.verified, path: req.path
41
21
  });
42
22
  return next();
43
23
  }
44
24
 
45
- // 2. CHECK IF TRAP ACCESSED (only after confirming not a legit bot)
25
+ // Trap access immediate ban
46
26
  if (trapPaths.includes(req.path)) {
47
27
  logger.trapTriggered(clientIP, req.path);
48
28
  events.emitTrapTriggered(clientIP, req.path);
49
29
 
50
- // Ban the IP immediately
51
30
  try {
52
31
  await storage.ban(clientIP, `Trap triggered: ${req.path}`);
53
32
  events.emitIpBanned(clientIP, `Trap triggered: ${req.path}`);
@@ -55,67 +34,47 @@ function createRequestAnalyzer(config, storage, botTracker, detector, aiAnalyzer
55
34
  logger.error('Error banning IP', { error: err.message });
56
35
  }
57
36
 
58
- // AI analysis for trap trigger
59
37
  if (aiAnalyzer.isEnabled()) {
60
38
  aiAnalyzer.analyzeTrap({
61
- ip: clientIP,
62
- path: req.path,
63
- userAgent: req.headers['user-agent'],
64
- timestamp: Date.now()
39
+ ip: clientIP, path: req.path,
40
+ userAgent: req.headers['user-agent'], timestamp: Date.now()
65
41
  }).then(report => {
66
42
  if (report) {
67
43
  logger.info('AI Trap Analysis', { ip: clientIP, report });
68
44
  events.emitAiAnalysis(clientIP, report);
69
45
  }
70
- }).catch(err => {
71
- logger.error('AI analysis failed', { error: err.message });
72
- });
46
+ }).catch(err => logger.error('AI trap analysis failed', { error: err.message }));
73
47
  }
74
48
 
75
- return res.status(403).send(
76
- '<h1>403 Forbidden</h1><p>You have accessed a restricted area.</p>'
77
- );
49
+ return res.status(403).send('<h1>403 Forbidden</h1><p>You have accessed a restricted area.</p>');
78
50
  }
79
51
 
80
- // 3. OTHER THREAT DETECTION (patterns, rate limiting, behavioral)
81
-
82
- if (analysis.detected) {
52
+ // Pattern/rate/behavioral/bot threats
53
+ if (analysis.detected && analysis.threatLevel >= 50) {
83
54
  logger.threatDetected(clientIP, analysis.threats, analysis.threatLevel);
84
55
  events.emitThreatDetected(clientIP, analysis.threats, analysis.threatLevel);
85
56
 
86
- // Ban if threat level is high
87
- if (analysis.threatLevel >= 50) {
88
- try {
89
- await storage.ban(clientIP, `Threat detected: ${analysis.threats[0]}`);
90
- events.emitIpBanned(clientIP, `Threat detected: ${analysis.threats[0]}`);
91
- } catch (err) {
92
- logger.error('Error banning IP', { error: err.message });
93
- }
94
-
95
- // AI analysis for threat
96
- if (aiAnalyzer.isEnabled()) {
97
- aiAnalyzer.analyzeThreat({
98
- ip: clientIP,
99
- path: req.path,
100
- method: req.method,
101
- threats: analysis.threats,
102
- userAgent: req.headers['user-agent'],
103
- referer: req.headers['referer'],
104
- timestamp: Date.now()
105
- }).then(report => {
106
- if (report) {
107
- logger.info('AI Threat Analysis', { ip: clientIP, report });
108
- events.emitAiAnalysis(clientIP, report);
109
- }
110
- }).catch(err => {
111
- logger.error('AI analysis failed', { error: err.message });
112
- });
113
- }
57
+ try {
58
+ await storage.ban(clientIP, `Threat detected: ${analysis.threats[0]}`);
59
+ events.emitIpBanned(clientIP, `Threat detected: ${analysis.threats[0]}`);
60
+ } catch (err) {
61
+ logger.error('Error banning IP', { error: err.message });
62
+ }
114
63
 
115
- return res.status(403).send(
116
- '<h1>403 Forbidden</h1><p>Malicious activity detected.</p>'
117
- );
64
+ if (aiAnalyzer.isEnabled()) {
65
+ aiAnalyzer.analyzeThreat({
66
+ ip: clientIP, path: req.path, method: req.method,
67
+ threats: analysis.threats, userAgent: req.headers['user-agent'],
68
+ referer: req.headers['referer'], timestamp: Date.now()
69
+ }).then(report => {
70
+ if (report) {
71
+ logger.info('AI Threat Analysis', { ip: clientIP, report });
72
+ events.emitAiAnalysis(clientIP, report);
73
+ }
74
+ }).catch(err => logger.error('AI analysis failed', { error: err.message }));
118
75
  }
76
+
77
+ return res.status(403).send('<h1>403 Forbidden</h1><p>Malicious activity detected.</p>');
119
78
  }
120
79
 
121
80
  next();
@@ -1,50 +1,25 @@
1
- // honeyweb-core/middleware/trap-injector.js
2
- // Trap injection middleware (extracted from main index.js)
3
-
4
1
  const cheerio = require('cheerio');
5
2
 
6
- /**
7
- * Create trap injector middleware
8
- * @param {Object} config - Trap configuration
9
- * @returns {Function} - Middleware function
10
- */
11
3
  function createTrapInjector(config) {
12
4
  const trapPaths = config.traps.paths;
13
5
 
14
6
  return function injectTraps(req, res, next) {
15
- // Only inject if trap injection is enabled
16
- if (!config.traps.injection.enabled) {
17
- return next();
18
- }
7
+ if (!config.traps.injection.enabled) return next();
19
8
 
20
- // Intercept res.send to inject traps
21
9
  const originalSend = res.send;
22
-
23
10
  res.send = function (body) {
24
- // Only inject if the content is HTML
25
11
  if (typeof body === 'string' && (body.includes('<html') || body.includes('<body'))) {
26
12
  try {
27
13
  const $ = cheerio.load(body);
28
-
29
- // Pick a random trap
30
14
  const trapUrl = trapPaths[Math.floor(Math.random() * trapPaths.length)];
31
-
32
- // Create invisible link (Hidden by CSS)
33
- const trapLink = `<a href="${trapUrl}" style="opacity:0; position:absolute; z-index:-999; left:-9999px;">Admin Panel</a>`;
34
-
35
- // Inject into body
36
- $('body').append(trapLink);
37
-
15
+ $('body').append(`<a href="${trapUrl}" style="opacity:0;position:absolute;z-index:-999;left:-9999px;">Admin Panel</a>`);
38
16
  body = $.html();
39
17
  } catch (err) {
40
- // If cheerio fails, just send original body
41
- console.error('[TrapInjector] Failed to inject trap:', err.message);
18
+ // If injection fails, send original body
42
19
  }
43
20
  }
44
-
45
21
  originalSend.call(this, body);
46
22
  };
47
-
48
23
  next();
49
24
  };
50
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "honeyweb-core",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Production-ready honeypot middleware with behavioral analysis, bot fingerprinting, and AI threat intelligence",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,16 +1,11 @@
1
1
  // honeyweb-core/storage/bot-tracker.js
2
- // Track legitimate bot visits for dashboard display
3
2
 
4
3
  class BotTracker {
5
4
  constructor() {
6
- this.botVisits = new Map(); // botName -> array of visits
7
- this.maxVisitsPerBot = 50; // Keep last 50 visits per bot
5
+ this.botVisits = new Map();
6
+ this.maxVisitsPerBot = 50;
8
7
  }
9
8
 
10
- /**
11
- * Record a legitimate bot visit
12
- * @param {Object} visit - Visit data
13
- */
14
9
  recordVisit(visit) {
15
10
  const { botName, ip, path, userAgent, verified, timestamp } = visit;
16
11
 
@@ -19,49 +14,23 @@ class BotTracker {
19
14
  }
20
15
 
21
16
  const visits = this.botVisits.get(botName);
17
+ visits.unshift({ ip, path, userAgent, verified, timestamp: timestamp || Date.now() });
22
18
 
23
- // Add new visit at the beginning (most recent first)
24
- visits.unshift({
25
- ip,
26
- path,
27
- userAgent,
28
- verified,
29
- timestamp: timestamp || Date.now()
30
- });
31
-
32
- // Keep only last N visits
33
- if (visits.length > this.maxVisitsPerBot) {
34
- visits.pop();
35
- }
19
+ if (visits.length > this.maxVisitsPerBot) visits.pop();
36
20
  }
37
21
 
38
- /**
39
- * Get all bot visits grouped by bot name
40
- * @returns {Object} - Bot visits grouped by name
41
- */
42
22
  getAllVisits() {
43
23
  const result = {};
44
-
45
24
  for (const [botName, visits] of this.botVisits.entries()) {
46
25
  result[botName] = visits;
47
26
  }
48
-
49
27
  return result;
50
28
  }
51
29
 
52
- /**
53
- * Get visits for a specific bot
54
- * @param {string} botName - Bot name
55
- * @returns {Array} - Array of visits
56
- */
57
30
  getVisitsByBot(botName) {
58
31
  return this.botVisits.get(botName) || [];
59
32
  }
60
33
 
61
- /**
62
- * Get statistics
63
- * @returns {Object} - Statistics
64
- */
65
34
  getStats() {
66
35
  let totalVisits = 0;
67
36
  const botCounts = {};
@@ -71,37 +40,21 @@ class BotTracker {
71
40
  botCounts[botName] = visits.length;
72
41
  }
73
42
 
74
- return {
75
- totalBots: this.botVisits.size,
76
- totalVisits,
77
- botCounts
78
- };
43
+ return { totalBots: this.botVisits.size, totalVisits, botCounts };
79
44
  }
80
45
 
81
- /**
82
- * Clear all bot visits
83
- */
84
46
  clear() {
85
47
  this.botVisits.clear();
86
48
  }
87
49
 
88
- /**
89
- * Cleanup old visits (older than 7 days)
90
- */
91
50
  cleanup() {
92
- const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
51
+ const maxAge = 7 * 24 * 60 * 60 * 1000;
93
52
  const now = Date.now();
94
53
 
95
54
  for (const [botName, visits] of this.botVisits.entries()) {
96
- const filtered = visits.filter(visit => {
97
- return (now - visit.timestamp) < maxAge;
98
- });
99
-
100
- if (filtered.length === 0) {
101
- this.botVisits.delete(botName);
102
- } else {
103
- this.botVisits.set(botName, filtered);
104
- }
55
+ const filtered = visits.filter(v => (now - v.timestamp) < maxAge);
56
+ if (filtered.length === 0) this.botVisits.delete(botName);
57
+ else this.botVisits.set(botName, filtered);
105
58
  }
106
59
  }
107
60
  }
package/storage/index.js CHANGED
@@ -1,24 +1,15 @@
1
1
  // honeyweb-core/storage/index.js
2
- // Storage factory
3
2
 
4
3
  const JsonStore = require('./json-store');
5
4
  const MemoryStore = require('./memory-store');
6
5
 
7
- /**
8
- * Create storage instance based on configuration
9
- * @param {Object} config - Storage configuration
10
- * @returns {JsonStore|MemoryStore}
11
- */
12
6
  function createStorage(config) {
13
7
  const type = config.type || 'json';
14
8
 
15
9
  switch (type) {
16
- case 'json':
17
- return new JsonStore(config);
18
- case 'memory':
19
- return new MemoryStore(config);
20
- default:
21
- throw new Error(`[HoneyWeb] Unknown storage type: ${type}`);
10
+ case 'json': return new JsonStore(config);
11
+ case 'memory': return new MemoryStore(config);
12
+ default: throw new Error(`[HoneyWeb] Unknown storage type: ${type}`);
22
13
  }
23
14
  }
24
15
 
@@ -1,5 +1,4 @@
1
1
  // honeyweb-core/storage/json-store.js
2
- // Async JSON storage with in-memory cache
3
2
 
4
3
  const fs = require('fs-extra');
5
4
  const path = require('path');
@@ -8,18 +7,14 @@ const LRUCache = require('../utils/cache');
8
7
  class JsonStore {
9
8
  constructor(config) {
10
9
  this.filePath = path.resolve(config.path || './blocked-ips.json');
11
- this.banDuration = config.banDuration || 86400000; // 24 hours
10
+ this.banDuration = config.banDuration || 86400000;
12
11
  this.autoCleanup = config.autoCleanup !== false;
13
- this.cache = config.cache ? new LRUCache(1000, 60000) : null; // 1 minute cache
12
+ this.cache = config.cache ? new LRUCache(1000, 60000) : null;
14
13
 
15
- // Initialize file
16
14
  this._initFile();
17
15
 
18
- // Start auto-cleanup if enabled
19
16
  if (this.autoCleanup) {
20
- this.cleanupInterval = setInterval(() => {
21
- this._cleanupExpired();
22
- }, 300000); // Clean up every 5 minutes
17
+ this.cleanupInterval = setInterval(() => this._cleanupExpired(), 300000);
23
18
  }
24
19
  }
25
20
 
@@ -33,24 +28,12 @@ class JsonStore {
33
28
  }
34
29
  }
35
30
 
36
- /**
37
- * Get all banned IPs
38
- * @returns {Promise<Array>} - Array of banned IP objects
39
- */
40
31
  async getAll() {
41
- // Check cache first
42
- if (this.cache && this.cache.has('all')) {
43
- return this.cache.get('all');
44
- }
32
+ if (this.cache && this.cache.has('all')) return this.cache.get('all');
45
33
 
46
34
  try {
47
35
  const data = await fs.readJson(this.filePath);
48
-
49
- // Cache the result
50
- if (this.cache) {
51
- this.cache.set('all', data);
52
- }
53
-
36
+ if (this.cache) this.cache.set('all', data);
54
37
  return data;
55
38
  } catch (err) {
56
39
  console.error('[JsonStore] Error reading file:', err);
@@ -58,24 +41,16 @@ class JsonStore {
58
41
  }
59
42
  }
60
43
 
61
- /**
62
- * Check if IP is banned
63
- * @param {string} ip
64
- * @returns {Promise<boolean>}
65
- */
66
44
  async isBanned(ip) {
67
45
  const bannedIPs = await this.getAll();
68
46
 
69
- // Support both simple array format and object format
70
47
  if (bannedIPs.length > 0 && typeof bannedIPs[0] === 'string') {
71
48
  return bannedIPs.includes(ip);
72
49
  }
73
50
 
74
- // Object format with expiry
75
51
  const entry = bannedIPs.find(entry => entry.ip === ip);
76
52
  if (!entry) return false;
77
53
 
78
- // Check if expired
79
54
  if (entry.expiry && Date.now() > entry.expiry) {
80
55
  await this.unban(ip);
81
56
  return false;
@@ -84,79 +59,47 @@ class JsonStore {
84
59
  return true;
85
60
  }
86
61
 
87
- /**
88
- * Ban an IP
89
- * @param {string} ip
90
- * @param {string} reason
91
- * @returns {Promise<void>}
92
- */
93
62
  async ban(ip, reason = 'Suspicious activity') {
94
63
  const bannedIPs = await this.getAll();
95
64
 
96
- // Check if already banned
97
- const existingIndex = bannedIPs.findIndex(entry =>
65
+ const exists = bannedIPs.some(entry =>
98
66
  typeof entry === 'string' ? entry === ip : entry.ip === ip
99
67
  );
68
+ if (exists) return;
100
69
 
101
- if (existingIndex !== -1) {
102
- return; // Already banned
103
- }
104
-
105
- // Add new ban with expiry
106
- const banEntry = {
70
+ bannedIPs.push({
107
71
  ip,
108
72
  reason,
109
73
  timestamp: Date.now(),
110
74
  expiry: Date.now() + this.banDuration
111
- };
112
-
113
- bannedIPs.push(banEntry);
75
+ });
114
76
 
115
77
  await this._save(bannedIPs);
116
78
  }
117
79
 
118
- /**
119
- * Unban an IP
120
- * @param {string} ip
121
- * @returns {Promise<void>}
122
- */
123
80
  async unban(ip) {
124
81
  const bannedIPs = await this.getAll();
125
-
126
82
  const filtered = bannedIPs.filter(entry =>
127
83
  typeof entry === 'string' ? entry !== ip : entry.ip !== ip
128
84
  );
129
-
130
85
  await this._save(filtered);
131
86
  }
132
87
 
133
- /**
134
- * Save data to file and invalidate cache
135
- * @private
136
- */
137
88
  async _save(data) {
138
89
  try {
139
90
  await fs.writeJson(this.filePath, data, { spaces: 2 });
140
-
141
- // Invalidate cache
142
- if (this.cache) {
143
- this.cache.delete('all');
144
- }
91
+ if (this.cache) this.cache.delete('all');
145
92
  } catch (err) {
146
93
  console.error('[JsonStore] Error writing file:', err);
147
94
  }
148
95
  }
149
96
 
150
- /**
151
- * Clean up expired bans
152
- * @private
153
- */
154
97
  async _cleanupExpired() {
155
98
  const bannedIPs = await this.getAll();
156
99
  const now = Date.now();
157
100
 
158
101
  const active = bannedIPs.filter(entry => {
159
- if (typeof entry === 'string') return true; // Keep simple format
102
+ if (typeof entry === 'string') return true;
160
103
  return !entry.expiry || now <= entry.expiry;
161
104
  });
162
105
 
@@ -165,41 +108,24 @@ class JsonStore {
165
108
  }
166
109
  }
167
110
 
168
- /**
169
- * Get statistics
170
- * @returns {Promise<Object>}
171
- */
172
111
  async getStats() {
173
112
  const bannedIPs = await this.getAll();
174
113
  const now = Date.now();
175
-
176
- let active = 0;
177
- let expired = 0;
114
+ let active = 0, expired = 0;
178
115
 
179
116
  bannedIPs.forEach(entry => {
180
- if (typeof entry === 'string') {
181
- active++;
182
- } else if (!entry.expiry || now <= entry.expiry) {
117
+ if (typeof entry === 'string' || !entry.expiry || now <= entry.expiry) {
183
118
  active++;
184
119
  } else {
185
120
  expired++;
186
121
  }
187
122
  });
188
123
 
189
- return {
190
- total: bannedIPs.length,
191
- active,
192
- expired
193
- };
124
+ return { total: bannedIPs.length, active, expired };
194
125
  }
195
126
 
196
- /**
197
- * Cleanup and stop intervals
198
- */
199
127
  destroy() {
200
- if (this.cleanupInterval) {
201
- clearInterval(this.cleanupInterval);
202
- }
128
+ if (this.cleanupInterval) clearInterval(this.cleanupInterval);
203
129
  }
204
130
  }
205
131