cipher-shield 1.0.0

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.
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Ghost Route Handler v2.1
3
+ *
4
+ * Advanced honeypot route detection system that identifies access to sensitive
5
+ * or trap endpoints. Implements multiple detection strategies for comprehensive
6
+ * coverage while maintaining high performance.
7
+ *
8
+ * Detection Strategies:
9
+ * - Exact path matching
10
+ * - Prefix matching for directory structures
11
+ * - File extension detection (e.g., .env, .git)
12
+ * - Pattern-based matching with wildcards
13
+ *
14
+ * Performance optimized with:
15
+ * - Pre-compiled regex patterns
16
+ * - Early termination on matches
17
+ * - Memory-efficient string operations
18
+ *
19
+ * @module ghostHandler
20
+ * @version 1.0.0
21
+ * @author Omindu Dissanayaka
22
+ * @license MIT
23
+ */
24
+
25
+ /**
26
+ * Pre-compiled regex patterns for performance with security limits
27
+ * @private
28
+ * @type {Object.<string, RegExp>}
29
+ */
30
+ const PATTERN_CACHE = new Map();
31
+
32
+ /**
33
+ * Maximum pattern complexity to prevent ReDoS attacks
34
+ * @private
35
+ * @const {number}
36
+ */
37
+ const MAX_PATTERN_LENGTH = 200;
38
+ const MAX_WILDCARD_COUNT = 3;
39
+
40
+ /**
41
+ * Compiles a ghost route pattern into an optimized regex with security checks
42
+ *
43
+ * @private
44
+ * @param {string} pattern - Ghost route pattern
45
+ * @returns {RegExp|null} Compiled regex for pattern matching or null if unsafe
46
+ */
47
+ function compilePattern(pattern) {
48
+ if (PATTERN_CACHE.has(pattern)) {
49
+ return PATTERN_CACHE.get(pattern);
50
+ }
51
+
52
+ if (pattern.length > MAX_PATTERN_LENGTH) {
53
+ return null;
54
+ }
55
+
56
+ const wildcardCount = (pattern.match(/\*/g) || []).length;
57
+ if (wildcardCount > MAX_WILDCARD_COUNT) {
58
+ return null;
59
+ }
60
+
61
+ try {
62
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
63
+
64
+ const regexPattern = escaped.replace(/\\\*/g, '[^/]*?');
65
+
66
+ const regex = new RegExp(`^${regexPattern}`, 'i');
67
+
68
+ if (!isSafeRegex(regex)) {
69
+ return null;
70
+ }
71
+
72
+ PATTERN_CACHE.set(pattern, regex);
73
+ return regex;
74
+
75
+ } catch (error) {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Tests regex for potential ReDoS vulnerabilities
82
+ *
83
+ * @private
84
+ * @param {RegExp} regex - Regex to test
85
+ * @returns {boolean} True if regex appears safe
86
+ */
87
+ function isSafeRegex(regex) {
88
+ const testInputs = [
89
+ 'a'.repeat(1000),
90
+ 'a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z',
91
+ '/admin/admin/admin/admin/admin/admin/admin/admin/admin',
92
+ '///admin///admin///admin///admin///admin'
93
+ ];
94
+
95
+ for (const input of testInputs) {
96
+ try {
97
+ const startTime = Date.now();
98
+ regex.test(input);
99
+ const executionTime = Date.now() - startTime;
100
+
101
+ if (executionTime > 100) {
102
+ return false;
103
+ }
104
+ } catch (error) {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ return true;
110
+ }
111
+
112
+ /**
113
+ * Detects if a request path matches any configured ghost route
114
+ *
115
+ * Implements multi-strategy detection:
116
+ * 1. Exact path matching (fastest)
117
+ * 2. Prefix matching for directory structures
118
+ * 3. File extension detection
119
+ * 4. Pattern matching with wildcards
120
+ *
121
+ * @function detect
122
+ * @param {string} requestPath - The request URL path to check
123
+ * @param {string[]} ghostRoutes - Array of ghost route patterns to match against
124
+ * @returns {boolean} True if the path matches any ghost route pattern
125
+ *
126
+ * @example
127
+ * ```javascript
128
+ * const ghostRoutes = ['/admin', '.env', '/secret/*'];
129
+ * console.log(detect('/admin', ghostRoutes)); // true
130
+ * console.log(detect('/admin/login', ghostRoutes)); // true (prefix match)
131
+ * console.log(detect('/config/.env', ghostRoutes)); // true (extension match)
132
+ * console.log(detect('/secret/password', ghostRoutes)); // true (wildcard match)
133
+ * console.log(detect('/public', ghostRoutes)); // false
134
+ * ```
135
+ */
136
+ function detect(requestPath, ghostRoutes) {
137
+ if (!requestPath || typeof requestPath !== 'string') {
138
+ return false;
139
+ }
140
+
141
+ if (!Array.isArray(ghostRoutes) || ghostRoutes.length === 0) {
142
+ return false;
143
+ }
144
+
145
+ const normalizedPath = requestPath.split('?')[0].toLowerCase().trim();
146
+
147
+ const cleanPath = normalizedPath.replace(/\/+$/, '') || '/';
148
+
149
+ for (const pattern of ghostRoutes) {
150
+ if (!pattern || typeof pattern !== 'string') {
151
+ continue;
152
+ }
153
+
154
+ const normalizedPattern = pattern.toLowerCase().trim();
155
+
156
+ if (cleanPath === normalizedPattern) {
157
+ return true;
158
+ }
159
+
160
+ if (cleanPath.startsWith(normalizedPattern + '/')) {
161
+ return true;
162
+ }
163
+
164
+ if (normalizedPattern.startsWith('.') && cleanPath.includes(normalizedPattern)) {
165
+ return true;
166
+ }
167
+
168
+ if (normalizedPattern.includes('*')) {
169
+ const regex = compilePattern(normalizedPattern);
170
+ if (regex && regex.test(cleanPath)) {
171
+ return true;
172
+ }
173
+ }
174
+
175
+ if (normalizedPattern.startsWith('/') && cleanPath.endsWith(normalizedPattern)) {
176
+ return true;
177
+ }
178
+ }
179
+
180
+ return false;
181
+ }
182
+
183
+ /**
184
+ * Validates a ghost route pattern for correctness
185
+ *
186
+ * @function validatePattern
187
+ * @param {string} pattern - Pattern to validate
188
+ * @returns {boolean} True if pattern is valid
189
+ *
190
+ * @example
191
+ * ```javascript
192
+ * console.log(validatePattern('/admin')); // true
193
+ * console.log(validatePattern('.env')); // true
194
+ * console.log(validatePattern('/secret/*')); // true
195
+ * console.log(validatePattern('')); // false
196
+ * ```
197
+ */
198
+ function validatePattern(pattern) {
199
+ if (!pattern || typeof pattern !== 'string') {
200
+ return false;
201
+ }
202
+
203
+ const trimmed = pattern.trim();
204
+ if (trimmed.length === 0) {
205
+ return false;
206
+ }
207
+
208
+ if (trimmed === '/' || trimmed === '/*') {
209
+ return false;
210
+ }
211
+
212
+ return true;
213
+ }
214
+
215
+ /**
216
+ * Gets statistics about ghost route patterns
217
+ *
218
+ * @function getPatternStats
219
+ * @param {string[]} ghostRoutes - Array of ghost route patterns
220
+ * @returns {Object} Statistics about the patterns
221
+ *
222
+ * @example
223
+ * ```javascript
224
+ * const stats = getPatternStats(['/admin', '.env', '/secret/*']);
225
+ * console.log(stats); // { total: 3, exact: 1, extensions: 1, wildcards: 1 }
226
+ * ```
227
+ */
228
+ function getPatternStats(ghostRoutes) {
229
+ if (!Array.isArray(ghostRoutes)) {
230
+ return { total: 0, exact: 0, extensions: 0, wildcards: 0, invalid: 0 };
231
+ }
232
+
233
+ let exact = 0;
234
+ let extensions = 0;
235
+ let wildcards = 0;
236
+ let invalid = 0;
237
+
238
+ for (const pattern of ghostRoutes) {
239
+ if (!validatePattern(pattern)) {
240
+ invalid++;
241
+ continue;
242
+ }
243
+
244
+ if (pattern.includes('*')) {
245
+ wildcards++;
246
+ } else if (pattern.startsWith('.')) {
247
+ extensions++;
248
+ } else {
249
+ exact++;
250
+ }
251
+ }
252
+
253
+ return {
254
+ total: ghostRoutes.length,
255
+ exact,
256
+ extensions,
257
+ wildcards,
258
+ invalid
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Clears the pattern cache (useful for testing or memory management)
264
+ *
265
+ * @function clearCache
266
+ * @returns {number} Number of cached patterns cleared
267
+ */
268
+ function clearCache() {
269
+ const size = PATTERN_CACHE.size;
270
+ PATTERN_CACHE.clear();
271
+ return size;
272
+ }
273
+
274
+ module.exports = {
275
+ detect,
276
+ validatePattern,
277
+ getPatternStats,
278
+ clearCache
279
+ };
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Shadow Ban Handler v2.1
3
+ *
4
+ * Intelligent time-wasting system for malicious IPs that delays responses
5
+ * without blocking the event loop or consuming excessive resources.
6
+ * Implements adaptive delays based on threat levels and DEFCON state.
7
+ *
8
+ * Key Features:
9
+ * - Non-blocking async delays that waste attacker time
10
+ * - Adaptive delay scaling based on DEFCON state
11
+ * - Configurable jitter to prevent timing analysis
12
+ * - Performance monitoring and statistics
13
+ * - Memory-efficient operation
14
+ *
15
+ * Shadow Ban vs Traditional Ban:
16
+ * - Doesn't immediately block (prevents attacker awareness)
17
+ * - Wastes attacker time with delays
18
+ * - Allows monitoring of attack patterns
19
+ * - Automatically expires via blacklist system
20
+ *
21
+ * @module shadowHandler
22
+ * @version 1.0.0
23
+ * @author Omindu Dissanayaka
24
+ * @license MIT
25
+ */
26
+
27
+ /**
28
+ * Default configuration constants
29
+ * @readonly
30
+ */
31
+ const DEFAULT_CONFIG = Object.freeze({
32
+ MIN_DELAY: 1000,
33
+ MAX_DELAY: 45000,
34
+ JITTER_PERCENT: 0.2,
35
+ RED_MULTIPLIER: 1.5,
36
+ YELLOW_MULTIPLIER: 1.2
37
+ });
38
+
39
+ /**
40
+ * Performance tracking statistics
41
+ * @private
42
+ */
43
+ let stats = {
44
+ totalDelays: 0,
45
+ totalDelayTime: 0,
46
+ averageDelay: 0,
47
+ lastDelay: 0,
48
+ delaysByDefcon: {
49
+ GREEN: 0,
50
+ RED: 0
51
+ }
52
+ };
53
+
54
+ /**
55
+ * Calculates adaptive delay based on DEFCON state and base delay
56
+ *
57
+ * @private
58
+ * @param {number} baseDelay - Base delay time in milliseconds
59
+ * @param {string} defconState - Current DEFCON state ('GREEN', 'RED', etc.)
60
+ * @returns {number} Calculated delay with DEFCON multiplier applied
61
+ */
62
+ function calculateAdaptiveDelay(baseDelay, defconState) {
63
+ let multiplier = 1.0;
64
+
65
+ switch (defconState) {
66
+ case 'RED':
67
+ multiplier = DEFAULT_CONFIG.RED_MULTIPLIER;
68
+ break;
69
+ case 'YELLOW':
70
+ multiplier = DEFAULT_CONFIG.YELLOW_MULTIPLIER;
71
+ break;
72
+ case 'GREEN':
73
+ default:
74
+ multiplier = 1.0;
75
+ break;
76
+ }
77
+
78
+ const adaptiveDelay = baseDelay * multiplier;
79
+
80
+ return Math.max(DEFAULT_CONFIG.MIN_DELAY,
81
+ Math.min(adaptiveDelay, DEFAULT_CONFIG.MAX_DELAY));
82
+ }
83
+
84
+ /**
85
+ * Applies random jitter to delay to prevent timing analysis attacks
86
+ * Uses cryptographically secure randomness for unpredictability
87
+ *
88
+ * @private
89
+ * @param {number} delay - Base delay time
90
+ * @returns {number} Delay with secure random jitter applied
91
+ */
92
+ function applyJitter(delay) {
93
+ const jitterRange = delay * DEFAULT_CONFIG.JITTER_PERCENT;
94
+ const randomBytes = require('crypto').randomBytes(4);
95
+ const randomValue = randomBytes.readUInt32LE(0) / 0xFFFFFFFF;
96
+
97
+ const jitter = jitterRange * (randomValue * 2 - 1);
98
+ return Math.max(DEFAULT_CONFIG.MIN_DELAY, Math.floor(delay + jitter));
99
+ }
100
+
101
+ /**
102
+ * Delays request processing for blacklisted IPs using shadow ban technique
103
+ *
104
+ * This function implements the core shadow ban mechanism:
105
+ * 1. Calculates adaptive delay based on DEFCON state
106
+ * 2. Applies random jitter to prevent timing analysis
107
+ * 3. Performs non-blocking async delay
108
+ * 4. Updates performance statistics
109
+ * 5. Returns blocking decision
110
+ *
111
+ * The delay wastes attacker time while allowing legitimate monitoring
112
+ * of attack patterns and automated threat response.
113
+ *
114
+ * @function delay
115
+ * @param {string} ip - Client IP address being shadow banned
116
+ * @param {number} delayTime - Base delay duration in milliseconds
117
+ * @param {string} defconState - Current DEFCON state ('GREEN', 'RED', etc.)
118
+ * @returns {Promise<boolean>} Always returns true (indicating request should be blocked after delay)
119
+ *
120
+ * @example
121
+ * ```javascript
122
+ * // Shadow ban an IP for 5 seconds in GREEN state
123
+ * const shouldBlock = await shadowHandler.delay('192.168.1.100', 5000, 'GREEN');
124
+ * if (shouldBlock) {
125
+ * res.status(408).json({ error: 'Request Timeout' });
126
+ * }
127
+ * ```
128
+ */
129
+ async function delay(ip, delayTime, defconState = 'GREEN') {
130
+ const startTime = process.hrtime.bigint();
131
+
132
+ const baseDelay = Math.max(0, Math.min(delayTime || 0, DEFAULT_CONFIG.MAX_DELAY));
133
+ const cleanDefconState = (defconState || 'GREEN').toUpperCase();
134
+
135
+ const adaptiveDelay = calculateAdaptiveDelay(baseDelay, cleanDefconState);
136
+
137
+ const finalDelay = applyJitter(adaptiveDelay);
138
+
139
+ if (process.env.NODE_ENV === 'development') {
140
+ console.log(`[SHADOW] Delaying ${ip} for ${finalDelay}ms (${cleanDefconState} state)`);
141
+ }
142
+
143
+ await new Promise(resolve => {
144
+ setTimeout(resolve, finalDelay);
145
+ });
146
+
147
+ const delayDuration = Number(process.hrtime.bigint() - startTime) / 1000000;
148
+ stats.totalDelays++;
149
+ stats.totalDelayTime += delayDuration;
150
+ stats.averageDelay = stats.totalDelayTime / stats.totalDelays;
151
+ stats.lastDelay = delayDuration;
152
+ stats.delaysByDefcon[cleanDefconState] = (stats.delaysByDefcon[cleanDefconState] || 0) + 1;
153
+
154
+ return true;
155
+ }
156
+
157
+ /**
158
+ * Calculates random jitter for delay unpredictability (legacy function)
159
+ *
160
+ * @deprecated Use applyJitter() internally. This function is kept for backward compatibility.
161
+ * @function addJitter
162
+ * @param {number} baseDelay - Base delay time in milliseconds
163
+ * @returns {number} Delay with jitter applied
164
+ */
165
+ function addJitter(baseDelay) {
166
+ return applyJitter(baseDelay);
167
+ }
168
+
169
+ /**
170
+ * Retrieves shadow ban performance statistics
171
+ *
172
+ * @function getStats
173
+ * @returns {Object} Performance statistics
174
+ * @property {number} totalDelays - Total number of delays performed
175
+ * @property {number} totalDelayTime - Total time spent delaying (ms)
176
+ * @property {number} averageDelay - Average delay duration (ms)
177
+ * @property {number} lastDelay - Most recent delay duration (ms)
178
+ * @property {Object} delaysByDefcon - Delays grouped by DEFCON state
179
+ *
180
+ * @example
181
+ * ```javascript
182
+ * const stats = shadowHandler.getStats();
183
+ * console.log(`Average delay: ${stats.averageDelay}ms`);
184
+ * ```
185
+ */
186
+ function getStats() {
187
+ return {
188
+ ...stats,
189
+ config: DEFAULT_CONFIG
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Resets performance statistics (useful for testing)
195
+ *
196
+ * @function resetStats
197
+ * @returns {Object} Previous statistics
198
+ */
199
+ function resetStats() {
200
+ const previousStats = { ...stats };
201
+ stats = {
202
+ totalDelays: 0,
203
+ totalDelayTime: 0,
204
+ averageDelay: 0,
205
+ lastDelay: 0,
206
+ delaysByDefcon: {
207
+ GREEN: 0,
208
+ RED: 0
209
+ }
210
+ };
211
+ return previousStats;
212
+ }
213
+
214
+ /**
215
+ * Validates delay parameters for security and performance
216
+ *
217
+ * @function validateDelayParams
218
+ * @param {number} delayTime - Delay time to validate
219
+ * @param {string} defconState - DEFCON state to validate
220
+ * @returns {Object} Validation result
221
+ * @property {boolean} valid - Whether parameters are valid
222
+ * @property {string[]} errors - Array of validation errors
223
+ * @property {Object} sanitized - Sanitized parameters
224
+ */
225
+ function validateDelayParams(delayTime, defconState) {
226
+ const errors = [];
227
+ const sanitized = {
228
+ delayTime: DEFAULT_CONFIG.MIN_DELAY,
229
+ defconState: 'GREEN'
230
+ };
231
+
232
+ if (typeof delayTime !== 'number' || isNaN(delayTime)) {
233
+ errors.push('Delay time must be a valid number');
234
+ } else if (delayTime < 0) {
235
+ errors.push('Delay time cannot be negative');
236
+ sanitized.delayTime = DEFAULT_CONFIG.MIN_DELAY;
237
+ } else if (delayTime > DEFAULT_CONFIG.MAX_DELAY) {
238
+ errors.push(`Delay time cannot exceed ${DEFAULT_CONFIG.MAX_DELAY}ms`);
239
+ sanitized.delayTime = DEFAULT_CONFIG.MAX_DELAY;
240
+ } else {
241
+ sanitized.delayTime = delayTime;
242
+ }
243
+
244
+ const validStates = ['GREEN', 'YELLOW', 'RED'];
245
+ const upperState = (defconState || '').toUpperCase();
246
+ if (!validStates.includes(upperState)) {
247
+ errors.push(`Invalid DEFCON state: ${defconState}. Must be one of: ${validStates.join(', ')}`);
248
+ } else {
249
+ sanitized.defconState = upperState;
250
+ }
251
+
252
+ return {
253
+ valid: errors.length === 0,
254
+ errors,
255
+ sanitized
256
+ };
257
+ }
258
+
259
+ module.exports = {
260
+ delay,
261
+ addJitter,
262
+ getStats,
263
+ resetStats,
264
+ validateDelayParams,
265
+ DEFAULT_CONFIG
266
+ };